間接通訊的核心概念
用中介者取代直接連線,打破收發雙方的緊耦合。
先讀原文開場,旁邊就是白話
這是一本英文書。左邊放原文、右邊放白話導讀——你既讀得懂,也順手碰了原文。
用中介者取代直接連線,打破收發雙方的緊耦合。
什麼是間接通訊
用中介者取代直接連線,打破收發雙方的緊耦合。
深度探秘
在中間放一個人
什麼是間接通訊?
前面幾章談的通訊(像 RPC、socket)都是直接的:送方明確指定收方是誰,兩邊直接連在一起。這很方便,卻也帶來僵化。
間接通訊(indirect communication)的定義:分散式系統中的實體,透過一個中介者(intermediary)溝通,送方與收方之間沒有直接耦合。
注意「收方」可以是複數——許多間接通訊範式天生就支援一對多。
為什麼直接耦合會出問題?
想像最單純的 client-server:
- 因為直接綁定,要換掉伺服器、改用功能相同的另一台,很麻煩。
- 伺服器當機會直接波及 client,client 必須自己處理失敗。
間接通訊把這層直接連結拿掉,於是繼承了許多好處。電腦科學有句名言:
All problems in computer science can be solved by another level of indirection.(所有問題都能靠多加一層間接來解決。)
間接通訊=透過中介者溝通,送收雙方不直接耦合。
生活妙喻
公司前台與郵件信箱
比喻:公司的前台收發室
你要寄文件給「採購部負責人」。
- 直接通訊:你得知道那個人是誰、坐哪、現在在不在位子上,親手交到他手裡。哪天他離職、換人、請假,你就卡住了。
- 間接通訊:你把文件交給前台收發室(中介者)。你根本不必認識採購負責人本人;就算今天換了新人,收發室也會把文件轉給「現任負責人」。
收發室就是那「多加的一層」。它讓你和真正的收件人鬆綁:人可以換、可以多人、可以晚點再來拿。
但天下沒有白吃的午餐
多繞一手,文件會晚一點到——這就是間接帶來的效能成本。所以還有另一句配對名言:
There is no performance problem that cannot be solved by eliminating a level of indirection.(沒有什麼效能問題是「拿掉一層間接」解決不了的。)
中介者像前台收發室,讓你不必認識收件人,但訊息會晚一點到。
實用超能力
哪裡用得上?
什麼情況最適合間接通訊?
當系統預期會變動時,間接通訊特別好用:
- 行動環境:使用者頻繁連上/斷開全球網路,收方來來去去。
- 事件散播:收方未知且常變動,例如金融系統的即時行情推播。
- 大型基礎設施:例如 Google 的關鍵元件就大量使用間接通訊。
代價與權衡
| 面向 | 好處 | 代價 |
|---|---|---|
| 變化 | 元件可替換、複製、遷移 | 效能多了一層開銷 |
| 耦合 | 收發雙方鬆綁 | 缺乏直接耦合,較難管理與除錯 |
flowchart LR S[送方] --> M[中介者] M --> R1[收方一] M --> R2[收方二] M --> R3[收方三]
選用前先問自己:我的系統會常常變動嗎?我能接受多一層延遲嗎?
系統愈會變動,間接通訊愈值得;代價是效能與管理難度。
你把文件交給收發室而非本人,就算負責人換人或不在,文件照樣轉達。
包裹經由轉運中心比直送多繞一手,較有彈性但會晚一點到。
本節字彙
空間解耦與時間解耦
兩個關鍵性質:不必知道對方是誰、不必同時存在。
深度探秘
兩個維度的鬆綁
中介者帶來的兩大性質
用了中介者後,系統會出現兩個關鍵的『解耦(uncoupling)』性質:
空間解耦(space uncoupling)
送方不需要、也不用知道收方是誰,反之亦然。
因為彼此不認識,開發者就有很大的自由度去應付變化:參與者(送方或收方)可以被替換、更新、複製或遷移。
時間解耦(time uncoupling)
送方與收方可以擁有各自獨立的生命週期——換句話說,它們不必同時存在也能溝通。
這在易變環境裡特別寶貴:送方與收方常常來來去去。
兩者不一定同時出現
常見的誤會是『間接=兩種解耦都有』,其實不然。最有名的反例是 IP multicast:它是空間解耦但時間耦合——訊息送往群組(不認識特定收方,空間解耦),但所有收方必須在送出當下都在線(時間耦合)。
空間解耦=不必知道對方是誰;時間解耦=不必同時存在。
生活妙喻
留言板上的便利貼
比喻:辦公室的留言板
想像門口有面留言板當中介者。
- 空間解耦:你貼一張『冰箱裡的便當記得拿走』,你不必知道便當主人是誰,他也不必知道是誰提醒他。你們互不相識,照樣溝通。
- 時間解耦:你早上九點貼,主人下午三點才看到。你貼完早就走了,他來看時你不在現場——你們沒有同時存在也完成了溝通。
關鍵在於:便利貼會留在板子上(持久性),所以晚到的人還能看到。
持久性是時間解耦的前提
如果留言板是一塊『只在你站在前面時才顯示字、你一走就消失』的螢幕,那就沒有時間解耦了——這正是 IP multicast 的處境:它不保存訊息,收方沒在線就錯過。
留言板貼便利貼=既不必認識對方(空間),也不必同時在場(時間),但需要把訊息留下來。
實用超能力
四個象限定位法
用 2x2 表格幫範式定位
把『空間』與『時間』當兩軸,任何通訊範式都能歸位:
| 時間耦合 | 時間解耦 | |
|---|---|---|
| 空間耦合 | 訊息傳遞、RPC/RMI(第 4、5 章) | 較罕見(見書中習題探討) |
| 空間解耦 | IP multicast(收方須在線) | 本章多數間接通訊範式 |
怎麼判斷一個系統落在哪格?
問兩個問題:
- 送方需要知道收方是誰嗎?(不需要→空間解耦)
- 收方必須在送出當下就存在嗎?(不必→時間解耦)
flowchart TD
Q1{送方需知道收方身分嗎}
Q1 -->|需要| SC[空間耦合]
Q1 -->|不需要| SU[空間解耦]
SU --> Q2{收方須送出當下在線嗎}
Q2 -->|是| TC[時間耦合 例如 IP multicast]
Q2 -->|否| TU[時間解耦 多數間接範式]
想達成時間解耦,中介者就必須能儲存訊息,等收方準備好再遞送。
用『需不需知道對方』與『須不須同時在線』兩問,就能定位任何範式。
貼的人和看的人互不相識,照樣傳遞訊息。
訊息被留下來,雙方不必同時在場就能溝通。
不保存訊息,沒在線的人就錯過,所以只解耦空間不解耦時間。
本節字彙
非同步不等於時間解耦
釐清容易混淆的「非同步通訊」與「時間解耦」。
深度探秘
兩個容易搞混的詞
別把『非同步』和『時間解耦』畫上等號
這兩個詞長得很像,卻是不同維度。
非同步通訊(asynchronous communication)
送方送出訊息後不阻塞,直接繼續做自己的事,不必停下來等收方。
它解決的是『送方要不要等』的問題(也稱同步解耦/synchronization uncoupling)。
時間解耦(time uncoupling)
送方與收方可以各自獨立存在——例如送方送出時,收方根本還不存在。
它多了一個維度:雙方的生命週期可以完全不重疊。
關鍵差異
非同步只是說『我送完就走,不等回應』;時間解耦更強,說『就算你現在還沒出生,我也能留訊息給你』。前者談的是『等不等』,後者談的是『在不在』。
非同步=送完不等;時間解耦=雙方可不同時存在。兩者是不同維度。
生活妙喻
打電話 vs 寄信 vs 時空膠囊
比喻:三種傳話方式
- 同步:打電話。你得等對方接、講完才能掛——送方被卡住,雙方必須同時在線。
- 非同步:傳簡訊。你按下傳送就繼續滑手機,不必等他回。但對方此刻仍得是個『存在的門號』,訊息才送得出去。
- 時間解耦:寫一封信投進時空膠囊,要給『十年後住進這間房子的人』。收件人現在根本還不存在,但訊息會被保存到他出現。
看出差別了嗎?簡訊是非同步,但收件人此刻得存在;時空膠囊則連『收件人尚未出現』都能處理。
一個反向提醒
有些間接通訊雖然走中介者,卻仍提供同步服務。例如 JGroups 的 MessageDispatcher / RpcDispatcher 操作就是在間接通訊之上做出同步呼叫——所以『間接』也不必然等於『非同步』。
簡訊是非同步(送完不等);時空膠囊才是時間解耦(收件人可尚未存在)。
實用超能力
用兩個問題分辨
實戰:怎麼快速分辨?
面對一個系統,問兩個獨立的問題:
- 送方送出後會被卡住等回應嗎? 不會→非同步。
- 收方必須在送出當下就存在嗎? 不必→時間解耦。
這兩題答案可以任意組合:
| 範例 | 非同步? | 時間解耦? |
|---|---|---|
| 打電話 | 否 | 否 |
| 傳簡訊(收方須在線) | 是 | 否 |
| 寄信到持久信箱 | 是 | 是 |
| JGroups RpcDispatcher | 否 | 視實作 |
為什麼要分清楚?
本章很多技術同時是『非同步且時間解耦』,容易讓人誤以為兩者一體。但設計系統時若混淆,你可能誤以為『改成非同步就能讓離線使用者補收訊息』——其實那需要的是時間解耦(持久性),不是非同步。
用『會不會被卡住』與『收方須不須在場』兩個獨立問題,就能精準分辨。
按下送出就繼續做別的事,不必等回覆;但對方門號此刻得存在。
收件人現在還不存在,訊息也能被保存到他出現。
走了中介者,但你仍站著等對方回話,所以間接不必然非同步。
本節字彙
群組通訊(Group Communication)
用一個 multicast 操作送給整群人,比逐一送更有效率也更有保證。
群組與多播的程式模型
用一個 multicast 操作送給整群人,比逐一送更有效率也更有保證。
深度探秘
一次操作,送給一整群
群組通訊的核心:群組
群組通訊(group communication)的中心概念是群組(group)與群組成員(membership):程序可以加入(join)或離開(leave)群組。送方對群組送一則訊息,系統就把它傳遞給所有成員,並提供可靠性與排序的保證。送方不知道收方們是誰——這正是空間解耦。
三種傳播範圍
| 名稱 | 範圍 |
|---|---|
| Unicast(單播) | 送給單一程序 |
| Multicast(多播) | 送給群組裡的所有成員 |
| Broadcast(廣播) | 送給系統裡的所有程序 |
群組通訊實作的就是多播:一個操作(在 Java 中是 aGroup.send(aMessage))就送給整群,而不是對每個成員各送一次。
群組通訊之於 IP multicast
群組通訊是多播的抽象,可建在 IP multicast 或覆蓋網路之上,額外加上成員管理、失效偵測、可靠性與排序保證。
群組通訊之於 IP multicast,就像 TCP 之於 IP 的點對點服務。
群組通訊=一個 multicast 操作送給整群成員,並附帶 IP multicast 沒有的保證。
生活妙喻
群組廣播 vs 一個個打電話
比喻:通知全班停課
班導要通知全班『明天停課』。
- 多次個別送(單播):一個個打電話。打第 30 通時,第 1 個同學早就掛了;萬一你打到一半手機沒電,有些人收到、有些人沒收到,而且大家收到的時間先後不一。
- 單一多播操作:在班級群組發一則公告。系統一次處理,能保證『要嘛全班都收到、要嘛都沒收到』,順序也一致。
為什麼單一操作不只是方便?
它讓底層實作能做到頻寬最佳化:訊息透過散播樹(distribution tree)在每條連線上最多送一次,還能利用網路硬體的多播能力。
例如從倫敦送給加州同網段的兩台電腦:兩次 UDP 各送一份、第二份還被第一份拖延;而一次 IP multicast 只送一份跨洋,到了當地路由器再用乙太網硬體一次送達兩台。
單一多播不只省事,更能省頻寬、縮短總時間,並提供『全有或全無』的整體保證。
實用超能力
封閉群組與開放群組
群組服務的關鍵分類
各種群組通訊服務的假設不同,最重要的兩組區分:
封閉(closed) vs 開放(open)群組
- 封閉群組:只有成員能對群組多播。成員多播時,自己也會收到。適合『合作的伺服器彼此私下傳訊息,只有它們該收到』。
- 開放群組:群組外的程序也能往裡送。適合『把事件遞送給一群有興趣的程序』。
重疊(overlapping) vs 非重疊群組
- 重疊:一個實體可同時屬於多個群組(現實系統通常如此)。
- 非重疊:每個程序最多屬於一個群組。
程序群組 vs 物件群組
flowchart TD A[群組服務] --> B[程序群組] A --> C[物件群組] B --> B1[訊息是未結構化位元組陣列] B --> B2[層次類似 socket] C --> C1[呼叫自動 marshalling 與 dispatch] C --> C2[client 透過本地 proxy 視為單一物件]
實務上程序群組仍佔主流(例如 JGroups),物件群組(如 Electra)雖更高階卻較少用。
封閉群組只准成員送、開放群組外人也能送;現實常見重疊群組;程序群組是主流。
群組公告一次到位、順序一致、全有或全無;逐一打電話會漏人又錯序。
外人發不了訊息,確保內部訊息只有成員看得到。
同一份在每段路最多送一次,到最後一站才分送,省下重複傳輸。
本節字彙
可靠性與排序保證
完整性、有效性、一致同意,以及 FIFO/因果/全序三種排序。
深度探秘
可靠的三個條件
可靠多播的三大性質
單對單的可靠通訊靠兩個性質:
- 完整性(integrity):收到的訊息與送出的相同,且不會重複遞送(最多一次)。
- 有效性(validity):任何送出的訊息終究會被遞送。
要擴展到『多個收方』,再加上第三個:
- 一致同意(agreement):如果訊息遞送給群組中任一個程序,就會遞送給群組中所有程序。
換句話說,可靠多播追求的是『要嘛大家都收到、要嘛大家都沒收到』,而且內容正確、不重複、終究會到。
為什麼排序也要管?
底層的 IPC 原語不保證排序。若多播是用一連串單對單訊息實作,這些訊息可能遭遇任意延遲;用 IP multicast 也有類似問題。所以群組通訊另外提供有序多播(ordered multicast)。
可靠多播=完整性+有效性+一致同意;排序則需另外的有序多播保證。
生活妙喻
三種排序:報告先後的規矩
比喻:群組裡訊息該怎麼排
想像一個專案群組,大家都在發訊息。三種排序保證好比三種不同嚴格度的規矩:
FIFO 排序(先進先出,也叫來源排序)
同一個人先說的,會先被所有人看到。
比喻:每個人說話照自己的順序,但不同人之間誰先誰後不保證。像大家各自的訊息按自己的時間軸排好,但你的第 1 句和我的第 1 句先後不定。
因果排序(causal ordering)
若一則訊息『發生在另一則之前』(有因果關係,例如『回覆』在『原話』之後),這個先後會在所有人那裡被保留。
比喻:你回覆了我的話,那大家一定先看到我的原話、再看到你的回覆——不會看到莫名其妙的回覆卻沒原話。
全序(total ordering)
只要在某個人那裡 A 比 B 先到,那在所有人那裡也都是 A 比 B 先。
比喻:全班看到的訊息順序一模一樣,像同一份逐字稿。
FIFO=同來源有序;因果=有因果關係的先後一致;全序=所有人看到的順序完全相同。
實用超能力
怎麼選排序強度
排序由弱到強
flowchart LR FIFO[FIFO 同來源有序] --> CAUSAL[因果 保留發生在前的關係] CAUSAL --> TOTAL[全序 所有人順序完全一致]
這三種可單用、也可混用(hybrid)。
怎麼挑?看需求
| 需求情境 | 建議排序 |
|---|---|
| 只要同一來源的訊息不亂序 | FIFO |
| 回覆不能比原話早出現 | 因果 |
| 多個副本必須做出完全一致的決定 | 全序 |
天下沒有白吃的保證
排序與可靠性都是分散式系統中『協調與一致同意』的例子。保證愈強,演算法愈複雜、對擴展性的負面衝擊愈大——尤其是全序。所以別一味追求最強,按應用真正需要的程度選即可。完整的演算法細節留待第 15 章。
排序強度由弱到強為 FIFO→因果→全序;保證愈強愈傷擴展性,按需選擇。
只要有一個人收到,就保證全部人都收到,不會只有部分人拿到。
有因果關係的訊息先後在每個人那裡都被保留,不會出現孤兒回覆。
所有人看到的訊息順序完全相同。
本節字彙
群組成員管理
在成員會加入、離開、當機的動態環境裡,維持一致的成員視圖。
深度探秘
誰還在群組裡?
動態環境的挑戰
在真實系統裡,程序會隨時加入、離開、甚至當機。群組成員管理(group membership management)的任務,就是隨時維持一份準確的當前成員視圖。它有四大任務:
- 提供成員變更介面:建立/銷毀群組、加入/移出程序。
- 失效偵測(failure detection):監看成員是否當機或因通訊故障而不可達,把程序標記為 Suspected(疑似失效)或 Unsuspected,並據此把疑似失效者排除出群組。
- 通知成員變更:當有程序加入或被排除(失效或主動退出)時,通知其他成員。
- 群組位址展開(group address expansion):送方只提供群組識別碼,由成員服務展開成當前成員清單來遞送。
為什麼位址展開很關鍵?
服務能藉由控制位址展開,讓多播遞送與成員變更協調一致——即使遞送途中成員正在變動,也能一致地決定每則訊息該送到哪些人。
成員服務四大任務:變更介面、失效偵測、通知變更、位址展開。
生活妙喻
社團幹部的點名簿
比喻:社團的點名與通訊錄
把成員服務想成社團裡那位盡責的幹部,手上拿著即時更新的通訊錄:
- 變更介面:有人填表入社、有人退社,幹部負責更新名單。
- 失效偵測:某人連續三次沒回訊息、電話也不通,幹部先把他標『疑似失聯(Suspected)』,確認後就從名單拿掉。
- 通知變更:誰來了、誰走了,幹部會在群組公告,讓大家知道現在還有誰。
- 位址展開:你只要說『發給全社』,幹部就照當下的名單一個個轉發——你不必自己列出每個人。
IP multicast 是個『不完整的幹部』
IP multicast 只做了部分:能讓人動態加入/離開、也會做位址展開(送方只給一個多播位址)。但它不告訴你現在群組裡有誰,多播遞送也不與成員變更協調。要補齊這些,需要更進階的『視圖同步(view-synchronous)群組通訊』。
成員服務像盡責的社團幹部,維護即時名單;IP multicast 只是個只做一半的幹部。
實用超能力
規模與波動的限制
群組通訊的適用邊界
flowchart TD J[加入 join] --> M[成員管理] L[離開 leave] --> M F[失效 fail] --> M M --> E[位址展開] E --> MC[多播遞送給當前成員]
維護成員視圖的需求,深刻影響群組做法的實用性:
- 最有效:小規模、靜態的系統。
- 較吃力:大規模、或波動度高(成員頻繁進出)的環境。
原因是它需要某種同步假設。每當成員變動就要重新達成一致視圖,規模一大、變動一頻繁,成本就爆炸。
怎麼突破?
- 用更機率性的成員管理(如 Ganesh 等人提出,底層用 gossip 八卦協定)來應付大規模、動態環境。
- 也有專為 ad hoc 網路與行動環境設計的成員協定。
實戰啟示:若你的系統節點成千上萬又頻繁進出,傳統強保證群組通訊會很痛,這時要考慮機率性/gossip 路線。
群組通訊最適合小規模靜態系統;大規模高波動時改採機率性或 gossip 做法。
負責更新名單、偵測失聯、公告變動,並照當下名單轉發訊息。
送方只給群組識別碼,服務把它展開成當前成員清單。
能加入離開與位址展開,但不告訴你現在有誰,也不協調遞送與變更。
本節字彙
案例:JGroups 工具箱
用 Channel、Building blocks、可組合的協定堆疊實作可靠群組通訊。
深度探秘
三層架構
JGroups 是什麼
JGroups 是用 Java 寫的可靠群組通訊工具箱,傳承自 Cornell 大學的 ISIS、Horus、Ensemble,現由開源社群(JBoss 中介軟體社群的一部分)維護。它支援程序加入/離開群組、向全群或單一成員送訊息、接收訊息,並提供多種可靠性與排序保證以及成員服務。
三個主要元件
flowchart TD APP[應用程式] --> BB[Building blocks 高階抽象] APP --> CH[Channel 最原始介面] BB --> CH CH --> PS[協定堆疊] PS --> NET[網路]
- Channel(通道):給應用開發者的最原始介面,提供加入、離開、送、收四項核心功能。
- Building blocks(建構區塊):建在 channel 之上的更高階抽象。
- Protocol stack(協定堆疊):底層通訊協定,由一疊可組合的協定層構成。
JGroups 三層:Channel(核心介面)、Building blocks(高階抽象)、協定堆疊(可組合協定層)。
生活妙喻
Channel 像對講機頻道
比喻:Channel 就像對講機頻道
一個程序透過 channel 物件接觸群組,它就像一支對講機的『把手』:
- 剛建立時是斷線狀態。
- 執行
connect綁定到一個具名群組(若群組不存在,第一次 connect 時就隱式建立)。 - 用
disconnect離開群組,用close讓 channel 不再可用。 - 一個 channel 一次只能連一個群組;要連多個群組就得開多支對講機(多個 channel)。
連上之後就能透過 channel 送收訊息,訊息以可靠多播送出(確切語意由所部署的協定堆疊決定)。
火災警報範例
書中用一個火災警報服務示範:警報器 connect("AlarmChannel") 後,建一則目的地為 null(代表送給全體成員)的訊息,payload 是未結構化位元組,透過 send 送給全群。接收端 connect 後呼叫 receive(0)(timeout 為 0 代表阻塞直到收到訊息)。
JChannel channel = new JChannel();
channel.connect("AlarmChannel");
channel.send(new Message(null, null, "Fire!"));
注意:JGroups 的 channel 與 6.3 的『頻道式發布訂閱』不同,這裡的 channel 等同於一個群組的實例。
Channel 像對講機把手:connect 綁群組、一次連一群,目的地 null 代表送給全體。
實用超能力
可組合的協定堆疊
協定堆疊:像積木一樣疊
協定是一疊雙向的協定層,每層都實作兩個方法:
public Object up(Event evt); // 事件往上傳
public Object down(Event evt); // 事件往下傳
事件(進出的訊息、或視圖變更等管理事件)在堆疊裡上下穿梭。每層可對訊息做任意處理:修改內容、加標頭,甚至丟棄或重排。
常見的五層範例
| 層 | 職責 |
|---|---|
| UDP | 最常見傳輸層,用 IP multicast 送全群、UDP datagram 做點對點 |
| FRAG | 訊息分段封包化(預設上限 8192 bytes) |
| MERGE | 處理網路分裂後子群組的重新合併 |
| GMS | 群組成員協定,維持一致成員視圖 |
| CAUSAL | 實作因果排序 |
為什麼可組合很強?
因為所有層都實作相同介面,可以任意順序組合(雖然不是每種組合都合理)。需要 FIFO、全序、加密、流量控制?換上對應的層即可。
一條鐵律:同一群組的所有成員必須共用相同的協定堆疊,否則大家對訊息的處理方式就不一致了。
協定堆疊由統一介面的協定層積木組成,可按需替換;同群組所有成員必須用相同堆疊。
connect 綁定到一個具名群組,一次只能連一群,連多群要多支對講機。
每塊積木介面相同可任意疊換,想要因果排序、加密就換上對應那塊。
不指定特定位址,訊息就送給群組的所有成員。
本節字彙
發布訂閱系統(Publish-Subscribe)
publish、subscribe、notify 等操作,以及頻道/主題/內容/型別四種訂閱模型。
發布訂閱的程式模型與訂閱模型
publish、subscribe、notify 等操作,以及頻道/主題/內容/型別四種訂閱模型。
深度探秘
發布者、訂閱者與事件服務
什麼是發布訂閱系統
發布訂閱(publish-subscribe)系統,也叫分散式事件式系統,是本章所有間接通訊技術中最廣泛使用的一種。
發布者(publisher)把結構化的事件發布到事件服務,訂閱者(subscriber)透過訂閱表達對特定事件的興趣(訂閱可以是事件上的任意樣式)。系統負責把訂閱與已發布事件配對,並正確遞送通知(notification)。
一個事件可能遞送給很多訂閱者,所以發布訂閱本質上是一對多。
核心操作
publish(e):發布者散播事件 e。subscribe(f):訂閱者用過濾器(filter)f 表達興趣。unsubscribe(f):取消訂閱。notify(e):事件到達訂閱者時遞送。- (選用)
advertise(f)/unadvertise(f):發布者預先宣告將產生的事件種類。
發布訂閱=發布者發事件、訂閱者用過濾器表達興趣,系統配對並通知,本質一對多。
生活妙喻
報章雜誌的訂閱
比喻:雜誌訂閱與兩個特性
發布訂閱就像訂雜誌:出版社(發布者)出刊,你(訂閱者)只訂你想要的刊物,郵局(事件服務)負責把對的刊物送到對的人。你不必認識出版社、出版社也不必認識你——空間解耦。
發布訂閱有兩大特性:
異質性(heterogeneity)
原本沒打算互通的元件,只要發布者公開它提供的事件類型、訂閱者訂閱事件樣式並提供處理通知的介面,就能一起工作。
好比『小孩回家就開暖氣』——感測器和暖氣本來互不相干,靠事件就串起來了。
非同步性(asynchronicity)
通知由發布者非同步送給有興趣的訂閱者,避免發布者得與訂閱者同步——兩者需解耦。
交易室系統範例
書中的交易室系統:資訊提供者process 不斷收到外部行情,把每筆更新當事件發布;交易員process 為每支關注的股票建立訂閱,收到通知就顯示。為了公平,所有訂閱同一支股票的交易員必須收到相同資訊,因此需要可靠多播。
像訂雜誌:空間解耦、異質元件可互通、發布者與訂閱者非同步解耦。
實用超能力
四種訂閱模型
訂閱(過濾器)模型決定表達能力
由簡單到精細:
| 模型 | 怎麼訂閱 | 表達能力 |
|---|---|---|
| 頻道式(channel) | 訂閱具名頻道,收該頻道全部事件 | 最原始,唯一定義實體頻道 |
| 主題式(topic) | 事件含一個 topic 欄位,依主題訂閱(可階層化) | 等同頻道,但主題顯式宣告 |
| 內容式(content) | 對事件多個欄位值下查詢條件 | 遠比前兩者強,但實作更難 |
| 型別式(type) | 依事件型別/子型別配對 | 與物件導向結合,可做型別檢查 |
flowchart LR CH[頻道式 最簡單] --> TP[主題式 顯式主題] TP --> CT[內容式 多欄位查詢] CT --> TY[型別式 結合型別系統]
進階:複雜事件處理
有些應用光訂閱單一事件不夠,需要辨識事件樣式——例如『若 River Eden 至少三處水位上升超過 20% 且模擬模型也報淹水風險就通知我』。這叫複雜事件處理(complex event processing),樣式可以是邏輯、時間或空間上的。
主題階層的實戰好處
訂 indirect_communication 收整章;訂 indirect_communication/publish-subscribe 只收更細的子主題。階層讓你粗細自如。
訂閱模型由弱到強:頻道<主題<內容<型別;複雜事件處理可辨識事件樣式。
出版社出刊、你只訂想要的、郵局負責配送,雙方互不相識。
感測器與暖氣本不相干,靠事件樣式就能串接協作。
可訂上層主題收全部,也可訂子主題只收特定範圍。
本節字彙
集中式與分散式實作
從單一 broker,到 broker 網路,再到完全 P2P 的演進。
深度探秘
從一個 broker 到一張網
發布訂閱的核心任務
把事件有效率地遞送給所有『過濾器與該事件相符』的訂閱者。
此外還可能要兼顧安全、擴展性、失效處理、並行與服務品質——這讓實作相當複雜。
三種架構由簡到繁
1. 集中式(centralized) 單一節點上的伺服器當事件代理(event broker)。發布者把事件(與可選的廣告)送給 broker,訂閱者把訂閱送給 broker 並收回通知。互動全是點對點訊息(用訊息傳遞或遠端呼叫實作)。
優點:簡單。缺點:broker 是單點失效與效能瓶頸,缺乏韌性與擴展性。
2. 分散式(distributed) 把單一 broker 換成一張互相合作的 broker 網路。能在節點失效後存活,並在網際網路規模良好運作。
3. 完全 P2P(peer-to-peer) 不再區分發布者、訂閱者與 broker——所有節點都是 broker,合作實作事件路由。這是近期系統很受歡迎的策略。
架構三階:集中式(簡單但單點瓶頸)、broker 網路(有韌性可擴展)、完全 P2P(人人皆 broker)。
生活妙喻
郵局的三種規模
比喻:投遞系統的三種規模
集中式=只有一間總郵局 全城的信都得經過這唯一一間。簡單好管,但這間一旦失火(失效),全城停擺;尖峰時段也會大排長龍(瓶頸)。
分散式=一張郵局網 各地都有郵局,彼此合作轉信。某間關門,信還能繞道別間,承載量也大得多——這就是 broker 網路。
完全 P2P=鄰里互助傳信 沒有專門的郵局,每戶人家都兼任轉信站,大家合力把信接力送到目的地。非常有彈性,適合超大規模。
為什麼集中式撐不住大場面?
因為所有訂閱配對與通知都壓在同一台機器上。發布量一大,它先累垮;它一掛,整個系統跟著掛。所以一旦講求規模與韌性,就得往 broker 網路或 P2P 走。
集中式像唯一總郵局(怕失火怕塞車)、broker 網路像郵局網、P2P 像鄰里互助接力。
實用超能力
哪種訂閱模型好分散?
不同訂閱模型,分散難度差很多
flowchart TD C[集中式] --> E[簡單 維護訂閱庫做配對] CH[頻道式 主題式] --> M[相對簡單 對映到群組用多播遞送] CB[內容式 型別式] --> H[較複雜 需要內容式路由]
- 集中式:最直接,central service 維護訂閱庫,把事件與訂閱配對。
- 頻道式/主題式的分散實作:相對簡單——把頻道或主題對映到群組(見 6.2),再用底層多播(視需要用可靠/有序版本)遞送。
- 內容式(推而廣之,型別式)的分散實作:明顯更複雜,值得專門探討(下一節的事件路由)。
實戰決策
| 你的需求 | 建議 |
|---|---|
| 小系統、求快上線 | 集中式 broker |
| 大規模、要韌性、頻道/主題訂閱 | 分散式,對映到群組多播 |
| 大規模、內容式訂閱 | 分散式/P2P+內容式路由 |
關鍵心法:訂閱模型愈精細(內容式),分散實作就愈難,因為無法簡單對映到固定群組。
頻道/主題式可對映到群組多播,分散容易;內容式分散難,需要專門的內容式路由。
簡單好管,但怕失火(單點失效)也怕排隊(瓶頸)。
某間關門信件能繞道,承載量與韌性都更好。
沒有專職郵局,每戶都兼轉信站,適合超大規模。
本節字彙
事件路由:氾流、過濾與會合
內容式路由如何用最少訊息把事件送到正確訂閱者。
深度探秘
三層架構與事件路由
發布訂閱的整體分層
由下而上:
flowchart TD NP[網路協定 TCP IP multicast 無線] --> ER[事件路由 與覆蓋網路] ER --> MA[配對 matching]
- 底層:各種 IPC 服務(TCP/IP、IP multicast、無線等)。
- 核心:事件路由(event routing)——盡可能有效率地把通知送到合適的訂閱者;由覆蓋網路(overlay)支撐(架起 broker 網路或 P2P 結構)。內容式情境下這叫內容式路由(CBR)。
- 頂層:配對(matching)——確保事件符合某訂閱。配對常被下推進路由機制裡。
為什麼路由是重點?
因為要在不浪費頻寬的前提下,只把事件送給真正想要的人。不同路由策略就是在『簡單』與『省訊息』之間做取捨。
發布訂閱分三層:網路協定、事件路由(核心,靠覆蓋網路)、配對;配對常下推進路由。
生活妙喻
三種找人方式
比喻:要把消息送到對的人,有三招
氾流(flooding)=挨家挨戶大喊 把事件送給網路裡所有節點,配對留到訂閱端做;或反過來把訂閱氾流給所有可能的發布者。
好比在大樓裡每一戶都按門鈴喊一遍,誰有興趣自己出來。超簡單,但製造大量無謂流量。
過濾式(filtering)=沿路設路標 每個 broker 維護鄰居清單、訂閱清單與路由表。訂閱資訊往發布者方向傳播並沿路存下『這條路通往某訂閱』。事件來時,broker 只把它往有訂閱的路徑轉送。
好比郵差沿路看路標:『往左有人訂這份報』才往左走,沒人訂的方向就不送。
會合式(rendezvous)=指定碰頭點
把所有可能事件視為一個事件空間,切給各 broker 負責。SN(s) 決定某訂閱交給哪些會合節點保管;EN(e) 決定某事件去哪些會合節點配對。只要 EN(e) 與 SN(s) 的交集非空(對映交集規則),相符的事件與訂閱就會在某個會合點相遇。
氾流=挨家大喊(簡單但吵);過濾式=沿路看路標只走有訂閱的路;會合式=事件與訂閱在約定的碰頭點相遇。
實用超能力
優化與選擇
過濾式路由演算法(精簡版)
收到 publish(e) 來自節點 x:
matchlist := match(e, 本地訂閱)
notify(e) 給 matchlist
fwdlist := match(e, 路由表)
publish(e) 給 fwdlist 但排除 x
收到 subscribe(s) 來自節點 x:
若 x 是直連客戶 → 加入訂閱表
否則 → 把 (x, s) 加入路由表
把 subscribe(s) 轉給除了 x 以外的鄰居
用廣告再優化
純過濾式會因『訂閱往所有可能發布者氾流』而產生大量流量。若系統支援廣告(advertisement),可把廣告往訂閱者方向(對稱地)傳播來減負;有些系統兩者並用。
會合式與 DHT、gossip
- 一個漂亮的做法是把事件空間對映到分散式雜湊表(DHT):雜湊函式同時把事件與訂閱映到對應的會合節點。
- 另一路是gossip(八卦):節點週期性、機率性地與鄰居交換事件。純 gossip 等於另一種氾流;若納入內容資訊就成informed gossip,特別適合高度動態、節點頻繁進出(churn)的環境。
設計空間一覽
| 系統 | 訂閱模型 | 分布 | 路由 |
|---|---|---|---|
| CORBA Event Service | 頻道式 | 集中式 | 無 |
| Scribe | 主題式 | P2P (DHT) | 會合式 |
| Siena | 內容式 | 分散式 | 過濾式 |
| Meghdoot | 內容式 | P2P | 會合式 |
過濾式沿路存訂閱、可用廣告優化;會合式常用 DHT;gossip 加內容成 informed gossip 適合高 churn。
送給所有人、誰有興趣自己出來,簡單但製造大量無謂流量。
只往『有人訂閱』的方向轉送,沒人訂的方向就不走。
事件與訂閱各自被導到負責的會合節點,交集非空才能相遇。
本節字彙
訊息佇列(Message Queues)
producer 送、consumer 取,三種 receive 風格,以及持久性與交易支援。
訊息佇列的程式模型
producer 送、consumer 取,三種 receive 風格,以及持久性與交易支援。
深度探秘
點對點的中介者
訊息佇列是什麼
群組通訊與發布訂閱都是一對多;訊息佇列(message queue)不同,它提供點對點服務,用『佇列』當中介者,同樣達成空間與時間解耦。
它是點對點的:送方把訊息放進佇列,再由單一程序取走。
訊息佇列也稱訊息導向中介軟體(Message-Oriented Middleware),是重要的商業中介軟體類別,代表產品有 IBM WebSphere MQ、Microsoft MSMQ、Oracle AQ。主要用途是企業應用整合(EAI),也因內建交易支援而廣用於交易處理系統。
程式模型很簡單
- producer 把訊息送到特定佇列。
- consumer 從佇列取訊息。
三種 receive 風格:
- 阻塞式 receive:等到有合適訊息才回傳。
- 非阻塞式 receive(輪詢):檢查佇列狀態,有就回傳、沒有就回『不可用』。
- notify 操作:佇列有訊息時發出事件通知。
訊息佇列=點對點,送方放進佇列、單一程序取走;三種 receive:阻塞、輪詢、通知。
生活妙喻
便利商店的取貨櫃
比喻:超商取貨櫃
訊息佇列就像超商的取貨櫃:
- 寄件人(producer)把包裹放進櫃子,放完就走——不必等收件人在場(時間解耦),也不必認識是哪位店員之後來取(空間解耦)。
- 收件人或店員(consumer)之後來把包裹取走,一個包裹只會被一個人取走(點對點)。
三種取法剛好對應:
- 阻塞:站在櫃子前一直等,直到有你的包裹。
- 輪詢:每隔一陣子來看一眼,有就拿、沒有就走。
- 通知:留個電話,包裹到了櫃子打給你。
多人共用一個櫃子
多個寄件人可往同一佇列放,多個取件人也可從同一佇列取。預設是 FIFO(先進先出),但多數實作支援優先權(高優先先取),consumer 也能依訊息屬性挑選特定訊息。
像超商取貨櫃:放完就走、之後單人取走;阻塞=站著等、輪詢=定時看、通知=到了打給你。
實用超能力
持久、交易與轉換
訊息結構與持久性
一則訊息含:目的地(佇列識別碼)、中繼資料(如優先權、遞送模式)、主體(body)。主體通常是不透明的(系統不去動它),可用 marshalled 型別、物件序列化或 XML 序列化。
最關鍵的性質:持久性。
訊息佇列會把訊息無限期儲存(直到被取走),並寫入磁碟以確保可靠遞送。任何送出的訊息終究會被收到(有效性)、且與送出的相同、不會重複遞送(完整性)。
也就是說它保證會送到且只送一次,但不保證遞送的時間點。
額外能力
flowchart LR P[Producer] -->|Send| Q[訊息佇列] Q -->|Receive 或 Poll 或 Notify| C[Consumer]
- 交易(transaction):把送或收包進交易,達成『全有或全無』。
- 訊息轉換(transformation):對到達的訊息做格式轉換(如 big-endian↔little-endian、SOAP↔IIOP),是處理異質性、達成 EAI 的利器;負責轉換的服務常稱 message broker。
- 安全:如 WebSphere MQ 用 SSL 做機密傳輸與認證授權。
與訊息傳遞的關鍵差別
訊息傳遞(第 4 章)的佇列是隱含在收發雙方身上的;訊息佇列的佇列是顯式的第三方實體,獨立於送收雙方——這正是它成為間接通訊、達成空間與時間解耦的關鍵。
訊息持久(寫磁碟、保證送達且只一次但不保證時間),並支援交易、轉換與安全;佇列是獨立第三方實體。
放完就走、之後被單一人取走,達成時間與空間解耦的點對點傳遞。
訊息寫入磁碟無限期保存,保證送達且只一次。
佇列獨立於收發雙方,因此能達成空間與時間解耦。
本節字彙
實作架構與 WebSphere MQ
佇列管理員、訊息通道,以及 hub-and-spoke 拓樸。
深度探秘
集中式 vs 分散式
核心抉擇:集中還是分散
訊息佇列實作的關鍵問題,是集中式還是分散式。
- 集中式:一或多個佇列由某節點上的佇列管理員(queue manager)管理。優點是簡單,但管理員會變得笨重,可能成為瓶頸或單點失效。
- 分散式:為克服上述問題而生。我們以 IBM WebSphere MQ 為代表來看分散式架構。
WebSphere MQ 基本元素
- 佇列管理員(queue manager):託管並管理佇列,讓應用透過 MQI(Message Queue Interface) 存取。MQI 操作很單純:
MQCONN/MQDISC(連線/斷線)、MQPUT/MQGET(送/收)。 - 一台實體伺服器上可有多個佇列管理員。
訊息佇列關鍵抉擇是集中式(簡單但易成瓶頸)或分散式(如 WebSphere MQ)。
生活妙喻
兩種通道:客戶端 vs 佇列間
比喻:總倉與物流網
把佇列管理員想成物流倉庫,負責收發包裹(訊息)。
兩種不同的『通道』別搞混:
- 客戶通道(client channel):客戶應用 → 佇列管理員之間的連線。用熟悉的 proxy(代理) 概念:MQI 指令在 proxy 上發出,再用 RPC 透明地送到佇列管理員執行。好比你(客戶)打電話到倉庫下單,這通電話就是客戶通道。
- 訊息通道(message channel):兩個佇列管理員之間的單向連線,用來非同步地把訊息從一個佇列轉到另一個。由兩端的 訊息通道代理(MCA) 維護,初始會協商通道性質(含安全)。好比兩個倉庫之間的貨運專線。
flowchart LR CLI[客戶應用] -->|客戶通道 用 RPC| QM1[佇列管理員一] QM1 -->|訊息通道 非同步| QM2[佇列管理員二] QM2 --> SVC[服務]
搭配每個佇列管理員裡的路由表,就能組出任意拓樸(樹、網狀、匯流排等)。
客戶通道是客戶到佇列管理員用 RPC;訊息通道是佇列管理員之間單向非同步轉送。
實用超能力
hub-and-spoke 拓樸
hub-and-spoke(軸輻式)拓樸
一種 WebSphere MQ 常用的拓樸:
- 指定一個佇列管理員當 hub(軸心),上面託管多數服務。
- 客戶不直接連 hub,而是透過稱為 spoke(輻條) 的佇列管理員連入。
- spoke 把訊息轉送到 hub 的佇列供各服務處理。spoke 策略性地散布在網路各處,讓客戶能就近以高頻寬連線(spoke 甚至可放在與客戶同一台機器上以降低延遲)。
關鍵:阻塞只到本地
回想:客戶↔佇列管理員用 RPC,佇列管理員之間是非同步。因此客戶只會被阻塞到『訊息存進本地 spoke』為止;後續可能跨廣域網的遞送是非同步的,但由 MQ 中介軟體保證可靠送達。
代價與補救
| 問題 | 補救 |
|---|---|
| hub 是瓶頸與單點失效 | 用**佇列管理員叢集(cluster)**讓多個管理員支援同一服務並隱式負載平衡 |
實戰心法:軸輻式讓『跨國慢連線』變成『先快寫本地、再背景可靠轉送』,大幅改善客戶端體驗。
hub-and-spoke 讓客戶就近連 spoke 快速寫入,跨網遞送非同步進行;hub 瓶頸用叢集補救。
負責託管佇列、收發訊息,集中式時可能變成笨重的單一倉庫。
前者客戶到管理員用 RPC,後者管理員之間單向非同步轉送。
客戶就近連 spoke 快速寫入,再由系統背景把訊息可靠送到 hub。
本節字彙
案例:Java Messaging Service
JMS 如何用同一套 API 同時支援主題(發布訂閱)與佇列。
深度探秘
一套 API 兩種範式
JMS 是什麼
Java Messaging Service(JMS)是讓分散式 Java 程式間接通訊的標準規格。它最值得注意的一點是:把發布訂閱與訊息佇列兩種範式(至少表面上)統一起來——用 topic(主題) 與 queue(佇列) 當兩種不同的訊息目的地(destination)。
許多實作存在(Joram、JBoss Messaging、Open MQ、ActiveMQ、OpenJMS 等),WebSphere MQ 也提供 JMS 介面。
關鍵角色
- JMS client:產生或消費訊息的 Java 程式;producer 產生、consumer 接收。
- JMS provider:任何實作 JMS 規格的系統。
- JMS message:在 client 間傳遞資訊的物件。
- JMS destination:支援間接通訊的物件,不是 topic 就是 queue。
JMS 用同一套 API、以 topic 與 queue 兩種目的地,統一發布訂閱與訊息佇列。
生活妙喻
從總機到通話的層層建立
比喻:打一通正式商務電話的流程
JMS 的程式模型像一層層搭起一通電話:
flowchart TD CF[connection factory 總機] --> CONN[connection 連線] CONN --> SESS[session 通話階段] SESS --> MP[message producer 說話的人] SESS --> MC[message consumer 聽的人]
- connection factory(連線工廠):像總機,負責產生具備所需性質的連線。
- connection(連線):client 與 provider 間的邏輯通道(底層可能是 TCP/IP socket)。分 TopicConnection 與 QueueConnection 兩型,強制清楚區分兩種運作模式。
- session(階段):一連串建立、產生、消費訊息的操作;也支援交易(全有或全無)。TopicConnection 只能開 topic session、QueueConnection 只能開 queue session——不能在同一連線混用。所以說兩者只是『相當表面』地整合。
- producer / consumer:在 session 上建立,分別發送與接收訊息。
JMS 模型像層層搭電話:connection factory→connection→session→producer/consumer;topic 與 queue 連線不可混用。
實用超能力
訊息結構與 selector
JMS 訊息三部分
- header(標頭):識別與路由所需資訊——目的地、優先權、過期時間、訊息 ID、時間戳。多由系統產生。
- properties(屬性):使用者自訂的中繼資料(例如情境感知系統的 location 欄位)。
- body(主體):不透明、系統不動,可為文字、位元組串流、序列化 Java 物件、基本型別串流或名稱/值對。
consumer 比 producer 複雜:兩個原因
1. message selector(訊息選擇器) 可對 header 與 properties(不含 body)定義謂詞來過濾訊息,語法用 SQL 的子集。例如只收某 location 的訊息。
2. 兩種接收模式
- 阻塞的
receive操作;或 - 設一個 message listener 物件,提供
onMessage方法,一有合適訊息就被呼叫。
火災警報範例(topic 版)
TopicConnection conn = topicFactory.createTopicConnection();
TopicSession sess = conn.createTopicSession(false, Session.AUTO_ACKNOWLEDGE);
TopicPublisher pub = sess.createPublisher(topic);
TextMessage msg = sess.createTextMessage();
msg.setText("Fire!");
pub.publish(msg);
比 JGroups 版繁瑣,主要因為要建 connection、session、publisher、message,還要用 JNDI 找到 connection factory 與 topic。createTopicSession(false, ...) 第一參數 false 代表非交易,AUTO_ACKNOWLEDGE 代表自動確認收訊。
JMS 訊息分 header、properties、body;selector 用 SQL 子集過濾 header 與 properties(不含 body),接收可阻塞或用 listener。
層層建立,最後在 session 上產生說話與聽話的人(producer/consumer)。
TopicConnection 只能開 topic session,故整合僅止於表面。
用 SQL 子集對標頭與屬性過濾,但看不到包裹內容(body)。
本節字彙
共享記憶體取向(Shared Memory)
讓不共享實體記憶體的電腦,像存取普通變數一樣共享資料。
分散式共享記憶體(DSM)
讓不共享實體記憶體的電腦,像存取普通變數一樣共享資料。
深度探秘
讀寫變數就能溝通
什麼是 DSM
分散式共享記憶體(Distributed Shared Memory, DSM)是讓不共享實體記憶體的多台電腦之間共享資料的抽象。
程序透過對自己位址空間裡『看似普通的記憶體』做讀取與更新來存取 DSM。底層執行期系統會透明地確保不同電腦上的程序能看到彼此所做的更新。
感覺上像在存取單一共享記憶體,但實體記憶體其實是分散的。
DSM 的價值
它省去程式設計師處理訊息傳遞的麻煩——本來得自己 marshalling、收送訊息,現在直接讀寫共享變數即可。
適合與不適合
- 適合:平行運算,或任何能直接存取個別共享資料項的分散式應用。
- 不適合:典型的 client-server 系統。因為 client 通常把伺服器資源視為抽象資料、以請求方式存取(基於模組化與保護的考量)。
背後仍是訊息
沒有實體共享記憶體時,DSM 執行期支援還是得靠訊息在電腦間傳送更新。DSM 系統管理複製資料:每台電腦保有最近存取資料的本地副本以加速存取,因此實作問題與『複製』和『快取共享檔案』密切相關。
DSM 讓多台機器像讀寫普通記憶體般共享資料,省去 marshalling,但底層仍靠訊息同步副本。
生活妙喻
共用白板的錯覺
比喻:每人桌上的『同步白板』
想像一群分處各地的同事,每人桌上都有一塊白板。神奇的是:任何人在自己白板上寫字,其他人的白板也會自動出現相同內容——你會以為大家共用『同一塊白板』,其實是各自有實體白板,背後有人偷偷把更新抄來抄去(這就是底層的訊息傳遞)。
- 你不必『寫信告訴大家我改了什麼』(不必 marshalling/送訊息),直接寫白板就好。
- 大家手上都有最近內容的副本,看自己的白板就很快(本地快取)。
訊息傳遞 vs DSM 的取捨
| 面向 | 訊息傳遞 | DSM |
|---|---|---|
| 資料交換 | 需 marshalling/unmarshalling | 直接共享變數,免 marshalling |
| 保護 | 各自私有位址空間,互不干擾 | 共享,可能因誤改資料害到對方 |
| 異質性 | marshalling 處理資料表示差異 | 不同整數表示法如何共享記憶體是難題 |
| 同步 | 靠訊息原語(如 lock server) | 靠 lock、semaphore 等共享記憶體構件 |
| 生命週期 | 雙方須同時執行 | DSM 可持久,雙方生命週期可不重疊 |
| 成本可見度 | 每次遠端存取都明確 | 某次讀寫是否觸發通訊不一定看得出 |
DSM 像『自動同步的共用白板』:直接寫就好、免 marshalling,但失去私有位址空間的保護。
實用超能力
效能與成本可見度
硬體脈絡:從多處理器到叢集
flowchart TD SM[共享記憶體多處理器] --> NUMA[NUMA 架構 最多約 64 處理器] NUMA --> DM[分散記憶體多處理器與叢集] DM --> Q[能否把共享記憶體知識搬到更可擴展的架構]
- 共匯流排的處理器約 10 個就會因匯流排競爭而效能驟降。
- NUMA 架構(板間用高速交換器連接)可達約 64 個處理器,但板上記憶體存取較快、跨板較慢(故名 Non-Uniform)。
- 分散記憶體叢集則能擴展到遠多於 64 個。一個核心研究問題是:共享記憶體的演算法知識能否轉用到更可擴展的分散架構。
效能與一個重要陷阱
實驗顯示,在少量電腦(約 10 台)時,DSM 程式可與功能等價的訊息傳遞程式效能相當——但這無法一般化,效能高度取決於資料共享的樣式(例如某項是否被多個程序更新)。
成本可見度:DSM 的隱藏代價
訊息傳遞中,所有遠端存取都是明確的,程式設計師永遠知道哪個操作要付通訊代價。
DSM 中,任何一次讀或寫可能、也可能不觸發底層通訊,端看資料是否曾被存取、跨機共享樣式如何。
這把通訊成本『藏』起來了——方便,卻也讓你較難預估效能。是否該用 DSM 沒有定論,最終取決於它能被多有效率地實作。
DSM 在少量機器可媲美訊息傳遞,但效能依共享樣式而定;最大隱憂是通訊成本被隱藏、難以預估。
各自有白板卻自動同步內容,感覺像共用一塊,背後靠訊息偷偷抄送更新。
共享資料沒有私有位址空間的隔離,誤改會害到別人。
一次讀寫可能只是本地、也可能引發跨機通訊,代價藏在背後。
本節字彙
元組空間與 Linda 模型
以內容定址、可阻塞配對的共享元組集合,天生空間與時間解耦。
深度探秘
用內容找資料
元組空間是什麼
元組空間(tuple space)由 David Gelernter 提出,是一種基於**生成式通訊(generative communication)**的分散式運算模型。
程序透過把元組(tuple)放進一個共享的元組空間來間接溝通,其他程序可從中讀取或移除元組。
關鍵特色:元組沒有位址,而是用內容做樣式配對來存取——這叫內容定址記憶體(content-addressable memory),也就是『聯想式定址』。這形成了影響深遠的 Linda 程式模型,後續催生了 JavaSpaces、IBM TSpaces 等系統。
與 DSM 的層次差異
- DSM 以讀寫位元組運作,用位址存取。
- 元組空間提供更高層次的視角:半結構化資料,用**內容(聯想式)**存取。
三個核心操作
write:加入一個元組,不影響既有元組。read:回傳一個符合的元組,但不移除它。take:回傳一個符合的元組,並移除它。
元組是**不可變(immutable)**的——不能直接改,只能 take 出來、改完再 write 回去。
元組空間=把元組丟進共享空間、用內容樣式配對存取(聯想式);三操作 write/read/take,元組不可變。
生活妙喻
失物招領的留言箱
比喻:以特徵領取的失物招領
元組空間像一個失物招領處,每件物品(元組)就是一串有型別的欄位,例如 <"Capital", "Scotland", "Edinburgh">、<"Population", "UK", 61000000>。
你領東西時不是說『給我第 3 號櫃』(位址),而是描述特徵:
take(<String, "Scotland", String>)→ 配到<"Capital", "Scotland", "Edinburgh">並取走。take(<String, "Scotland", Integer>)→ 配到<"Population", "Scotland", 5168000>並取走。read(<"Population", String, Integer>)→ 找到一個符合的就看一眼(不取走)。
阻塞同步:等到有符合的為止
read 與 take 都會阻塞,直到空間裡出現符合的元組——這讓程序能藉此同步活動。好比你在失物招領處說『有人撿到藍色雨傘就通知我』,沒有就一直等。
共享計數器範例(不可變)
要把計數器加一,不能直接改,得先 take 出來再 write 回去:
<s, count> := myTS.take(<"counter", integer>)
myTS.write(<"counter", count+1>)
像以『特徵』領取的失物招領:用內容配對存取;read/take 會阻塞等到有符合元組,達成同步。
實用超能力
解耦性質與演進
天生的空間與時間解耦
flowchart LR P1[送方任意] -->|write| TS[元組空間] TS -->|read 或 take| P2[收方任意]
- 空間解耦:元組可來自任意送方、被任意收方取得(Linda 稱為『分散式命名』)。
- 時間解耦:元組放進空間後會留著(可能無限期)直到被移除,因此送收方不必在時間上重疊。
兩者合起來,提供完全在空間與時間上分散的共享。
原始操作名稱對照
| 本書用語 | Linda 原名 |
|---|---|
| write | out |
| read | rd |
| take | in |
演進:多重元組空間
原始 Linda 用單一全域元組空間。系統一大就有意外混名(aliasing)的危險:元組一多,read/take 不小心配到錯的元組的機率上升(尤其是用型別配對如 take(<String, integer>) 時)。因此後來提出多重元組空間、甚至能動態建立,引入**作用域(scoping)**來降低誤配。
案例:JavaSpaces
JavaSpaces 把元組空間帶進物件導向世界:元組變成 entry 物件,用 write、read、take 操作,配對時填入欄位值就只配到該值的 entry,留空則任何同型 entry 都算符合。
元組空間天生空間與時間解耦;單一全域空間易誤配,故演進出多重元組空間與作用域;JavaSpaces 將其物件化。
不說櫃號(位址),而是描述物品特徵(內容)來配對取得。
沒有符合元組就阻塞,直到出現為止,藉此同步活動。
要更新得先 take 出舊的、改完再 write 回新的。
本節字彙
五種間接通訊的總結比較
把五種範式放在一起,比較解耦程度、服務風格與擴展性。
深度探秘
五種範式的共性
五種間接通訊範式
本章涵蓋了五種透過『中介者』溝通的方式:
- 群組通訊(中介=群組)
- 發布訂閱(中介=頻道/主題/發布訂閱系統整體)
- 訊息佇列(中介=佇列)
- 分散式共享記憶體 DSM(中介=共享記憶體)
- 元組空間(中介=元組空間)
它們的共性:都透過某種中介者間接溝通,因此都帶來『送收解耦』與『應付變化/容錯』的好處。
解耦程度回顧
- 空間解耦:五種全部都有(訊息都導向中介者,而非特定收方)。
- 時間解耦:較微妙,取決於持久性。
- 一定有:訊息佇列、DSM、元組空間。
- 可能有(看實作):群組通訊、發布訂閱。例如 JGroups 可選擇讓新成員補看歷史;JMS 支援持久事件。
五種範式全部空間解耦;時間解耦取決於持久性——佇列/DSM/元組空間一定有,群組/發布訂閱看實作。
生活妙喻
通訊式 vs 狀態式
兩大家族:強調『通訊』還是『狀態』
把五種分成兩派,像兩種辦公室協作文化:
通訊式(communication-based):群組、發布訂閱、訊息佇列
強調『傳訊息/傳事件』。
像同事間互相傳便條、發公告——重點是訊息的流動。
狀態式(state-based):DSM、元組空間
提供共享狀態的抽象。
像大家圍著一塊共享白板改來改去——重點是共享的狀態。
這個差異牽動擴展性
flowchart TD COMM[通訊式] --> SCALE[配合適當路由 可擴展到極大規模] STATE[狀態式] --> LIMIT[受限 需維持一致的共享狀態視圖] COMM --> GEXC[例外 群組通訊因需維護成員管理而受限]
- 通訊式有潛力擴展到極大規模(配合路由基礎設施)——但群組通訊是例外,因為要維護成員管理。
- 狀態式擴展受限,因為要在多讀多寫者間維持一致的共享狀態視圖。元組空間較微妙:元組不可變,但破壞性的 take 操作在大規模難實作;有趣的是,拿掉 take,元組空間就很像發布訂閱(因此也就潛在地可高度擴展)。
通訊式(群組/發布訂閱/佇列)潛在可大擴展但群組通訊例外;狀態式(DSM/元組空間)因需一致共享視圖而擴展受限。
實用超能力
一張表選對工具
五種範式總比較
| 面向 | 群組 | 發布訂閱 | 訊息佇列 | DSM | 元組空間 |
|---|---|---|---|---|---|
| 空間解耦 | 是 | 是 | 是 | 是 | 是 |
| 時間解耦 | 可能 | 可能 | 是 | 是 | 是 |
| 服務風格 | 通訊式 | 通訊式 | 通訊式 | 狀態式 | 狀態式 |
| 通訊樣式 | 一對多 | 一對多 | 一對一 | 一對多 | 一對一或一對多 |
| 主要用途 | 可靠分散式運算 | 資訊散播/EAI;行動與普及運算 | 資訊散播/EAI;商業交易處理 | 平行與分散式運算 | 平行與分散式運算;行動與普及運算 |
| 擴展性 | 有限 | 可能 | 可能 | 有限 | 有限 |
| 聯想式定址 | 否 | 僅內容式發布訂閱 | 否 | 否 | 是 |
怎麼選?看意圖
- 要可靠、要排序保證 → 群組通訊(但別期待大擴展)。
- 要大規模資訊散播/EAI → 發布訂閱。
- 要點對點、持久、交易 → 訊息佇列(天生點對點,常與發布訂閱搭售)。
- 要平行運算直接共享資料 → DSM 或元組空間。
- 要內容配對(聯想式定址) → 內容式發布訂閱或元組空間。
尚未解的挑戰
本章未深談服務品質(QoS)。空間與時間解耦的本質,讓推理端到端性質(如即時行為、安全)變得困難,這仍是重要的研究方向。
用『解耦、服務風格、通訊樣式、擴展性、聯想式定址、主要意圖』六面向,就能對症選出最適合的間接通訊範式。
通訊式重訊息流動、狀態式重共享狀態,後者因需一致視圖而較難擴展。
去掉破壞性的 take,元組空間就很像發布訂閱,因此潛在可高度擴展。
要可靠選群組、要散播選發布訂閱、要點對點持久選佇列、要共享資料選 DSM 或元組空間。