发布与订阅 P52

Redis 实现了发布与订阅(publish/subscribe)模式,又称 pub/sub 模式(与设计模式中的观察者模式类似)。订阅者负责订阅频道,发送者负责向频道发送二进制字符串消息。每当有消息被发送至给定频道时,频道的所有订阅者都会接收到消息。

发布与订阅命令 P52
命令 格式 描述
SUBSCRIBE SUBSCRIBE channel [channel ...] 订阅一个或多个频道
UNSUBSCRIBE UNSUBSCRIBE [channel [channel ...]] 退订一个或多个频道;没有指定频道,则退订全部频道
PUBLISH PUBLISH channel message 给指定频道发送消息,返回接收到消息的订阅者数量
PSUBSCRIBE PSUBSCRIBE pattern [pattern ...] 订阅一个或多个模式,与模式匹配的频道均会订阅
PUNSUBSCRIBE PUNSUBSCRIBE [pattern [pattern ...]] 退订一个或多个模式;没有指定模式,则退订全部模式

相关演示代码如下:

// 执行发布订阅相关操作(注意:pubSubConn 中的 Conn 对象不能是 conn 对象,即必须建立两个不同的连接)
func executePubSubOperation(pubSubConn redis.PubSubConn, conn redis.Conn) {
// 监听频道消息并输出
go func() {
for ; ; {
switch result := pubSubConn.Receive().(type) {
case redis.Message:
// byte 转 string
resultMap := map[string]string {
"Channel": result.Channel,
"Pattern": result.Pattern,
"Data": string(result.Data),
}
handleResult(resultMap, nil)
case redis.Subscription:
handleResult(result, nil)
} }
}() // 订阅两个频道(由于 Subscribe 内没有执行 Receive,所以只有 error,没有错误时就输出 nil)
// 订阅者收到相应的消息订阅信息,分别输出 -> {subscribe channel_1 1} 和 {subscribe channel_2 2}
handleResult(nil, pubSubConn.Subscribe("channel_1", "channel_2"))
// 订阅两个模式,分别以 _1 和 g_2 为结尾的频道 (由于 PSubscribe 内没有执行 Receive,所以只有 error,没有错误时就输出 nil)
// 订阅者收到相应的消息订阅信息,分别输出 -> {psubscribe *_1 3} 和 {psubscribe *g_2 4}
handleResult(nil, pubSubConn.PSubscribe("*_1", "*g_2")) time.Sleep(time.Second) // 发布消息到频道 channel_1,输出 -> 2,两个订阅者接收到消息
// 订阅者分别输出 -> map[Channel:channel_1 Data:channel1 Pattern:] 和 map[Channel:channel_1 Data:channel1 Pattern:*_1]
handleResult(conn.Do("PUBLISH", "channel_1", "channel1"))
// 发布消息到频道 channel_2,输出 -> 1,一个订阅者接收到消息
// 订阅者输出 -> map[Channel:channel_2 Data:channel1 Pattern:]
handleResult(conn.Do("PUBLISH", "channel_2", "channel1")) // 退订两个频道(由于 Subscribe 内没有执行 Receive,所以只有 error,没有错误时就输出 nil)
// 订阅者收到相应的消息退订信息,分别输出 -> {unsubscribe channel_1 3} 和 {unsubscribe channel_2 2}
handleResult(nil, pubSubConn.Unsubscribe("channel_1", "channel_2"))
// 退订两个频道(由于 Subscribe 内没有执行 Receive,所以只有 error,没有错误时就输出 nil)
// 订阅者收到相应的消息退订信息,分别输出 -> {punsubscribe *_1 1} 和 {punsubscribe *g_2 0}
handleResult(nil, pubSubConn.PUnsubscribe("*_1", "*g_2")) time.Sleep(time.Second)
}
风险 P54
  • 稳定性:旧版 Redis 的客户端读取消息不够快时,不断积压的消息就会使 Redis 的缓冲区越来越大,可能导致 Redis 的速度变慢,甚至直接崩溃,也有使 Redis 可能被操作系统强制杀死。新版 Redis 会自动断开不符合 client-output-buffer-limit pubsub 配置选项要求的客户端。
  • 可靠性:任何网络系统在执行操作时都有可能会遇上断线情况,而断线产生的连接错误通常会使得网络连接两端中的其中一端进行重新连接。如果客户端在执行订阅操作的过程中断线,那么客户端将丢失在断线期间发送的所有消息。

