Redis第二十九讲 Redis集群发布订阅模式以及Redis集群事务

本文详细介绍了Redis集群环境下的发布订阅模式,包括频道和模式的订阅与取消订阅,以及其底层原理。同时,文章阐述了Redis集群不支持的事务特性,解释了单节点事务的命令如Watch、Multi、EXEC和Discard,以及Redis事务的执行特点和潜在风险。

Redis集群状态下的发布订阅

在Redis的几个基本数据结构介绍中,有讲过List数据结构,可以使用List的阻塞特性实现订阅消费,关于Redis的底层数据结构可以参考我的这篇博客:Redis第六讲 Redis之List底层数据结构实现
底层数据结构基本操作可以看我的这篇博客,Redis第十五讲 Redis常用数据结构的基本操作

Redis发布订阅机制,在这种机制下,消息发布者向指定频道(channel)发布消息,消息订阅者可以收到指定频道的消息,同一个频道可以有多个消息订阅者,如下图:
在这里插入图片描述

Redis发布订阅/生产者消费者

在Redis中,发布订阅相关命令有:

- 发布消息
- 订阅频道
- 取消订阅
- 按照模式订阅
- 按照模式取消订阅
- 查询订阅信息
  • 发布消息的命令是publish,语法是:
publish 频道名称 消息


192.168.36.128:6382> publish channel:shanghai "tody is sunny"
(integer) 

返回的结果是订阅者的个数,上例中没有订阅者,所以返回结果为0。

  • 订阅消息的命令是subscribe,订阅者可以订阅一个或者多个频道,语法是:

192.168.36.128:6382>  subscribe channel:shanghai
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "channel:shanghai"
3) (integer) 1

返回结果中有3条,分别表示:返回值的类型(订阅成功)、订阅的频道名称、目前已订阅的频道数量。当订阅者接受到消息时,就会显示:

1) "message"
2) "channel:shanghai"
3) "tody is sunny"

同样也是3条结果,分别表示:返回值的类型(信息)、消息来源的频道名称、消息内容。
新开启的订阅者,是无法收到该频道之前的历史消息的,因为Redis没有对发布的消息做持久化。

  • 取消订阅的命令是unsubscribe,可以取消一个或者多个频道的订阅,语法是:
unsubscribe [频道名称 [频道名称 ...]]
  • 按模式订阅消息的命令是psubscribe,订阅一个或多个符合给定模式的频道,语法是:
psubscribe 模式 [模式 ...]
每个模式以 * 作为匹配符,比如 channel* 匹配所有以 channel 开头的频道,命令如下:
> psubscribe channel:*
Reading messages... (press Ctrl-C to quit)
1) "psubscribe"
2) "channel*"
3) (integer) 1

返回结果中有3条,分别表示:返回值的类型(按模式订阅成功)、订阅的模式、目前已订阅的模式数量。当订阅者接受到消息时,就会显示:

1) "pmessage"
2) "channel*"
3) "channel:one-more-study:demo"
4) "I am One More Study."
  • 按模式取消订阅的命令是punsubscribe,可以取消一个或者多个模式的订阅,语法是
punsubscribe [模式 [模式 ...]]
每个模式以 * 作为匹配符,比如 channel:* 匹配所有以 channel 开头的频道,命令如下:

查询订阅信息

  • 查看活跃频道
    活跃频道指的是至少有一个订阅者的频道,语法是:
pubsub channels [模式]
  • 查看频道订阅数
pubsub numsub [频道名称 ...]

  • 查看模式订阅数
> pubsub numpat
(integer) 1

发布订阅模式原理

频道的发布订阅理

Redis将所有频道的订阅关系都保存在服务器状态的 pubsub_channels 字典,字典的键是某个被订阅的频道,而对应值则是一个链表,链表里记录了所有订阅这个频道的客户端。

struct redisServer{    //...
    // 保存所有频道订阅关系    dict *pubsub_channels;        //...}

在这里插入图片描述

  • 建立订阅关系执行分两种情况:

1)该频道已有其他订阅者

该频道在 pubsub_channels 字典中存在订阅者链表,将此客户端添加至订阅者链表末尾即可;

2)该频道暂无订阅者

该频道在 pubsub_channels 字典中不存在订阅者链表,首先在字典中为频道创建一个键,并将这个键的值设置为空链表,然后将客户端添加到链表,成为链表的第一个元素。

当客户端退订某个或某些频道的时候,服务器将从 pubsub_channels 中解除客户端与被退订频道之间的关联。

  • 解除订阅关系执行过程:

1)根据被退订频道的名字,在 pubsub_channels 字典中找到频道对应的订阅者链表,然后从订阅者链表中删除退订客户端的信息;

2)假如删除退订客户端后,频道的订阅者链表变成了空链表,那么说明这个频道已无任何订阅者了,将从 pubsub_channels 字典中删除频道对应的键。

模式的发布订阅原理

模式与频道的区别,简单理解模式是多个频道的组合。Redis将所有模式的订阅关系都保存在服务器状态的 pubsub_patterns 链表,链表的每个节点都包含着一个 pubsub Pattern 结构,这个结构的 pattern 属性记录了被订阅的模式,而 client 属性则记录了订阅模式的客户端。

