Redis 实战 —— 05. Redis 其他命令简介
发布与订阅 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 个命令可以让用户在不被打断的情况下对多个键执行操作,它们分别是: WATCH
、 MULTI
、 EXEC
、UNWATCH
和 DISCART
。基本的 Redis 事务只用 MULTI
和 EXEC
即可,使用多个命令的事务将在以后进行介绍。 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
函数中,如果将投票用户添加到投票用户集合中后,还没来得及给文章的相关信息进行设置,那么这个用户以后不能再投票,并且文章的投票信息不对。
不是太明白究竟在竞争什么,只能针对以上问题处理。用事务只能再添加一个集合在事务中标记事务是否执行成功,处理流程大致如下:
- 先将用户与文章作为值加入到这个集合
- 再将用户加入到投票集合中
- 然后开启事务,依次发送更新信息的命令和删除那个集合中的相关信息,并执行
- 最后有一个 worker 扫描这个集合,将其中值拿出来解析出用户和文章,再查改用户是否已在集合中,如果在集合中,则重新执行 步骤3,最后删除该值
练习题:提高性能 P58
简单实践 - 文章投票 中 ListArticles
函数在获取整个页面的文章时,需要在 Redis 与客户端之间最多会进行 26 次通信往返,这种做法十分低效,你能否想个办法将 ListArticles
函数的往返次数降低为 2 次呢?
提示:使用流水线
- 获取文章列表时,先获取相应的 id 列表(最多 25 个),再循环获取每个 id 对应的文章,所以最多会进行 26 次通信往返
- 由于必须先获取 id 列表,再获取每个 id 对应的文章,所以只能将这两块分开,所以最低至少有 2 次通信往返。大致流程如下:
- 先获取 id 列表
- 使用流水线,依次将获取每个 id 的文章的命令发送至缓冲区,最后与服务端通信并执行命令(Go 中可以使用上述事务演示代码的方式进行操作 )
- 最后按照顺序解析结果
过期时间 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 其他命令简介的更多相关文章
- Redis实战之Redis + Jedis
用Memcached,对于缓存对象大小有要求,单个对象不得大于1MB,且不支持复杂的数据类型,譬如SET 等.基于这些限制,有必要考虑Redis! 相关链接: Redis实战 Redis实战之Redi ...
- Redis实战之Redis + Jedis[转]
http://blog.csdn.net/it_man/article/details/9730605 2013-08-03 11:01 1786人阅读 评论(0) 收藏 举报 目录(?)[-] ...
- Redis 实战 —— 04. Redis 数据结构常用命令简介
字符串 P39 Redis 的字符串是一个有字节组成的序列,可以存储以下 3 种类型的值:字节串(byte string).整数.浮点数. 在需要的时候, Redis 会将整数转换成浮点数.整数的取值 ...
- 分布式缓存技术redis学习系列(五)——redis实战(redis与spring整合,分布式锁实现)
本文是redis学习系列的第五篇,点击下面链接可回看系列文章 <redis简介以及linux上的安装> <详细讲解redis数据结构(内存模型)以及常用命令> <redi ...
- 分布式缓存技术redis系列(五)——redis实战(redis与spring整合,分布式锁实现)
本文是redis学习系列的第五篇,点击下面链接可回看系列文章 <redis简介以及linux上的安装> <详细讲解redis数据结构(内存模型)以及常用命令> <redi ...
- Redis实战总结-Redis的高可用性
在之前的博客<Redis实战总结-配置.持久化.复制>给出了一种Redis主从复制机制,简单地实现了Redis高可用.然后,如果Master服务器宕机,会导致整个Redis瘫痪,这种方式的 ...
- Redis 实战 —— 14. Redis 的 Lua 脚本编程
简介 Redis 从 2.6 版本开始引入使用 Lua 编程语言进行的服务器端脚本编程功能,这个功能可以让用户直接在 Redis 内部执行各种操作,从而达到简化代码并提高性能的作用. P248 在不编 ...
- .Net Redis实战——使用Redis构建Web应用
示例介绍 示例1:借助Redis实现购物车功能 示例2:Redis实现网页缓存和数据缓存 借助Redis实现购物车功能 每个用户的购物车都是一个散列,散列存储了商品ID与商品订购数量之间的映射.订购商 ...
- Redis实战之Redis命令
阅读目录 1. 字符串命令 2. 列表命令 3. 集合命令 4. 散列命令 5. 有序集合命令 6. 发布与订阅命令 7. 小试牛刀 Redis可以存储键与5种不同数据结构类型之间的映射,这5种数据结 ...
随机推荐
- Mysql联合索引的最左前缀原则以及b+tree
软件版本mysql5.7 根据官网的文档 https://dev.mysql.com/doc/refman/5.7/en/multiple-column-indexes.html 查询条件要符合最左原 ...
- kubeadm部署k8s
Kubernetes技术已经成为了原生云技术的事实标准,它是目前基础软件领域最为热门的分布式调度和管理平台.于是,Kubernetes也几乎成了时下开发工程师和运维工程师必备的技能之一. 官方文档 ...
- Python-其他-round()保留小数位时遇到的问题
最近有一个需求,原有整数计算,改成小数计算,保留一位小数. 于是按照需求,将数据结构由 int 改为 float ,计算时采用round()方法来保留小数位. 第一版代码如下: a = 0.10000 ...
- 在vue cli3.0 里面下载less,下载成功了还是提示没有 需要下载
今天正好新建了一个项目,在用less的时候就各种花样报错提示我: Failed to resolve loader: less-loaderYou may need to install it.Err ...
- Astra示例程序库正式上线啦
新上线的Astra示例程序库提供了基于多种编程语言和框架使用Astra的例子.借助这个示例程序库,你可以在短时间内建构起数据库.创建多个表.装载示例数据并部署基于Cassandra的应用程序. 什么是 ...
- A child container failed during start
先贴一下bug详情 严重: A child container failed during start java.util.concurrent.ExecutionException: org.apa ...
- Mac电脑jsp连接mysql
四个步骤教你如何用jsp代码连接mysql 第一步:下载jdbc驱动 进入mysql官网:https://dev.mysql.com/downloads/connector/ 找到Connect/J ...
- Hbase集群模式搭建
1.官网下载hbase安装包 这里不做赘述. 2.解压---直接tar -zxvf xxxx 3.配置hbase集群,要修改3个文件(首先zk集群已经安装好了) 注意:要把hadoop的hdfs-si ...
- Linux 网卡 team配置
网卡 team配置 目录 网卡 team配置 一.介绍 runner 方式: 1.roundrobin [mode 0]轮转策略 (balance-rr) 2.activebackup[mode 1] ...
- 大话MySQL锁
一.锁介绍 不同存储引擎支持的锁是不同的,比如MyISAM只有表锁,而InnoDB既支持表锁又支持行锁. 下图展示了InnoDB不同锁类型之间的关系: 图中的概念比较多不好理解,下面依次进行说明. 1 ...