排序 P54

SORT 命令可以对列表、集合和有序集合进行排序 ,可以将 SORT 命令看作使 SQL 中的 order by 子句。 P55

命令 格式 描述
SORT SORT key [BY pattern] [LIMIT offset count] [GET pattern [GET pattern ...]] [ASC|DESC] [ALPHA] [STORE destination] 根据给定的选项,返回或保存给定列表、集合、有序集合 key 中经过排序的元素

可实现功能: P55

  • 根据升序或降序排序元素(使用 [ASC|DESC],默认为升序)
  • 将元素看作数字或者字符串进行排序(使用 [ALPHA] 可以当作字符串排序,默认为数字)
  • 使用被排序元素之外的其他值作为权重来排序,甚至还可以从输入的列表、集合、有序集合以外的其他地方进行取值(使用 [BY pattern] 可以根据指定值排序;可以使用不存在的键作为参数选项跳过排序没直接返回结果)
  • 使用被排序元素之外的其他值作为返回结果(使用 [GET pattern [GET pattern ...]] 可以根据排序结果返回相应的值)
  • 保存排序结果(使用 [STORE destination] 可以指定将结果保存到指定 key,此时返回保存的元素的数量)
  • 限制返回结果(使用 [LIMIT offset count] 可以指定要跳过的元素数量和返回的元素数量)

相关演示代码如下:

// 执行 SORT 命令
func executeSortOperation(conn redis.Conn) {
// 删除原有值
handleResult(redis.Int(conn.Do("DEL", "id", "age", "name", "destination")))
// 初始化
handleResult(redis.Int(conn.Do("RPUSH", "id", 1, 4, 3, 2, 5)))
handleResult(redis.String(conn.Do("SET", "age_1", 15)))
handleResult(redis.String(conn.Do("SET", "age_2", 14)))
handleResult(redis.String(conn.Do("SET", "age_3", 11)))
handleResult(redis.String(conn.Do("SET", "age_4", 12)))
handleResult(redis.String(conn.Do("SET", "age_5", 10)))
handleResult(redis.String(conn.Do("SET", "name_1", "tom")))
handleResult(redis.String(conn.Do("SET", "name_2", "jerry")))
handleResult(redis.String(conn.Do("SET", "name_3", "bob")))
handleResult(redis.String(conn.Do("SET", "name_4", "mary")))
handleResult(redis.String(conn.Do("SET", "name_5", "jack"))) // 根据 id 降序排序,跳过第一个元素,获取接下来的两个元素,输出 -> [4 3]
handleResult(redis.Ints(conn.Do("SORT", "id", "LIMIT", "1", "2", "DESC")))
// 根据 age_{id} 升序排序,按照 id age_{id} name_{id} 顺序返回结果,输出 -> [5 10 jack 3 11 bob 4 12 mary 2 14 jerry 1 15 tom]
handleResult(redis.Strings(conn.Do("SORT", "id", "BY", "age_*", "GET", "#", "GET", "age_*", "GET", "name_*", "ALPHA")))
// 根据 name_{id} 字典序降序排序,按照 id age_{id} name_{id} 顺序返回结果,存储到 destination 中
// 输出 -> 15
handleResult(redis.Int(conn.Do("SORT", "id", "BY", "name_*", "GET", "#", "GET", "age_*", "GET", "name_*", "ALPHA", "DESC", "STORE", "destination")))
// 输出 列表 结果,输出 -> [1 15 tom 4 12 mary 2 14 jerry 5 10 jack 3 11 bob]
handleResult(redis.Strings(conn.Do("LRANGE", "destination", 0, -1)))
}

基本的 Redis 事务

Redis 有 5 个命令可以让用户在不被打断的情况下对多个键执行操作,它们分别是: WATCHMULTIEXECUNWATCHDISCART 。基本的 Redis 事务只用 MULTIEXEC 即可,使用多个命令的事务将在以后进行介绍。 P56

Redis 的基本事务可以让一个客户端在不被其他客户端打断的情况下执行多个命令。当一个事务执行完毕之后, Redis 才会处理其他客户端的命令。 P56

假如某个(或某些) key 正处于 WATCH 命令的监视之下,且事务块中有和这个(或这些) key 相关的命令,那么 EXEC 命令只在这个(或这些) key 没有被其他命令所改动的情况下执行并生效,否则该事务被打断(abort)。