struct redisServer{
    //...        // 保存所有模式订阅关系    dict *pubsub_patterns;        //...}

在这里插入图片描述

  • 当客户端执行 PSUBSCRIBE 命令订阅某个或某些模式的时候,服务器会对每个被订阅的模式执行以下两个操作:

1)新建一个 pubsubPattern结果,将结构的 pattern 属性设置为被订阅的模式,client 属性设置为订阅模式的客户端;

2)将pubsubPattern结构添加到 pubsub_patterns 链表的尾部。

  • 当客户端退订某个或某些模式的时候,服务器将从 pubsub_patterns 链表中查找并删除那些 pattern 属性为被退订模式,并且client 属性为执行退订命令的客户端的 pubsubPattern 结构。

简单理解即:查找client、pattern 均相同的 pubsubPattern 并删除。

Redis集群事务

redis集群不支持事务,但内部单节点是支持事务的。而且Redis没有行锁的概念也没有事务回滚,但提供了事务撤销命令

  • redis事务常用命令
    Watch: 可以监听一个或者多个key,在提交事务之前是否有发生了变化 如果发生边了变化就不会提交事务,
    没有发生变化才可以提交事务 版本号 乐观锁
    Multi: 开启事务
    EXEC: 提交事务
    Discard: 取消提交事务

  • watch和exec区别
    watch 是监听key,watch实际上用的是版本号乐观锁概念,一旦有变动,版本号+1
    mult 是开启事务 执行exec的时候,如果用了watch发现版本号变了就不会提交,如果没用watch,就直接提交事务。

String key = "transaction-key";  
jedis.set(key, "20");  
jedis.watch(key);//注册key,此后key将会被监控,如果在事务执行前被修改,则导致事务被DISCARD。  
jedis.incr(key);//此key被修改,即使是自己,也会导致watch在事务中执行失效  
jedis.unwatch();//取消注册  
jedis.watch(key);//重新注册,在重新注册前,必须unwatch  
Transaction tx = jedis.multi();//开启事务,开启事务前进行watch
  • Multi开启事务的特点
    Multi开启事务的时候,不和mysql一样拥有行锁的功能,也就是没有隔离性,多个事务可以同时对一个key进行操作,且每次都会覆盖,最后提交的事务就是key的最后值。

  • 取消事务跟回滚有什么区别
    回滚对事务取消和行锁都会撤销
    Redis没有回滚 单纯取消事务(不提交事务) 不上锁,事务取消只会取消当前提交,不会影响其他提交(已经提交的),而mysql如果在同一个事务中有一个错误其他提交都需要回滚

Redis事务底层原理

Redis中,如果一个事务被提交,那么事务中的所有操作将会被顺序执行(放在一个队列里面),且在事务执行期间,其他client的操作将会被阻塞;Redis采取了这种简单而“粗鲁”的方式来确保事务的执行更加的快速和更少的外部干扰因素。

EXEC指令将会触发事务中所有的操作被写入AOF文件(如果开启了AOF),然后开始在内存中实施这些数据变更操作;Redis将会尽力确保事务中所有的操作都能够执行,如果redis环境故障,有可能导致事务未能成功执行,那么需要在redis重启后增加额外的校验工作。

如果在EXEC指令被提交之前,Redis-server即检测到提交的某个指令存在语法错误,那么此事务将会被提前标记DISCARD,此后事务提交也将直接被驳回;但是如果在EXEC提交后,在实施数据变更时(Redis将不会预检测数据类型,比如你对一个“非数字”类型的key执行INCR操作),某个操作导致了ERROR,那么redis仍然不会回滚此前已经执行成功的操作,而且也不会中断ERROR之后的其他操作继续执行。对于开发者而言,你务必关注事务执行后返回的结果(结果将是一个集合,按照操作提交的顺序排列,对于执行失败的操作,结果将是一个ERROR)。

Redis的事务之所以如此设计,它为了确保本身的性能,同时不引入“关系型数据库”的设计复杂度;你不能完全希望Redis能为你交付完美的事务操作,只能说,你选择了错误的工具。

 public void transaction(){  
        String key = "transaction-key";  
        jedis.set(key, "20");  
        jedis.watch(key);  
        Transaction tx = jedis.multi();  
        tx.incr(key);  
        tx.incr(key);  
        tx.incr(key);  
        List<Object> result = tx.exec();  
        if(result == null || result.isEmpty()){  
            System.out.println("Transaction error...");//可能是watch-key被外部修改,或者是数据操作被驳回  
            return;  
        }  
        for(Object rt : result){  
            System.out.println(rt.toString());  
        }  
    }  

Redis 在接收到 MULTI 命令后便会开启一个事务,这之后的所有读写命令都会保存在队列中但并不执行,直到接收到 EXEC 命令后,Redis 会把队列中的所有命令连续顺序执行,并以数组形式返回每个命令的返回结果

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

程序员路同学

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值