布-訂閱(Pub-Sub)系統(tǒng)設計:從需求到 Go 源碼級實現(xiàn))
示例工程【免費下載鏈接】awesome-low-level-designLearn Low Level Design (LLD) and prepare for interviews using free resources.項目地址https://gitcode.com/GitHub_Trending/aw/awesome-low-level-design點擊查看免費下載導讀本文以 awesome-low-level-design 倉庫中 solutions/golang/pubsubsystem 的實現(xiàn)為主線完整講解發(fā)布-訂閱Publisher-Subscriber系統(tǒng)的核心需求、類/接口設計以及 Go 語言落地實現(xiàn)。你將掌握 Topic、Subscriber、Publisher 的角色劃分理解如何用sync.RWMutex保障并發(fā)安全并能運行倉庫自帶的 pubsub_system_demo.go 演示多發(fā)布者、多訂閱者的實時消息投遞場景。本文適用于準備系統(tǒng)設計/低層設計LLD面試、或需要快速搭建進程內(nèi)消息總線的開發(fā)者。一、系統(tǒng)需求Requirements關聯(lián)文檔 README.md 定義了本系統(tǒng)需要滿足的 6 條核心需求它們是后續(xù)所有類設計的出發(fā)點面向主題發(fā)布系統(tǒng)應允許發(fā)布者Publisher將消息發(fā)布到指定的主題Topic。按主題訂閱訂閱者Subscriber可以訂閱感興趣的主題并接收發(fā)布到這些主題上的消息。多對多支持系統(tǒng)應支持多個發(fā)布者和多個訂閱者。實時投遞消息應實時投遞給主題的所有訂閱者。并發(fā)安全系統(tǒng)應處理并發(fā)訪問并確保線程安全??蓴U展與高效系統(tǒng)在消息投遞方面應具備可擴展性和高效性。這 6 條需求定義了一個進程內(nèi)、基于主題解耦的廣播模型發(fā)布者與訂閱者互不感知只通過 Topic 這一中間媒介建立聯(lián)系。二、核心類、接口與枚舉設計關聯(lián)文檔給出了 7 個核心設計元素逐一展開如下Message消息表示一條可被發(fā)布、可被訂閱者接收的消息內(nèi)容為消息正文。對應 Go 實現(xiàn)見 message.go。Topic主題消息發(fā)布的目標。維護一組訂閱者集合提供添加/移除訂閱者以及向所有訂閱者發(fā)布消息的方法。對應 topic.go。Subscriber訂閱者接口定義訂閱者的契約聲明onMessage方法該方法在訂閱者收到消息時被調用。對應 subscriber.go。PrintSubscriber打印訂閱者Subscriber接口的具體實現(xiàn)接收消息并打印到控制臺。對應 print_subscriber.go。Publisher發(fā)布者向指定主題發(fā)布消息。對應 publisher.go。PubSubSystem系統(tǒng)主類管理主題、訂閱者與消息發(fā)布。按文檔描述它使用ConcurrentHashMap存儲主題、用ExecutorService處理并發(fā)消息發(fā)布——這是 Java 版 PubSubService.java 的設計對應倉庫內(nèi) UML 類圖 pubsubsystem-class-diagram.png而 Go 版本采用“Topic 自持讀寫鎖 按主題同步廣播”的等價方案將并發(fā)控制下沉到每個 Topic。PubSubDemo演示類通過創(chuàng)建主題、訂閱者、發(fā)布者并發(fā)布消息來演示系統(tǒng)用法。Go 版對應 pubsub_system_demo.go 中的Run()入口。三、Go 源碼級實現(xiàn)剖析3.1 Message輕量消息載體type Message struct { Content string } func NewMessage(content string) *Message { return Message{Content: content} }message.go 僅保留Content字段并通過NewMessage構造器統(tǒng)一創(chuàng)建。實際業(yè)務場景中可在此基礎上擴展Timestamp、Topic、Headers等元數(shù)據(jù)Java 類圖中的Message即攜帶timestamp: Instant與payload: String兩個字段見 pubsubsystem-class-diagram.png。3.2 Subscriber 接口與 PrintSubscriber 實現(xiàn)type Subscriber interface { OnMessage(message *Message) }subscriber.go 定義了訂閱者的唯一契約OnMessage。得益于 Go 接口的鴨子類型任何實現(xiàn)該方法的類型都能成為訂閱者。倉庫提供的默認實現(xiàn) print_subscriber.gotype PrintSubscriber struct { Name string } func NewPrintSubscriber(name string) *PrintSubscriber { return PrintSubscriber{Name: name} } func (ps *PrintSubscriber) OnMessage(message *Message) { fmt.Printf(Subscriber %s received message: %s\n, ps.Name, message.Content) }每個訂閱者通過Name區(qū)分身份OnMessage將消息打印到控制臺——這也正是需求 4“消息實時投遞給所有訂閱者”的可觀察落點。若需接入真實業(yè)務只需實現(xiàn)新的OnMessage邏輯如寫入隊列、調用下游 API。3.3 Topic訂閱注冊表 廣播中樞type Topic struct { Name string Subscribers map[Subscriber]struct{} mu sync.RWMutex } func NewTopic(name string) *Topic { return Topic{ Name: name, Subscribers: make(map[Subscriber]struct{}), } } func (t *Topic) AddSubscriber(subscriber Subscriber) { t.mu.Lock() defer t.mu.Unlock() t.Subscribers[subscriber] struct{}{} } func (t *Topic) RemoveSubscriber(subscriber Subscriber) { t.mu.Lock() defer t.mu.Unlock() delete(t.Subscribers, subscriber) } func (t *Topic) Publish(message *Message) { t.mu.RLock() defer t.mu.RUnlock() for subscriber : range t.Subscribers { subscriber.OnMessage(message) } }topic.go 是整套系統(tǒng)的核心包含三個設計要點訂閱集合用map[Subscriber]struct{}以接口值作鍵天然去重同一訂閱者重復AddSubscriber不會產(chǎn)生重復投遞struct{}作為空值占位零內(nèi)存開銷。讀寫鎖sync.RWMutex保證并發(fā)安全對應需求 5AddSubscriber/RemoveSubscriber寫操作加寫鎖LockPublish讀操作加讀鎖RLock允許多個發(fā)布者并發(fā)廣播、同時阻塞寫入期間的集合變更。廣播采用“讀鎖快照式遍歷”Publish在持有 RLock 期間遍歷訂閱者并同步調用OnMessage保證發(fā)布瞬間的訂閱集合一致性代價是投遞是同步的、按調用者線程串行完成。3.4 Publisher受控發(fā)布type Publisher struct { Topics map[*Topic]struct{} } func NewPublisher() *Publisher { return Publisher{Topics: make(map[*Topic]struct{})} } func (p *Publisher) RegisterTopic(topic *Topic) { p.Topics[topic] struct{}{} } func (p *Publisher) Publish(topic *Topic, message *Message) { if _, exists : p.Topics[topic]; !exists { fmt.Printf(This publisher cant publish to topic: %s\n, topic.Name) return } topic.Publish(message) }publisher.go 引入了一個文檔中未展開、但源碼里明確實現(xiàn)的發(fā)布權限控制機制發(fā)布者必須先RegisterTopic(topic)登記主題才能調用Publish未登記的主題會被拒絕并打印提示。這一設計讓“發(fā)布者只允許向授權主題發(fā)布”成為顯式約束是對需求 1 的工程化加強。3.5 PubSubDemo完整的端到端演示func Run() { // Create topics topic1 : NewTopic(Topic1) topic2 : NewTopic(Topic2) // Create publishers publisher1 : NewPublisher() publisher2 : NewPublisher() // Create subscribers subscriber1 : NewPrintSubscriber(Subscriber1) subscriber2 : NewPrintSubscriber(Subscriber2) subscriber3 : NewPrintSubscriber(Subscriber3) publisher1.RegisterTopic(topic1) publisher2.RegisterTopic(topic2) // Subscribe to topics topic1.AddSubscriber(subscriber1) topic1.AddSubscriber(subscriber2) topic2.AddSubscriber(subscriber2) topic2.AddSubscriber(subscriber3) // Publish messages publisher1.Publish(topic1, NewMessage(Message1 for Topic1)) publisher1.Publish(topic1, NewMessage(Message2 for Topic1)) publisher2.Publish(topic2, NewMessage(Message1 for Topic2)) // Unsubscribe from a topic topic1.RemoveSubscriber(subscriber2) // Publish more messages publisher1.Publish(topic1, NewMessage(Message3 for Topic1)) publisher2.Publish(topic2, NewMessage(Message2 for Topic2)) }pubsub_system_demo.go 完整覆蓋了文檔演示類描述的所有動作且特意構造了跨主題訂閱subscriber2同時訂閱 Topic1 與 Topic2和中途退訂兩個邊界場景步驟動作預期效果創(chuàng)建2 個 Topic、2 個 Publisher、3 個 Subscriber多發(fā)布者/多訂閱者拓撲就緒訂閱Topic1←{S1,S2}Topic2←{S2,S3}Subscriber2 同時訂閱兩個主題發(fā)布P1 發(fā) 2 條到 Topic1P2 發(fā) 1 條到 Topic2各主題訂閱者分別收到消息退訂RemoveSubscriber(subscriber2)Subscriber2 不再收到 Topic1 后續(xù)消息再發(fā)布P1 發(fā) Message3P2 發(fā) Message2驗證退訂生效S2 只收到 Topic2 的新消息四、運行方式Go 實現(xiàn)位于倉庫的 solutions/golang 模塊下模塊聲明見 go.modgo 1.23.2。運行有兩種方式方式一通過統(tǒng)一入口運行。倉庫根入口 solutions/golang/main.go 預留了所有項目的Run()調用點取消對應行的注釋并執(zhí)行go run .方式二直接在 pubsubsystem 包內(nèi)驗證。將Run()改為臨時main()后執(zhí)行go run pubsub_system_demo.go運行后控制臺將依次輸出類似Subscriber Subscriber1 received message: Message1 for Topic1的日志可用于直接驗證“實時投遞”“多訂閱者廣播”“退訂后不再接收”等需求是否成立。五、并發(fā)安全與擴展性分析對照需求 5 與需求 6從源碼結構可以做出如下分析并發(fā)安全Go 版以Topic.mu sync.RWMutex作為唯一同步原語覆蓋了訂閱集合的讀寫全部路徑無裸露的共享可變狀態(tài)Publisher.Topics在演示中僅在初始化階段寫入屬于線程啟動前的配置數(shù)據(jù)。這是比文檔描述的 Java 方案ConcurrentHashMapExecutorService見 PubSubService.java更簡潔的等價實現(xiàn)——Go 通過RWMutex把“讀多寫少”的廣播場景優(yōu)化為并發(fā)讀??蓴U展性當前Publish是同步串行廣播訂閱者數(shù)量增加時投遞時延隨之線性增長這是該實現(xiàn)的主要瓶頸點。如需擴展可參考文檔所述 Java 方案的思路——將OnMessage調用提交到ExecutorService異步執(zhí)行、或為每個 Topic 分配獨立 goroutine 隊列這些屬于基于原文檔設計意圖的演進方向倉庫當前 Go 實現(xiàn)并未包含。六、總結發(fā)布-訂閱系統(tǒng)的本質是用 Topic 解耦“誰生產(chǎn)”與“誰消費”發(fā)布者只認主題訂閱者只收回調。本倉庫的 Go 實現(xiàn)用 6 個文件、約 130 行代碼以sync.RWMutex精確滿足了文檔列出的全部 6 條需求并通過RegisterTopic的登記制與RemoveSubscriber的退訂能力提供了超出需求清單的工程細節(jié)。閱讀源碼時建議按Message → Subscriber → Topic → Publisher → Demo的順序推進即可完整復現(xiàn)一次“設計需求 → 類設計 → 并發(fā)落地 → 端到端驗證”的 LLD 實戰(zhàn)閉環(huán)。其他語言Java/C/C#/Python的對照實現(xiàn)可參考倉庫 problems/pub-sub-system.md 中列出的各語言目錄以及全局 UML 類圖 pubsubsystem-class-diagram.png。贊分享示例工程【免費下載鏈接】awesome-low-level-designLearn Low Level Design (LLD) and prepare for interviews using free resources.項目地址https://gitcode.com/GitHub_Trending/aw/awesome-low-level-design點擊查看免費下載相關推薦發(fā)布-訂閱Pub-Sub系統(tǒng)低層設計LLD實戰(zhàn)從需求到并發(fā)安全的 Java/Go 多語言實現(xiàn)發(fā)布 訂閱Pub Sub系統(tǒng)低層設計LLD實戰(zhàn)從需求到并發(fā)安全的 Java/Go 多語言實現(xiàn) 導讀 本文以 problems/pub sub syst示例工程基于 C 與 Java 雙實現(xiàn)剖析線程安全的發(fā)布-訂閱Pub-Sub系統(tǒng)設計基于 C 與 Java 雙實現(xiàn)剖析線程安全的發(fā)布 訂閱Pub Sub系統(tǒng)設計 發(fā)布 訂閱Pub Sub模式是解耦消息生產(chǎn)者與消費者的核心架構范式在實示例工程yuzu 模擬器Switch 游戲上 PC 的 30 分鐘上手與調優(yōu)指南yuzu 模擬器Switch 游戲上 PC 的 30 分鐘上手與調優(yōu)指南 yuzu 是一款用 C 編寫的開源 Switch 模擬器由 Citra 開發(fā)團虛擬化桌面應用圖形學上一篇XML Notepad智能編輯工作流突破XML處理效率瓶頸的全棧解決方案下一篇3個維度開源工具WarcraftHelper實現(xiàn)魔獸爭霸3兼容性優(yōu)化全指南創(chuàng)作聲明:本文部分內(nèi)容由AI輔助生成(AIGC),僅供參考