命令 格式 描述
MULTI MULTI 标记一个事务块的开始,总是返回 OK
EXEC EXEC 执行所有事务块内的命令,按顺序返回命令的执行结果。当操作被打断时,返回 nil

相关演示代码如下:

// 执行事务命令
func executeTransactionOperation(conn redis.Conn) {
// 删除原有值
handleResult(redis.Int(conn.Do("DEL", "counter")))
// 开启事务(采用流水线方式,降低通信开销)
handleResult(nil, conn.Send("MULTI"))
// 事务中执行自增操作(采用流水线方式,降低通信开销)
handleResult(nil, conn.Send("INCR", "counter"))
handleResult(nil, conn.Send("INCR", "counter"))
handleResult(nil, conn.Send("INCR", "counter"))
// 执行命令,依次执行自增操作,分别返回操作结果,输出 -> [1 2 3]
handleResult(redis.Ints(conn.Do("EXEC")))
}
练习题:移除竞争条件 P58

简单实践 - 文章投票VoteArticle 函数内曾说明没有事务控制,会存在并发问题。该函数包含一个竞争条件以及一个因为竞争条件而出现的 bug 。函数的竞争条件可能会造成内存泄漏,而函数的 bug 则可能会导致不正确的投票结果出现。你能想办法修复它们吗?

提示:如果你觉得很难理解竞争条件为什么会导致内存泄漏,那么可以在分析 简单实践 - 文章投票 中的 PostArticle 的函数的同时,阅读一下 6.2.5 节。

  • 感觉还是无法理解为什么会有这种情况,强行猜测以下可能性(虽然都不是竞争条件造成的):

    • PostArticle 函数中,在将作者加入到投票用户集合中后,给其设定过期时间。如果设定过期时间之前由于某些原有异常导致没有进行相关操作,那么这个集合将一直在内存中,不会过期,从而造成内存泄漏。
    • VoteArticle 函数中,如果将投票用户添加到投票用户集合中后,还没来得及给文章的相关信息进行设置,那么这个用户以后不能再投票,并且文章的投票信息不对。
  • 不是太明白究竟在竞争什么,只能针对以上问题处理。用事务只能再添加一个集合在事务中标记事务是否执行成功,处理流程大致如下:

    1. 先将用户与文章作为值加入到这个集合
    2. 再将用户加入到投票集合中
    3. 然后开启事务,依次发送更新信息的命令和删除那个集合中的相关信息,并执行
    4. 最后有一个 worker 扫描这个集合,将其中值拿出来解析出用户和文章,再查改用户是否已在集合中,如果在集合中,则重新执行 步骤3,最后删除该值
练习题:提高性能 P58

简单实践 - 文章投票ListArticles 函数在获取整个页面的文章时,需要在 Redis 与客户端之间最多会进行 26 次通信往返,这种做法十分低效,你能否想个办法将 ListArticles 函数的往返次数降低为 2 次呢?

提示:使用流水线

  • 获取文章列表时,先获取相应的 id 列表(最多 25 个),再循环获取每个 id 对应的文章,所以最多会进行 26 次通信往返
  • 由于必须先获取 id 列表,再获取每个 id 对应的文章,所以只能将这两块分开,所以最低至少有 2 次通信往返。大致流程如下:
    1. 先获取 id 列表
    2. 使用流水线,依次将获取每个 id 的文章的命令发送至缓冲区,最后与服务端通信并执行命令(Go 中可以使用上述事务演示代码的方式进行操作 )
    3. 最后按照顺序解析结果

过期时间 P58

只有少数几个命令可以原子地为键设置过期时间,并且对于列表、集合、哈希表和有序集合这样的容器来说,键过期命令只能为整个键设置过期时间,而没办法为键里面的单个元素设置过期时间(可以使用存储时间戳的有序集合来实现针对单个元素的过期时间;也可以以前缀的形式将容器中的单个元素变为字符串)。 P58

用于处理过期时间的 Redis 命令 P59
命令 格式 描述
PERSIST PERSIST key 移除键的过期时间
TTL TTL key 查看键距离过期时间还有多少秒
EXPIRE EXPIRE key seconds 让键在指定的秒数之后过期
EXPIREAT EXPIREAT key timestamp 让键在指定的 UNIX 秒级时间戳过期
PTTL PTTL key 查看键距离过期时间还有多少毫秒
PEXPIRE PEXPIRE key milliseconds 让键在指定的毫秒数之后过期
PEXPIREAT PEXPIREAT key milliseconds-timestamp 让键在指定的 UNIX 毫秒级时间戳过期

