Redis Pub/Sub 快速入门与源码解析

Redis 发布订阅:PubSub

Redis 实现了发布订阅模型,有三个角色:发布者,订阅者,频道。

频道用以解耦双方通信,订阅者通过订阅频道获取信息,发布者通过频道知道自己需要把信息发到哪。

频道也可以理解为消息类型,唯一确定某一类消息。

pubsub快速入门

1、基本pubsub模型

发布消息:publish

css 复制代码
PUBLISH [channel] [message]

订阅消息:subscribe

css 复制代码
SUBSCRIBE [channel1] [channel2] ...

取消订阅:unsubscribe

css 复制代码
UNSUBSCRIBE [channel1] [channel2] ...

2、模式匹配相关

客户端可订阅 glob 样式的模式,以接收发送到与给定模式匹配的通道名的所有消息。

订阅模式:psubscribe

PSUBSCRIBE news.*

取消订阅模式:punsubscribe

PUNSUBSCRIBE news.*

3、分片相关:Sharded Pub/Sub

分片相关在 Redis 7.0 引入。

全局 pubsub 存在着广播风暴问题,即在集群中的一个节点发布了一条消息,需要广播给集群的所有其它节点。

Sharded Pub/Sub 对 shard channels 做了与 key 一样的分片计算。即在 shard channels 上发布的消息必须发给拥有该 channel 对应的 slot 的节点,而订阅者只需要和该节点及其从节点建立连接,即可收到消息。

Sharded Pub/Sub 不会在集群的节点间进行无效的广播,从而提高性能。

发布:spublish

css 复制代码
PUBLISH [channel] [message]

订阅:ssubscribe

css 复制代码
SSUBSCRIBE [channel]

取消订阅:sunsubscribe

css 复制代码
PUNSUBSCRIBE [channel]

一条消息,多次匹配

一条消息,可能既与满足模式匹配,也满足基本模型的匹配,就会收到两条消息。

举个例子,如果订阅了:

c 复制代码
SUBSCRIBE foo
PSUBSCRIBE f*

然后收到一个来自 foo 频道的消息,会同时收到两条消息。

一个 message ,一个 pmessage

消息传递模型:at-most-once

pubsub的消息至多传递一次,容易丢失,不可靠。

pubsub实现原理

维护频道:哈希表

Redis 用dict 来维护所有的频道,key:频道的名称,value:频道的订阅者

c 复制代码
    // 三种 dict 分别对应三种 pubsub
    dict *pubsub_channels; 
    dict *pubsub_patterns;  
    dict *pubsubshard_channels;  
    int notify_keyspace_events; 
/* Events to propagate via Pub/Sub. This is an xor of NOTIFY_... flags. */
                                   

Redis启动时,在initServer中,会对它们做初始化,如下:

c 复制代码
    server.pubsub_channels = dictCreate(&keylistDictType);
    server.pubsub_patterns = dictCreate(&keylistDictType);
    server.pubsubshard_channels = dictCreate(&keylistDictType);

频道的来源:subscribe

subscribe 对应的实现函数是 subscribeCommand

除了订阅频道的作用,如果要订阅的频道不存在,会创建频道(publish 不会创建频道)

c 复制代码
int pubsubSubscribeChannel(client *c, robj *channel, pubsubtype type) {
    dictEntry *de;
    list *clients = NULL;
    int retval = 0;
    // 客户端维护订阅的频道
    if (dictAdd(type.clientPubSubChannels(c),channel,NULL) == DICT_OK) {
        retval = 1;
        incrRefCount(channel);
        de = dictFind(*type.serverPubSubChannels, channel);
        // Redis Server 端不存在该频道 创建
        if (de == NULL) {
            clients = listCreate();
            dictAdd(*type.serverPubSubChannels, channel, clients);
            incrRefCount(channel);
        } else {
            clients = dictGetVal(de);
        }
        // 将该 client 加入订阅列表
        listAddNodeTail(clients,c);
    }
    // 返回该客户端订阅的频道数量
    addReplyPubsubSubscribed(c,channel,type);
    return retval;
}

publish:发布消息

publish对应的实现函数是 publishCommand

c 复制代码
// 查找频道是否存在
de = dictFind(server.pubsub_channels,channel);
    if (de) { //频道存在
        // ...
        //遍历频道对应的订阅者
        //向每个订阅者发送要发布的消息
        while ((ln = listNext(&li)) != NULL) {
            client *c = ln->value;
            addReplyPubsubMessage(c,channel,message,*type.messageBulk);
            updateClientMemUsageAndBucket(c);
            // ...
        }
    }
// ... shared pubsub
// ... 查找模式匹配的频道

pubsub应用场景

哨兵间通信

哨兵的通信机制是基于 pubsub 实现。例如:"sentinel:hello"频道,就是哨兵发现彼此存在的频道。

监控

基于 pubsub ,我们可以监控服务端的特定行为。

pubsub的潜在问题

不可靠性

pubsub的消息传递模型是至多一次,不会对消息做持久化,因此出现连接断开时,消息会丢失

如果需要保证可靠,请考虑 Redis Stream。

订阅客户端的限制

在 RESP2 中,订阅频道的客户端除了订阅等命令不能发出其它命令,只能通过 Ctrl-C 退出。

不过 Redis6.0 推出的 RESP3 已经解决了这个问题。你可以通过HELLO 3 切换到 RESP3 协议。

相关推荐
程序猿麦小七29 分钟前
基于springboot的景区网页设计与实现
java·spring boot·后端·旅游·景区
蓝田~37 分钟前
SpringBoot-自定义注解,拦截器
java·spring boot·后端
theLuckyLong38 分钟前
SpringBoot后端解决跨域问题
spring boot·后端·python
.生产的驴39 分钟前
SpringCloud Gateway网关路由配置 接口统一 登录验证 权限校验 路由属性
java·spring boot·后端·spring·spring cloud·gateway·rabbitmq
小扳43 分钟前
Docker 篇-Docker 详细安装、了解和使用 Docker 核心功能(数据卷、自定义镜像 Dockerfile、网络)
运维·spring boot·后端·mysql·spring cloud·docker·容器
v'sir1 小时前
POI word转pdf乱码问题处理
java·spring boot·后端·pdf·word
李少兄1 小时前
解决Spring Boot整合Redis时的连接问题
spring boot·redis·后端
日里安1 小时前
8. 基于 Redis 实现限流
数据库·redis·缓存
码上一元6 小时前
SpringBoot自动装配原理解析
java·spring boot·后端
枫叶_v8 小时前
【SpringBoot】22 Txt、Csv文件的读取和写入
java·spring boot·后端