發布/訂閱(Publish/subscribe 或pub/sub)是一種消息范式,消息的發送者(發布者)不是計划發送其消息給特定的接收者(訂閱者)。而是發布的消息分為不同的類別,而不需要知道什么樣的訂閱者訂閱。訂閱者對一個或多個類別表達興趣,於是只接收感興趣的消息,而不需要知道什么樣的發布者發布的消息。這種發布者和訂閱者的解耦可以允許更好的可擴展性和更為動態的網絡拓撲.
發布/訂閱是消息隊列范式的兄弟,通常是更大的消息導向的中間件的系統的一部分。大多數消息系統在應用程序接口(API)中同時支持消息隊列模型和發布/訂閱模型,例如Java消息服務(JMS)。
現實中,並不是所有請求都期待答復,而不期待答復,自然就沒有了狀態。廣播聽過吧?收音機用過吧?就這個意思。
發布/訂閱模式定義了一種一對多的依賴關系,讓多個訂閱者對象同時監聽某一個主題對象。這個主題對象在自身狀態變化時,會通知所有訂閱者對象,使它們能夠自動更新自己的狀態。
消息過濾
在發布/訂閱模型中,訂閱者通常接收所有發布的消息的一個子集。選擇接受和處理的消息的過程被稱作過濾。有兩種常用的過濾形式:基於主題的和基於內容的。
在 基於主題 的系統中,消息被發布到主題或命名通道上。訂閱者將收到其訂閱的主題上的所有消息,並且所有訂閱同一主題的訂閱者將接收到同樣的消息。發布者負責定義訂閱者所訂閱的消息類別。
在 基於內容 的系統中,訂閱者定義其感興趣的消息的條件,只有當消息的屬性或內容滿足訂閱者定義的條件時,消息才會被投遞到該訂閱者。訂閱者需要負責對消息進行分類。
一些系統支持兩者的混合:發布者發布消息到主題上,而訂閱者將基於內容的訂閱注冊到一個或多個主題上。
拓撲
在許多發布/訂閱系統中,發布者發布消息到一個中間的 消息代理,然后訂閱者向該消息代理注冊訂閱,由消息代理來進行過濾。消息代理通常執行 存儲轉發 的功能將消息從發布者發送到訂閱者。
使用場景
很多項目中都有消息分發或者事件通知機制,尤其是模塊化程度高的項目。
比如:在你的系統中,很多模塊都對 新建用戶 感興趣。權限模塊希望給新用戶設置默認權限,報表模塊希望重新生成當月的報表,郵件系統希望給用戶發送激活郵件...諸如此類的代碼都寫到新建用戶的業務邏輯后面,會加大耦合度,降低可維護性,並且對於每個模塊都是一個獨立系統的情況,這種方式更是不可取。
對於簡單的情形,觀察者模式 The Observer Pattern 就足夠了。如果系統中有很多地方都需要收發消息,那么它就不適用了。否則會造成類數量的膨脹,增加類的復雜性,這時候就需要一種更集中的機制來處理這些業務邏輯。
特點
一個訂閱者可以訂閱多個發布者
消息是會到達所有訂閱者處,訂閱者根據 filter 丟掉自己不需要的消息(filter 是在訂閱端起作用的)
每個訂閱者都會接收到每條消息的一個副本
基於推送 push,其中消息自動地向訂閱者廣播,它們無須請求或輪詢主題來獲得新消息,發布/訂閱模式內部,有多種不同類型的訂閱者。
非持久訂閱者是臨時訂閱類型,它們只是在主動偵聽主題時才接收消息。
持久訂閱者將接收到發布的每條消息的一個副本,即便在發布消息,它們處於"離線"狀態時也是如此。
另外還有動態持久訂閱者和受管的持久訂閱者等類型。
優勢
- 降低了模塊間的耦合度:發布者與訂閱者松散地耦合,並且不需要知道對方的存在。相關操作都集中在 Publisher 中。
- 可擴展性強:系統復雜后,可以把消息訂閱和分發機制單獨作為一個模塊來實現,增加新特性以滿足需求
缺陷
與其說缺陷,不如說它設計本身就有如下特點。但不管怎么說,這種模式在邏輯上不可靠的。主要體現在:
- 發布者不知道訂閱者是否收到發布的消息
- 訂閱者不知道自己是否收到了發布者發出的所有消息
- 發送者不能獲知訂閱者的執行情況
- 沒人知道訂閱者何時開始收到消息