相关演示代码如下:

// 指定过期时间相关的命令
func executeExpirationOperation(conn redis.Conn) {
// 删除原有值
handleResult(redis.Int(conn.Do("DEL", "string")))
// 设置字符串的值为 value,输出 -> OK,string 变为 -> value
handleResult(redis.String(conn.Do("SET", "string", "value")))
// 查看 string 的过期时间,输出 -> -1,表示不过期
handleResult(redis.Int(conn.Do("TTL", "string")))
// 设置 string 在 3 秒后过期,输出 -> 1
handleResult(redis.Int(conn.Do("EXPIRE", "string", 3)))
time.Sleep(time.Second)
// 查看 string 的过期时间,输出 -> 2
handleResult(redis.Int(conn.Do("TTL", "string")))
// 移除 string 的过期时间,输出 -> 1
handleResult(redis.Int(conn.Do("PERSIST", "string")))
// 查看 string 的过期时间,输出 -> -1,表示不过期
handleResult(redis.Int(conn.Do("TTL", "string"))) // 设置 string 在当前时间 2500 毫秒后过期,输出 -> 1
handleResult(redis.Int(conn.Do("PEXPIREAT", "string", time.Now().UnixNano() / 1e6 + 2500)))
time.Sleep(time.Second)
// 查看 string 的过期时间,输出 -> 1499,表示还有 1499 毫秒过期
handleResult(redis.Int(conn.Do("PTTL", "string")))
time.Sleep(2 * time.Second)
// 查看 string 的过期时间,输出 -> -2,表示已过期
handleResult(redis.Int(conn.Do("PTTL", "string")))
}
练习题:使用 EXPIRE 命令代替时间戳有序集合 P59

简单实践 - Web应用中使用了一个根据时间戳排序、用于清除会话信息的有序集合,通过这个有序集合,程序可以在清理会话的时候,对用户浏览过的商品以及用户购物车里面的商品进行分析。但是,如果我们决定不对商品进行分析的话,那么就可以使用 Redis 提供的过期时间操作来自动清理过期的会话信息,而无须使用清理函数。那么,你能否修改简单实践 - Web应用中定义的 UpdateToken 函数和 UpdateCartItem 函数,让它们使用过期时间操作来删除会话信息,从而代替目前使用有序集合来记录并清除会话信息的做法呢?

  • UpdateToken 函数:令牌于 userId 的对应关系不在存储于哈希表中,而是以前缀的形式将容器中的单个元素变为字符串(上面提到过),并设置过期时间,并移除最近操作时间有序集合,这样令牌到期后就会自动删除,不需要清理函数了。
  • UpdateCartItem 函数:由于当时此处把 Redis 当作数据库使用,认为购物车不应该随登录态的失效而消失,所以购物车与 userId 挂钩,不存在上述问题。但是如果要让购物车也自动过期,就需要在 UpdateToken 函数内同时设置购物车的过期时间即可。

本文首发于公众号:满赋诸机(点击查看原文) 开源在 GitHub :reading-notes/redis-in-action

