关于java:Redis-发布订阅模式原理拆解并实现一个消息队列

4次阅读

共计 6679 个字符,预计需要花费 17 分钟才能阅读完成。

“65 哥,如果你交了个丑陋小姐姐做女朋友,你会通过什么形式将这个音讯广而告之给你的微信好友?“

“那不得拍点女朋友的美照 + 密切照弄一个九宫格图文音讯在朋友圈公布大肆宣传,暴击独身狗。”

像这种 65 哥通过朋友圈公布音讯,关注 65 哥的好友能收到告诉的场景叫做「公布 / 订阅机制」。

明天不聊小姐姐,深刻理解下「Redis 公布 / 订阅机制」。的原理与实战使用。

Redis 通过 SUBSCRIBEUNSUBSCRIBEPUBLISH 实现公布订阅消息传递模式,Redis 提供了两种模式实现,别离是「公布 / 订阅到频道」和「公布 \ 订阅到模式」。

Redis 公布订阅简介

Redis 公布订阅(Pus/Sub)是一种音讯通信模式:发送者通过 PUBLISH公布音讯,订阅者通过 SUBSCRIBE 订阅接管音讯或通过UNSUBSCRIBE 勾销订阅。

次要蕴含三个局部组成:「发布者」、「订阅者」、「Channel」。

发布者和订阅者属于客户端,Channel 是 Redis 服务端,发布者将音讯公布到频道,订阅这个频道的订阅者则收到音讯。

如下图所示,三个「订阅者」订阅「ChannelA」频道:

这时候,小组长往「ChannelA」公布音讯,这个音讯的订阅者就会收到音讯「关注码哥字节,晋升技术」:

Pub/Sub 实战

废话不多说,晓得基本概念当前,学习一个技术第一步先把它跑起来,接着才是摸索原理,从而达到「知其然,知其所以然」的境界。

一共有两种模式实现「公布 \ 订阅」:

  • 应用频道(Channel)的公布订阅;
  • 应用模式(Pattern)的公布订阅。

须要留神的是,公布订阅机制与 db 空间无关,比方在 db 10 公布,db0 的订阅者也会收到音讯。

通过频道(Channel)实现

三步走:

  1. 订阅者订阅频道;
  2. 发布者向「频道」公布音讯;
  3. 所有订阅「频道」的订阅者收到音讯。

订阅者订阅频道

应用 SUBSCRIBE channel [channel ...]订阅一个或者多个频道,O(n) 工夫复杂度,n = 订阅的 Channel 数量。

SUBSCRIBE develop
Reading messages... (press Ctrl-C to quit)
1) "subscribe" // 音讯类型
2) "develop" // 频道
3) (integer) 1 // 音讯内容

执行该指令后,客户端进入订阅状态,订阅者只能应用 subscribeunsubscribepsubscribepunsubscribe这四个属于 ” 公布 / 订阅 ” 的指令。

客户端「肖菜鸡」订阅了「develop」频道承受组长的音讯,音讯响应体别离示意:

  • 音讯类型:subscribe、message、unsubscribe
  • 频道
  • 音讯内容:随着音讯类型不同代表不同含意。

进入订阅后的客户端能够收到 3 种类型的音讯回复:

  1. subscribe:订阅胜利的反馈音讯,第二个值是订阅胜利的频道名称,第三个是以后客户端订阅的频道数量。
  2. message:客户端接管到音讯,第二个值示意产生音讯的频道名称,第三个值是音讯的内容。
  3. unsubscribe:示意胜利勾销订阅某个频道。第二个值是对应的频道名称,第三个值是以后客户端订阅的频道数量,当此值为 0 时客户端会退出订阅状态,之后就能够执行其余非 ” 公布 / 订阅 ” 模式的命令了。

发布者公布音讯

小组长应用 PUBLISH channel message 向指定「develop」频道公布音讯。

