消息模型
消息隊列的演進
消息隊列模型
早先的消息隊列是按照"隊列"的數據結構來設計的。
生產者(Producer)產生消息,進行入隊操作,消費者(Consumer)接收消息,就是出隊操作,存在於服務端的消息容器就稱為消息隊列。

當然消費者也可能不止一個,存在的多個消費者是競爭的關系,消息被其中的一個消費者消費了,其它的消費者就拿不到消息了。
發布訂閱模型
如果一個人消息想要同時被多個消費者消費,那么上面的隊列模式就不適用了,於是又引出了一種新的模式,發布訂閱模型。

在發布-訂閱模型中,消息的發送方稱為發布者(Publisher),消息的接收方稱為訂閱者(Subscriber),服務端存放消息的容器稱為主題(Topic)。
發布者發送消息到主題中,然后訂閱者需要先訂閱主題。訂閱主題的訂閱者之后就可以收到發送者發送的消息了。
發布訂閱也是兼容消息隊列模型的,如果只有一個訂閱者,就是消息隊列模型了。
RabbitMQ的消息模型
RabbitMQ 使用的還是消息隊列這種消息模型,不過它引入了一個 exchange 的概念。
exchange 也就是交換器,位於生產者和隊列之間,生產者產生的數據是直接發送到 exchange 中,然后 exchange 根據配置的策略將消息發送到對應的隊列中。

RabbitMQ 中通過綁定將交換器和隊列關聯起來,綁定的時候一般會指定一個綁定鍵(BindingKey)。
生產者發送消息的時候會指定一個 RoutingKey ,當 RoutingKey 和 BindingKey,一樣的時候就會被發送的對應的隊列中去。
交換器的類型
RabbitMQ 中腸常用的交換器有 fanout、direct、topic、headers
四種,這里來一一分析下
direct
direct 會根據發送消息的 RoutingKey ,然后發送到和 RoutingKey 匹配的 BindingKey 對應的隊列中去。

如果發送消息的路由鍵也就是 RoutingKey,為 log 的時候,兩個消息隊列都會收到消息,如果路由鍵為 debug ,exchange 只會把消息發送到消息隊列1中。
Default Exchange
Default Exchange
是一種特殊的 Direct Exchange
。
如果不指定 Exchange ,當你手動創建一個隊列時,后台會自動將這個隊列綁定到一個名稱為空的 Direct Exchange
上,綁定 RoutingKey 與隊列名稱相同。通過使用這個默認的交換器,可以省略掉 RoutingKey 的綁定,直接使用隊列即可,在某些場景可以簡化我們的代碼。
topic
direct 中的 RoutingKey 和 BindingKey 是完全匹配才能發送消息,topic 中在此基礎之上做了擴展,也就是引入了模糊匹配機制。
-
RoutingKey 和 BindingKey 中使用 . ,來分割字符串,被 . 分割開的每一段字符串就是一個匹配字符;
-
BindingKey 中主要通過 * 和 # ,用於模糊匹配,* 表示一個單詞,# 代表任意0個或多個單詞;
-
BindingKey 中單獨使用 # 時,會接收所有的消息,這與類型 fanout一致;

栗子:
1、路由鍵為 test.rabbitmq
消息隊列1和消息隊列2都會收到消息;
2、路由鍵為 rabbitmq
沒有隊列能收到消息;
3、路由鍵為 test
消息隊列2會收到消息;
4、路由鍵為 rr.info.ww
消息隊列2會收到消息;
5、路由鍵為 info
沒有隊列能收到消息;
fanout
該交換器收到的信息會被發送到所有與改交換器綁定的隊列中。
headers
headers 類型的交換器不依賴於路由鍵的匹配規則來路由消息,而是根據發送的消息內容中 headers 屬性進行匹配。在綁定隊列和交換器時制定一組鍵值對當發送消息到交換器時,RabbitMQ 會獲取到該消息的 headers (也是一個鍵值對的形式) ,對比其中的鍵值對是否完全匹配隊列和交換器綁定時指定的鍵值對,如果完全匹配則消息會路由到該隊列,否則不會路由到該隊列 headers 類型的交換器性能會很差,而且也不實用,基本上不會看到它的存在。
Kafka的消息模型

Kafaka 中引入了一個 broker。broker 接收生產者的信息,為消息設置偏移量,並且保存的磁盤中。broker 為消費者提供服務,對讀取分區的請求作出響應,返回已經提交到磁盤上的消息。
同時 broker 也會對生產者和消費者進行消息的確認。
生產者發送消息到 broker,如果沒有收到 broker 的確認就可以選擇繼續發送;
消費者同理,在消費端,消費者在收到消息並完成自己的消費業務邏輯(比如,將數據保存到數據庫中)后,也會給服務端發送消費成功的確認,broker 只有收到消費確認后,才認為一條消息被成功消費,否則它會給消費者重新發送這條消息,直到收到對應的消費成功確認。
如果一個主題中,每次只有一個消費實例在處理,同時我們也要保持消息的有序性,當前消息沒有被消費掉就不能接着消費下一個消息。那么,消費的性能將是極低的,這時候引入了一個分區的概念。
主題可以被分為若干個分區,一個分區就是一個提交日志。消息以追加的方式寫入分區,然后以先入先出的順序讀取。要注意,由於一個主題一般包含幾個分區,因此無法在整個主題范圍內保證消息的順序,但可以保證消息在單個分區內的順序。
同時引入了消費者組,消費者是消費者組中的一部分,這樣會有一個或者多個消費者讀一個分區,不過群組會保證一個分區只能被一個消費者消費,通過多消費者,這樣消費的性能就提高了。
每個消費組都消費主題中一份完整的消息,不同消費組之間消費進度彼此不受影響,也就是說,一條消息被Consumer Group1
消費過,也會再給Consumer Group2
消費。不過同組內是競爭關系,同組內一個消息只能被同組內的一個消息消費。
消費者通過偏移量來確認讀過的數據,他是個不斷累加的數據,每次成功消費一個數據這個偏移量就加一。在給定的分區中,每個消息的偏移量都是唯一的。消費者會把每個分區讀取的消息偏移量保存在 Zookeeper 或 Kafka 上,如果消費者關閉或重啟,它的讀取狀態不會丟失。
RocketMQ的消息模型

RocketMQ 中的消息模型和 Kafaka 類似,把 Kafaka 中的分區換成隊列,就是 RocketMQ 的消息模型了。
不過雖然消息模型類似,但是實現方式還是有很大的差別的。
參考
【消息隊列高手課】https://time.geekbang.org/column/intro/100032301
【消息隊列設計精要】https://tech.meituan.com/2016/07/01/mq-design.html
【RabbitMQ實戰指南】https://book.douban.com/subject/27591386/
【Kafka權威指南】https://book.douban.com/subject/27665114/
【RabbitMQ,RocketMQ,Kafka 消息模型對比分析】https://boilingfrog.github.io/2021/12/18/幾種消息隊列的消息模型/