Redis 实战 —— 05. Redis 其他命令简介的更多相关文章

  1. Redis实战之Redis + Jedis

    用Memcached,对于缓存对象大小有要求,单个对象不得大于1MB,且不支持复杂的数据类型,譬如SET 等.基于这些限制,有必要考虑Redis! 相关链接: Redis实战 Redis实战之Redi ...

  2. Redis实战之Redis + Jedis[转]

    http://blog.csdn.net/it_man/article/details/9730605 2013-08-03 11:01 1786人阅读 评论(0) 收藏 举报   目录(?)[-] ...

  3. 分布式缓存技术redis学习系列(五)——redis实战(redis与spring整合,分布式锁实现)

    本文是redis学习系列的第五篇,点击下面链接可回看系列文章 <redis简介以及linux上的安装> <详细讲解redis数据结构(内存模型)以及常用命令> <redi ...

  4. 分布式缓存技术redis系列(五)——redis实战(redis与spring整合,分布式锁实现)

    本文是redis学习系列的第五篇,点击下面链接可回看系列文章 <redis简介以及linux上的安装> <详细讲解redis数据结构(内存模型)以及常用命令> <redi ...

  5. Redis实战总结-Redis的高可用性

    在之前的博客<Redis实战总结-配置.持久化.复制>给出了一种Redis主从复制机制,简单地实现了Redis高可用.然后,如果Master服务器宕机,会导致整个Redis瘫痪,这种方式的 ...

  6. Redis实战之Redis命令

    阅读目录 1. 字符串命令 2. 列表命令 3. 集合命令 4. 散列命令 5. 有序集合命令 6. 发布与订阅命令 7. 小试牛刀 Redis可以存储键与5种不同数据结构类型之间的映射,这5种数据结 ...

  7. Linux+Redis实战教程_day01_常用命令【重点】

    3.常用命令[重点] Linux命令中参数,一般都是无序的.特殊情况下除外 3.1.磁盘管理命令 ls命令:列出目录内容 参数: -a 查询所有文件和文件夹.包含隐藏的 -l 查询详细列表    ls ...

  8. Redis实战(一)Redis简介及环境安装(Windows)

    提到Redis,大家肯定都听过,并且应该都在项目中或多或少的使用过,也许你觉得Redis用起来挺简单的呀,但如果有人问你下面的几个问题(比如同事或者面试官),你能回答的上来吗? 什么是Redis? R ...

  9. Redis实战

    大约一年多前,公司同事开始使用Redis,不清楚是配置,还是版本的问题,当时的Redis经常在使用一段时间后,连接爆满且不释放.印象中,Redis 2.4.8以下的版本由于设计上的主从库同步问题,就会 ...

  10. Redis实战之征服 Redis + Jedis + Spring (一)

    Redis + Jedis + Spring (一)—— 配置&常规操作(GET SET DEL)接着需要快速的调研下基于Spring框架下的Redis操作. 相关链接: Redis实战 Re ...

随机推荐

  1. CentOS7安装mongoDB数据库

    CentOS7安装mongoDB数据库 时间:2015-03-03 16:45来源:blog.csdn.net 作者:进击的木偶 举报 点击:8795次 mongoDB是目前发展比较好的NOSQL数据 ...

  2. Docker / CI / CD

    CI Weekly #6 | 再谈 Docker / CI / CD 实践经验   CI Weekly 围绕『 软件工程效率提升』 进行一系列技术内容分享,包括国内外持续集成.持续交付,持续部署.自动 ...

  3. 了解 hadoop

    <Hadoop基础教程>之初识Hadoop 博客分类: 读后感   Hadoop一直是我想学习的技术,正巧最近项目组要做电子商城,我就开始研究Hadoop,虽然最后鉴定Hadoop不适用我 ...

  4. C#中八皇后问题的递归解法——N皇后

    百度测试部2015年10月份的面试题之——八皇后. 八皇后问题的介绍在此.以下是用递归思想实现八皇后-N皇后. 代码如下: using System;using System.Collections. ...

  5. Qt之QHeaderView自定义排序(获取正确的QModelIndex)

    简述 前几节中分享过关于自定义排序的功能,貌似我们之前的内容已经可以很好地解决排序问题了,但是,会由此引发一些很难发现的问题...比如:获取QModelIndex索引错误. 下面,我们先来实现一个整行 ...

  6. Ubuntu12.04 + 虚拟机VMware 9 + Secure CRT + EditPlus 本地C++开发环境搭建

    1.1  软件准备 虚拟机VMware 9 Ubuntu 12.04 Secure CRT EditPlus 1.2  安装VMware 9与Ubuntu 12.04 这两个软件安装,按部就班,这里就 ...

  7. hdu1569find the safest road(floyd变形求最大安全值)

    find the safest road Time Limit: 10000/5000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Ot ...

  8. jenkins pipline 发送邮件

    推荐一个好网站https://www.w3cschool.cn/jenkins/jenkins-e7bo28ol.html 获取git 用户信息// Get checkout output value ...

  9. 洛谷.3834.[模板]可持久化线段树(主席树 静态区间第k小)

    题目链接 //离散化后范围1~cnt不要错 #include<cstdio> #include<cctype> #include<algorithm> //#def ...

  10. Robot Operating System (ROS)学习笔记3---键盘控制

    搭建环境:XMWare  Ubuntu14.04  ROS(indigo) 转载自古月居  转载连接:http://www.guyuehome.com/253 一.创建控制包 catkin_creat ...