PUBLISH develop 'do job'
(integer) 1

须要留神的是,公布的 音讯并不会长久化,音讯公布之后还有新「开发」靓仔订阅的话,只能接管后续公布到该频道的音讯。

好一个「不问过往,只争当下」。

订阅者承受音讯

关注了「develop」频道的订阅者将会收到「do job」音讯。

// 订阅 develop 频道
SUBSCRIBE develop
Reading messages... (press Ctrl-C to quit)
1) "subscribe" // 订阅频道胜利
2) "develop" // 频道
3) (integer) 1
// 当发布者公布音讯,订阅者读取到的音讯如下
1) "message" // 承受到音讯
2) "develop" // 频道名称
3) "do job" // 音讯内容

退订频道

订阅的反向操作,「65 哥」天天在朋友圈秀恩爱,受不了了,勾销订阅他的朋友圈。

应用 UNSUBSCRIBE 命令能够退订指定的「模式」不会影响通过 `subscribe 命令订阅的频道。

同样 unsubscribe命令也不会影响通过 psubscribe 命令订阅的规定。

通过模式(Pattern)实现

接下来看另一种形式实现公布订阅,如下图示意当「匹配模式」与这个频道匹配的话,当音讯向频道公布音讯,该音讯还会公布到与这个频道匹配的「模式」上,订阅这个模式的客户端也会收到音讯。

smile.girl.* 模式示意「你微笑时好美」pattern,与这个模式匹配的两个频道是 smile.girls.Tinasmile.girls.maggi,别离示意喜爱「微笑的 Tina」和喜爱「微笑的 maggi」的粉丝。

如下图:

当初 Tina 公布动静将音讯发送到 smile.girls.Tina频道的时候,除了订阅了 smile.girls.Tina 这个频道的粉丝收到音讯以外,这 个音讯还会发送给订阅 smile.girl.* 模式的粉丝(因为频道与模式匹配)。

这些粉丝比拟贪婪,所有「微笑时好美的 girls」都关注了,LSP~~,码哥可不是这样的人。

应用匹配模式,用 PUBLISH 将音讯公布到订阅 smile.girls.Tina 客户端之外,还会将该「频道」与「pub/sub pattern」中的模式进行比照,如果 Channel 与某个模式匹配的话,也将这个音讯公布到订阅这个模式的客户端。

订阅模式

订阅模式的指令是PSUBSCRIBE,如下示意 LSP 订阅「smile.girl.*」模式:

PSUBSCRIBE smile.girls.*
Reading messages... (press Ctrl-C to quit)
1) "psubscribe" // 音讯类型
2) "smile.girls.*"// 模式
3) (integer) 1 // 订阅数

对应的反向勾销模式订阅的指令是PUNSUBSCRIBE smile.girl.*

订阅「smile.girls.Tina」频道

SUBSCRIBE smile.girls.Tina
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "smile.girls.Tina"
3) (integer) 1

订阅「smile.girls.maggi」频道

SUBSCRIBE smile.girls.maggi
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "smile.girls.maggi"
3) (integer) 1

Tina 公布音讯,关注「smile.girls.Tina」的粉丝和订阅了与该频道匹配的「smile.girls.*」模式的粉丝收到音讯。

关注「smile.girls.*」模式的粉丝收到音讯

PSUBSCRIBE smile.girls.*
Reading messages... (press Ctrl-C to quit)
1) "psubscribe"
2) "smile.girls.*"
3) (integer) 1
// 进入订阅状态,接管到音讯
1) "pmessage" 音讯类型
2) "smile.girls.*"
3) "smile.girls.Tina"
4) "love u" // 音讯内容

关注「smile.girls.Tina」的粉丝收到音讯

127.0.0.1:6379> SUBSCRIBE smile.girls.Tina
Reading messages... (press Ctrl-C to quit)
// 订阅胜利
1) "subscribe"
2) "smile.girls.Tina"
3) (integer) 1
// 接管音讯
1) "message"
2) "smile.girls.Tina"
3) "love u"

须要留神的是,如果一个客户端订阅了与模式匹配的模式和频道,那么客户端会收到屡次音讯。

比方,65 哥 订阅了「smile.girls.Tina」频道和「smile.girls.*」模式,那么当 Tina 公布动静到频道的时候,65 哥会收到两条票音讯,一条音讯类型是message,一条类型是pmessage

Redisson 与 SpringBoot 实战

官网文档:https://github.com/redisson/r…

生产者代码

/**
 * 公布音讯到 Topic
 * @param message 音讯
 * @return 接管音讯的客户端数量
 */
public long sendMessage(String message) {RTopic topic = redissonClient.getTopic(CHANNEL);
    long publish = topic.publish(message);
    log.info("生产者发送音讯胜利,msg = {}", message);
    return publish;
}

消费者代码

public void onMessage() {
  // in other thread or JVM
  RTopic topic = redissonClient.getTopic(CHANNEL);
  topic.addListener(String.class, (channel, msg) -> {log.info("channel: {} 收到音讯 {}.",  channel, msg);
  });
}

须要留神的是,公布音讯与监听音讯要运行在不同的 JVM,如果应用同一个 redissonClient 公布的话,不会监听到本人的音讯。

原理剖析

咱们通过上文晓得了公布订阅的概念,一共两种模式实现公布订阅。并且使用原生指令和 Redisson 进行实战。

接下来,咱们要深刻了解 Redis 如何实现公布订阅机制,做到知其然知其所以然。

频道 (Channel) 的公布 / 订阅如何实现的?

65 哥,如果是你会应用什么数据结构来实现基于频道来定位对应客户端?

码哥,我感觉能够字典来实现,字典的 key 对应被订阅的频道,而字典的值能够应用一个链表,链表外面保留着订阅这个频道的所有客户端。

数据结构

聪慧,Redis 应用 redis.h中有一个 redisServer 构造体保护每个服务器过程示意服务器状态,pubsub_channels 属性是一个字典,用于保留订阅频道的信息。

struct redisServer {
  ...
  /* Pubsub */
   dict *pubsub_channels;
  ...
}

如下图所示,「码哥」、「靓仔」订阅了「redis-channel」,「宅男」「LSP」订阅了「枝~ 藤¥由 * 香 - 里」:

发送音讯到频道

生产者调用 PUBLISH channel messsage 发送音讯,程序先依据 channel 从 pubsub_channels 定位到字典的 key 所在的桶,接着把音讯发送给这个 key 对应的 value 链表的所有客户端。

退订频道

UNSUBSCRIBE命令能够退订指定的频道:丢与字典操作来说,依据 key 找到关注链表,遍历链表,删除这个客户端,这样音讯就不会发送给这个客户端了。

模式 (Pattern) 的公布 / 订阅如何实现的?

接下来,咱们持续看基于模式实现的公布订阅原理……

当应用 PUBLISH公布音讯到某个频道的时候,不仅订阅这个频道的所有客户端会收到音讯,与这个模式匹配的客户端也会收到音讯。

源码在 server.h 文件中的redisServer.pubsub_patterns 属性定义。

struct redisServer {
  ...
  /* A dict of pubsub_patterns */
    dict *pubsub_patterns;
  ...
}

也是 dict 字典类型,key 对应「pattern」模式,value 是一个 链表类型的构造:list *clients外面蕴含匹配个模式的客户端列表。

当执行 PSUBSCRIBE smile.girls.*命令的时候,会执行 pubsubSubscribePattern 办法。

在这里我分享下如何定位要害源码,公布订阅咱们依据教训搜寻 pubsub 便能检索到 pubsub.c

码哥应用 CLion 调试的 Redis 源码,跟咱们 Java 开发用的 IDEA 出自于一家,所以快捷键都是一样的,接着应用 Command + F12 弹出办法搜寻,找到 pubsubSubscribePattern 订阅模式的办法。

办法参数别分示意关注该模式的客户端 client c,和客户端想要关注的 pattern,办法次要逻辑如下:

  1. listSearchKey(c->pubsub_patterns,pattern):依据 pattern 从 redisServer.pubsub_patterns 字典查找是否曾经存在该模式的 key,存在则调用addReplyPubsubPatSubscribed 告诉客户端曾经订阅过了,否则继续执行以下逻辑。
  2. dictFind(server.pubsub_patterns,pattern):依据模式 pattern从字典 server.pubsub_patterns找到 dictEntry 哈希桶,为空就调用 listCreate()创立客户端链表 list *clients,并放到字典中,key = pattern,value = list *clients 链表。
  3. 哈希桶不为空,那么把以后客户端 client *c 增加到 list *clients链表尾节点。

所以模式实现的公布订阅也是通过字典来保留模式与客户端的关系,如下图所示:

当应用 PUBLISH 公布音讯的时候,除了公布到订阅 channel 的客户端以外,还会将该 channel 与 pubsub_patterns 字典中查找匹配模式 key 对应的 value 中的客户端链表,并执行音讯发送。

退订模式

应用 PUNSUBSCRIBE命令能够退订指定的模式,这个命令执行的是订阅模式的反操作:依据模式从 pubsub_patterns字典中找到客户端链表,遍历链表将以后客户端删除。

总结

Redis 公布订阅性能,次要通过如下命令实现:

  • subscribe channel [channel ...]:订阅一个或者多个频道;
  • unsubscribe channel 退订指定频道;
  • publish channel message 向指定频道发送音讯;
  • psubscribe pattern 订阅指定模式;
  • punsubscribe pattern 退订指定模式。

Pub/Sub 与数据库无关,比方在 DB0 上公布,DB1的订阅者也将接管到。

基于频道实现的公布订阅信息是由服务器过程的 redisServer.pubsub_channels 字典保留,key = 被订阅的频道,value 是订阅频道的所有客户端链表。

当音讯公布到频道的时候,遍历字典获取所有客户端并把音讯发送到频道的客户端。

基于模式实现的公布订阅的信息保留在字典 pubsub_patterns中,key = pattern,value 是客户端链表。

当音讯公布到频道的时候,除了订阅该频道的客户端收到音讯以外,所有订阅了与频道匹配的模式的客户端也会收到音讯。

应用场景

说了这么多,Redis 公布订阅能在什么场景发挥作用呢?

哨兵间通信

哨兵集群中,每个哨兵节点利用 Pub/Sub 公布订阅实现哨兵之间的互相发现彼此和找到 Slave,详情点击 ->《哨兵集群原理那些事》。

哨兵与 Master 建设通信后,利用 master 提供公布 / 订阅机制在 __sentinel__:hello 公布本人的信息,比方身高体重、是否独身、IP、端口……,同时订阅这个频道来获取其余哨兵的信息,就这样实现哨兵间通信。

音讯队列

之前「码哥」跟大家分享过如何利用 Redis List 与 Stream 实现音讯队列。

咱们也能够利用 Redis 公布订阅实现 轻量级简略的 MQ 性能,实现上下游解耦,须要留神点是 Redis 公布订阅的音讯不会被长久化,所以新订阅的客户端将收不到历史音讯。

也不反对 ACK 机制,所以以后业务不能容忍这些毛病,那就应用业余的音讯队列,如果能容忍那就能享受 Redis 快带来的劣势。

最初,能够在评论区叫我一声「靓仔」么?为了写这个文章,码哥看了好多微笑时好美的 girl 才写进去,原创不易。

敌人们点赞、分享、珍藏反对我吧。

参考资料

1.Redis 设计与实现

2.https://redisbook.readthedocs…

正文完
 0