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 协议。

相关推荐
GISer_Jing31 分钟前
前端面试通关:Cesium+Three+React优化+TypeScript实战+ECharts性能方案
前端·react.js·面试
uzong5 小时前
面试官:Redis中的 16 库同时发送命令,服务端是串行执行还是并行执行
后端·面试·架构
关键帧-Keyframe6 小时前
音视频面试题集锦第 26 期
面试·音视频
追逐时光者6 小时前
.NET 使用 MethodTimer 进行运行耗时统计提升代码的整洁性与可维护性!
后端·.net
ssshooter7 小时前
VSCode 自带的 TS 版本可能跟项目TS 版本不一样
前端·面试·typescript
你的人类朋友7 小时前
【Node.js】什么是Node.js
javascript·后端·node.js
David爱编程8 小时前
面试必问!线程生命周期与状态转换详解
java·后端
倔强青铜三9 小时前
苦练Python第39天:海象操作符 := 的入门、实战与避坑指南
人工智能·python·面试
LKAI.9 小时前
传统方式部署(RuoYi-Cloud)微服务
java·linux·前端·后端·微服务·node.js·ruoyi
Victor3569 小时前
Redis(11)如何通过命令行操作Redis?
后端