從金拱門餐廳聯想到的分佈式系統設計思維

從金拱門餐廳聯想到的分佈式系統設計思維

作者|周明耀

編輯|小智

其實在生活中,大到組織架構,小到瑣碎日常,都能學到一些經驗、總結出一些知識。共性往往隱藏在特性之中。你需要做的,可能僅僅只是細心觀察而已?

前幾天中午開會開到 12 點半,錯過了食堂吃飯時間,只能大家一起去麥當勞吃午飯。寫這篇文章,既是有感於麥當勞餐廳的管理模式,也是由於這次就餐在訂餐、取餐過程中發生了一些小事,讓我聯想到了分佈式軟件設計,我在 InfoQ 開設技術專欄之初就說過技術來源於生活細節,那麼今天就來系統性談談由麥當勞餐廳(這裡並不特指麥當勞,我相信也有類似的其他店家,這裡只是以麥當勞作為舉例)所想到的分佈式系統設計思維及問題。

結合現階段的觀察,我有以下觀點:

  • 店長負責制 ->主從模式(Master/Slave);

  • 訂單處理方式 ->兩階段提交;

  • 員工角色拆分 ->微服務設計;

  • 多個任務接收 ->微服務設計之後的服務橫向擴展;

  • 訂單處理過程上屏 ->任務隊列設計;

  • 座位設計模式 ->容器化管理。

我逐一聊聊我的看法,歡迎您留言指教。

店長負責制

每家麥當勞都會有一名當值經理,這位經理負責當前整個店的運營,如果遇到某個崗位繁忙的情況,那麼他會臨時調動其他相對空閒的員工支援,那麼這就是典型的 Master/Slave 架構。

從金拱門餐廳聯想到的分佈式系統設計思維

作者|周明耀

編輯|小智

其實在生活中,大到組織架構,小到瑣碎日常,都能學到一些經驗、總結出一些知識。共性往往隱藏在特性之中。你需要做的,可能僅僅只是細心觀察而已?

前幾天中午開會開到 12 點半,錯過了食堂吃飯時間,只能大家一起去麥當勞吃午飯。寫這篇文章,既是有感於麥當勞餐廳的管理模式,也是由於這次就餐在訂餐、取餐過程中發生了一些小事,讓我聯想到了分佈式軟件設計,我在 InfoQ 開設技術專欄之初就說過技術來源於生活細節,那麼今天就來系統性談談由麥當勞餐廳(這裡並不特指麥當勞,我相信也有類似的其他店家,這裡只是以麥當勞作為舉例)所想到的分佈式系統設計思維及問題。

結合現階段的觀察,我有以下觀點:

  • 店長負責制 ->主從模式(Master/Slave);

  • 訂單處理方式 ->兩階段提交;

  • 員工角色拆分 ->微服務設計;

  • 多個任務接收 ->微服務設計之後的服務橫向擴展;

  • 訂單處理過程上屏 ->任務隊列設計;

  • 座位設計模式 ->容器化管理。

我逐一聊聊我的看法,歡迎您留言指教。

店長負責制

每家麥當勞都會有一名當值經理,這位經理負責當前整個店的運營,如果遇到某個崗位繁忙的情況,那麼他會臨時調動其他相對空閒的員工支援,那麼這就是典型的 Master/Slave 架構。

從金拱門餐廳聯想到的分佈式系統設計思維

我們來看看較為典型的 Matser/Slave 架構設計。以 HBase 為例,看看 HBase 的 RegionServer 設計方式。在 HBase 內部,所有的用戶數據以及元數據的請求,在經過 Region 的定位,最終會落在 RegionServer 上,並由 RegionServer 實現數據的讀寫操作。RegionServer 是 HBase 集群運行在每個工作節點上的服務。它是整個 HBase 系統的關鍵所在,一方面它維護了 Region 的狀態,提供了對於 Region 的管理和服務;另一方面,它與 HMaster 交互,上傳 Region 的負載信息上傳,參與 HMaster 的分佈式協調管理。

從金拱門餐廳聯想到的分佈式系統設計思維

作者|周明耀

編輯|小智

其實在生活中,大到組織架構,小到瑣碎日常,都能學到一些經驗、總結出一些知識。共性往往隱藏在特性之中。你需要做的,可能僅僅只是細心觀察而已?

前幾天中午開會開到 12 點半,錯過了食堂吃飯時間,只能大家一起去麥當勞吃午飯。寫這篇文章,既是有感於麥當勞餐廳的管理模式,也是由於這次就餐在訂餐、取餐過程中發生了一些小事,讓我聯想到了分佈式軟件設計,我在 InfoQ 開設技術專欄之初就說過技術來源於生活細節,那麼今天就來系統性談談由麥當勞餐廳(這裡並不特指麥當勞,我相信也有類似的其他店家,這裡只是以麥當勞作為舉例)所想到的分佈式系統設計思維及問題。

結合現階段的觀察,我有以下觀點:

  • 店長負責制 ->主從模式(Master/Slave);

  • 訂單處理方式 ->兩階段提交;

  • 員工角色拆分 ->微服務設計;

  • 多個任務接收 ->微服務設計之後的服務橫向擴展;

  • 訂單處理過程上屏 ->任務隊列設計;

  • 座位設計模式 ->容器化管理。

我逐一聊聊我的看法,歡迎您留言指教。

店長負責制

每家麥當勞都會有一名當值經理,這位經理負責當前整個店的運營,如果遇到某個崗位繁忙的情況,那麼他會臨時調動其他相對空閒的員工支援,那麼這就是典型的 Master/Slave 架構。

從金拱門餐廳聯想到的分佈式系統設計思維

我們來看看較為典型的 Matser/Slave 架構設計。以 HBase 為例,看看 HBase 的 RegionServer 設計方式。在 HBase 內部,所有的用戶數據以及元數據的請求,在經過 Region 的定位,最終會落在 RegionServer 上,並由 RegionServer 實現數據的讀寫操作。RegionServer 是 HBase 集群運行在每個工作節點上的服務。它是整個 HBase 系統的關鍵所在,一方面它維護了 Region 的狀態,提供了對於 Region 的管理和服務;另一方面,它與 HMaster 交互,上傳 Region 的負載信息上傳,參與 HMaster 的分佈式協調管理。

從金拱門餐廳聯想到的分佈式系統設計思維

HRegionServer 與 HMaster 以及 Client 之間採用 RPC 協議進行通信。HRegionServer 向 HMaster 定期彙報節點的負載狀況,包括 RS 內存使用狀態、在線狀態的 Region 等信息。在該過程中 HRegionServer 扮演了 RPC 客戶端的角色,而 HMaster 扮演了 RPC 服務器端的角色。HRegionServer 內置的 RpcServer 實現了數據更新、讀取、刪除的操作,以及 Region 涉及到 Flush、Compaction、Open、Close、Load 文件等功能性操作。

Region 是 HBase 數據存儲和管理的基本單位。HBase 使用 RowKey 將表水平切割成多個 HRegion,從 HMaster 的角度,每個 HRegion 都紀錄了它的 StartKey 和 EndKey(第一個 HRegion 的 StartKey 為空,最後一個 HRegion 的 EndKey 為空),由於 RowKey 是排序的,因而 Client 可以通過 HMaster 快速的定位每個 RowKey 在哪個 HRegion 中。HRegion 由 HMaster 分配到相應的 HRegionServer 中,然後由 HRegionServer 負責 HRegion 的啟動和管理,和 Client 的通信,負責數據的讀 (使用 HDFS)。每個 HRegionServer 可以同時管理 1000 個左右的 HRegion。

如果我們需要把固定店長負責制改為全員負責制,有沒有類似案例?有,Apache Cassandra 就沒有固定的中心節點(無中心化設計),只有協調者節點(理論上所有節點都可以作為協調者,每次請求有且僅有一個),那麼它是怎麼做到的呢?

首先我們來看看 Gossip 協議。Cassandra 集群中的節點沒有主次之分,通過 Gossip 協議,可以知道集群中有哪些節點,以及這些節點的狀態如何等等。每一條 Gossip 消息上都有一個版本號,節點可以對接收到的消息進行版本比對,從而得知哪些消息是我需要更新的,哪些消息是我有而別人沒有的,然後互相傾聽吐槽,確保二者得到的信息相同,這很像現實生活中的八卦,一傳十,十傳百,最後盡人皆知。在 Cassandra 啟動時,會啟動 Gossip 服務,Gossip 服務啟動後會啟動一個任務 GossipTask,每秒鐘運行一次,這個任務會週期性與其他節點進行通信。回到麥當勞餐廳,是不是每個員工之間也需要有對講機,實時互相告知目前自己的工作狀態和訂單信息,這樣才能做到無固定店長管理制?

既然 Cassandra 是無中心化的,那麼如何來計算各個節點上存儲的數據之間的差異呢?如何判斷自己申請的那一份數據就是最新版本的?這裡我要引出 Snitch(告密者)機制了。

現實生活中的“告密者”會向別人告密誰有需要尋找的東西,Snitch 機制在 Cassandra 集群裡也起到了這樣的作用。Snitch 機制的功能是決定集群每個節點之間的相關性(值),這個值被用於決定從哪個節點讀取或寫入數據。此外,由於 Snitch 機制收集網絡拓撲內的信息,這樣 Cassandra 可以有效路由各類外部請求。Snitch 機制解決了節點與集群內其他節點之間的關係問題(前面介紹的 Gossip 解決了節點之間的消息交換問題)。當 Cassandra 收到一個讀請求,需要去做的是根據一致性級別聯繫對應的數據副本。為了支持最快速度的讀取請求,Cassandra 選擇單一副本讀取全部數據,然後要求附加數據副本的 hash 值,以此確保讀取的數據是最新的數據並返回。Snitch 的角色是幫助驗證返回最快的副本,這些副本又被用於讀取數據。回到麥當勞餐廳,如果互相都知道了誰有哪些產品,那麼完成訂單配單步驟就不難了。

正是有了 Gossip 協議和 Snitch 機制,才在通信層基本滿足無中心化的設計,當然還有很多其他因素一起參與,這裡不逐一介紹。

訂單處理方式

麥當勞和其他商家一樣,都在追求每日最大訂單處理量,你懂的,更多的訂單意味著更多的利潤。為了加快銷售速度,麥當勞採用了異步處理模式。當你在收銀臺下單後,收營員會進行下單,下單過程結束後你就進入到了製作訂單隊列。有了這個隊列,營業員和製作員之間的串行耦合方式被解耦了,這樣營業員就可以繼續接收訂單,即便製作員手上的訂單已經開始排隊了也沒關係。店裡忙的時候,會動態增加幾個製作員,他們之間本身也處在一個競爭環境下,就好像分佈式環境下會存在多個計算節點一樣。因此,麥當勞的這種製作過程,本質上就是一個分佈式處理過程:訂單隊列屬於中心節點的待運行任務隊列,每個製作員是一個計算節點,無論採用申請還是被分配策略,他們都可以正常幹活。這讓我聯想到了兩階段提交,為什麼麥當勞不採用兩階段提交協議呢?

以上我介紹的所有這些策略都不同於兩階段提交,什麼是兩階段提交?它是分佈式系統架構下的所有節點在進行事務提交時保持一致性而設計的一種算法。

兩階段提交協議一般將事務的提交過程分成了兩個階段:

  • 提交事務請求

  • 執行事務請求

核心是對每個事務都採用先嚐試後提交的處理方式,因此它是一個強一致性的算法。

雖然說兩階段提交算法具有原理簡單、實現方便的優點,但是它也有一些缺點:

  • 同步阻塞:所有參與該事務操作的邏輯都處於阻塞狀態;

  • 單點問題:協調者(營業員、訂單排隊隊列)如果出現問題,整個兩階段提交流程將無法運轉,參與者的資源將會處於鎖定狀態;

  • 數據不一致:協調者向所有的參與者發送 Commit 請求之後,由於局部網絡問題,會出現部分參與者沒有收到 Commit 請求,進一步造成數據不一致現象。麥當勞的例子中,如果有多個隊列排隊時,會存在問題;

  • 太過保守:參與者出現異常時,協調者只能通過其自身的超時機制來判斷是否需要中斷事務,即任意一個節點的失敗都會導致整個事務的失敗。

兩階段提交需要依賴於不同的準備和執行步驟。在麥當勞這個例子裡面,如果採用兩階段提交,顧客需要在收銀臺等待食物製作完畢,他得自己拿著錢。然後,錢、收據、食物會做一次一手交錢,一手交貨的交換。營業員和顧客在交易完成前都不能離開。兩階段提交可以讓生活更加簡單,但是也會傷害到消息的自由流通,因為本質上它是基於各方之間的有狀態交易資源的異步動作。

員工角色拆分

如果你去小型的快餐店,你會發現只有 1 名員工,他既要負責下單、收銀,也要負責事物製作、打包、售後,這樣自然速度就慢了。麥當勞把員工分為了幾個不同的角色,這些角色在幹活的時候互相不干擾,通過通信機制(訂單系統信息投影屏幕)方式協同工作,這樣可以最大化生產效率。即使出現意外情況,例如某位員工中暑了,店長可以立即協調一名員工補上他的位置,而不需要和其他角色的員工產生交互成本。

這樣的設計方式和微服務架構比較類似,下面這張圖是一個典型的傳統單體型服務架構模式:

從金拱門餐廳聯想到的分佈式系統設計思維

作者|周明耀

編輯|小智

其實在生活中,大到組織架構,小到瑣碎日常,都能學到一些經驗、總結出一些知識。共性往往隱藏在特性之中。你需要做的,可能僅僅只是細心觀察而已?

前幾天中午開會開到 12 點半,錯過了食堂吃飯時間,只能大家一起去麥當勞吃午飯。寫這篇文章,既是有感於麥當勞餐廳的管理模式,也是由於這次就餐在訂餐、取餐過程中發生了一些小事,讓我聯想到了分佈式軟件設計,我在 InfoQ 開設技術專欄之初就說過技術來源於生活細節,那麼今天就來系統性談談由麥當勞餐廳(這裡並不特指麥當勞,我相信也有類似的其他店家,這裡只是以麥當勞作為舉例)所想到的分佈式系統設計思維及問題。

結合現階段的觀察,我有以下觀點:

  • 店長負責制 ->主從模式(Master/Slave);

  • 訂單處理方式 ->兩階段提交;

  • 員工角色拆分 ->微服務設計;

  • 多個任務接收 ->微服務設計之後的服務橫向擴展;

  • 訂單處理過程上屏 ->任務隊列設計;

  • 座位設計模式 ->容器化管理。

我逐一聊聊我的看法,歡迎您留言指教。

店長負責制

每家麥當勞都會有一名當值經理,這位經理負責當前整個店的運營,如果遇到某個崗位繁忙的情況,那麼他會臨時調動其他相對空閒的員工支援,那麼這就是典型的 Master/Slave 架構。

從金拱門餐廳聯想到的分佈式系統設計思維

我們來看看較為典型的 Matser/Slave 架構設計。以 HBase 為例,看看 HBase 的 RegionServer 設計方式。在 HBase 內部,所有的用戶數據以及元數據的請求,在經過 Region 的定位,最終會落在 RegionServer 上,並由 RegionServer 實現數據的讀寫操作。RegionServer 是 HBase 集群運行在每個工作節點上的服務。它是整個 HBase 系統的關鍵所在,一方面它維護了 Region 的狀態,提供了對於 Region 的管理和服務;另一方面,它與 HMaster 交互,上傳 Region 的負載信息上傳,參與 HMaster 的分佈式協調管理。

從金拱門餐廳聯想到的分佈式系統設計思維

HRegionServer 與 HMaster 以及 Client 之間採用 RPC 協議進行通信。HRegionServer 向 HMaster 定期彙報節點的負載狀況,包括 RS 內存使用狀態、在線狀態的 Region 等信息。在該過程中 HRegionServer 扮演了 RPC 客戶端的角色,而 HMaster 扮演了 RPC 服務器端的角色。HRegionServer 內置的 RpcServer 實現了數據更新、讀取、刪除的操作,以及 Region 涉及到 Flush、Compaction、Open、Close、Load 文件等功能性操作。

Region 是 HBase 數據存儲和管理的基本單位。HBase 使用 RowKey 將表水平切割成多個 HRegion,從 HMaster 的角度,每個 HRegion 都紀錄了它的 StartKey 和 EndKey(第一個 HRegion 的 StartKey 為空,最後一個 HRegion 的 EndKey 為空),由於 RowKey 是排序的,因而 Client 可以通過 HMaster 快速的定位每個 RowKey 在哪個 HRegion 中。HRegion 由 HMaster 分配到相應的 HRegionServer 中,然後由 HRegionServer 負責 HRegion 的啟動和管理,和 Client 的通信,負責數據的讀 (使用 HDFS)。每個 HRegionServer 可以同時管理 1000 個左右的 HRegion。

如果我們需要把固定店長負責制改為全員負責制,有沒有類似案例?有,Apache Cassandra 就沒有固定的中心節點(無中心化設計),只有協調者節點(理論上所有節點都可以作為協調者,每次請求有且僅有一個),那麼它是怎麼做到的呢?

首先我們來看看 Gossip 協議。Cassandra 集群中的節點沒有主次之分,通過 Gossip 協議,可以知道集群中有哪些節點,以及這些節點的狀態如何等等。每一條 Gossip 消息上都有一個版本號,節點可以對接收到的消息進行版本比對,從而得知哪些消息是我需要更新的,哪些消息是我有而別人沒有的,然後互相傾聽吐槽,確保二者得到的信息相同,這很像現實生活中的八卦,一傳十,十傳百,最後盡人皆知。在 Cassandra 啟動時,會啟動 Gossip 服務,Gossip 服務啟動後會啟動一個任務 GossipTask,每秒鐘運行一次,這個任務會週期性與其他節點進行通信。回到麥當勞餐廳,是不是每個員工之間也需要有對講機,實時互相告知目前自己的工作狀態和訂單信息,這樣才能做到無固定店長管理制?

既然 Cassandra 是無中心化的,那麼如何來計算各個節點上存儲的數據之間的差異呢?如何判斷自己申請的那一份數據就是最新版本的?這裡我要引出 Snitch(告密者)機制了。

現實生活中的“告密者”會向別人告密誰有需要尋找的東西,Snitch 機制在 Cassandra 集群裡也起到了這樣的作用。Snitch 機制的功能是決定集群每個節點之間的相關性(值),這個值被用於決定從哪個節點讀取或寫入數據。此外,由於 Snitch 機制收集網絡拓撲內的信息,這樣 Cassandra 可以有效路由各類外部請求。Snitch 機制解決了節點與集群內其他節點之間的關係問題(前面介紹的 Gossip 解決了節點之間的消息交換問題)。當 Cassandra 收到一個讀請求,需要去做的是根據一致性級別聯繫對應的數據副本。為了支持最快速度的讀取請求,Cassandra 選擇單一副本讀取全部數據,然後要求附加數據副本的 hash 值,以此確保讀取的數據是最新的數據並返回。Snitch 的角色是幫助驗證返回最快的副本,這些副本又被用於讀取數據。回到麥當勞餐廳,如果互相都知道了誰有哪些產品,那麼完成訂單配單步驟就不難了。

正是有了 Gossip 協議和 Snitch 機制,才在通信層基本滿足無中心化的設計,當然還有很多其他因素一起參與,這裡不逐一介紹。

訂單處理方式

麥當勞和其他商家一樣,都在追求每日最大訂單處理量,你懂的,更多的訂單意味著更多的利潤。為了加快銷售速度,麥當勞採用了異步處理模式。當你在收銀臺下單後,收營員會進行下單,下單過程結束後你就進入到了製作訂單隊列。有了這個隊列,營業員和製作員之間的串行耦合方式被解耦了,這樣營業員就可以繼續接收訂單,即便製作員手上的訂單已經開始排隊了也沒關係。店裡忙的時候,會動態增加幾個製作員,他們之間本身也處在一個競爭環境下,就好像分佈式環境下會存在多個計算節點一樣。因此,麥當勞的這種製作過程,本質上就是一個分佈式處理過程:訂單隊列屬於中心節點的待運行任務隊列,每個製作員是一個計算節點,無論採用申請還是被分配策略,他們都可以正常幹活。這讓我聯想到了兩階段提交,為什麼麥當勞不採用兩階段提交協議呢?

以上我介紹的所有這些策略都不同於兩階段提交,什麼是兩階段提交?它是分佈式系統架構下的所有節點在進行事務提交時保持一致性而設計的一種算法。

兩階段提交協議一般將事務的提交過程分成了兩個階段:

  • 提交事務請求

  • 執行事務請求

核心是對每個事務都採用先嚐試後提交的處理方式,因此它是一個強一致性的算法。

雖然說兩階段提交算法具有原理簡單、實現方便的優點,但是它也有一些缺點:

  • 同步阻塞:所有參與該事務操作的邏輯都處於阻塞狀態;

  • 單點問題:協調者(營業員、訂單排隊隊列)如果出現問題,整個兩階段提交流程將無法運轉,參與者的資源將會處於鎖定狀態;

  • 數據不一致:協調者向所有的參與者發送 Commit 請求之後,由於局部網絡問題,會出現部分參與者沒有收到 Commit 請求,進一步造成數據不一致現象。麥當勞的例子中,如果有多個隊列排隊時,會存在問題;

  • 太過保守:參與者出現異常時,協調者只能通過其自身的超時機制來判斷是否需要中斷事務,即任意一個節點的失敗都會導致整個事務的失敗。

兩階段提交需要依賴於不同的準備和執行步驟。在麥當勞這個例子裡面,如果採用兩階段提交,顧客需要在收銀臺等待食物製作完畢,他得自己拿著錢。然後,錢、收據、食物會做一次一手交錢,一手交貨的交換。營業員和顧客在交易完成前都不能離開。兩階段提交可以讓生活更加簡單,但是也會傷害到消息的自由流通,因為本質上它是基於各方之間的有狀態交易資源的異步動作。

員工角色拆分

如果你去小型的快餐店,你會發現只有 1 名員工,他既要負責下單、收銀,也要負責事物製作、打包、售後,這樣自然速度就慢了。麥當勞把員工分為了幾個不同的角色,這些角色在幹活的時候互相不干擾,通過通信機制(訂單系統信息投影屏幕)方式協同工作,這樣可以最大化生產效率。即使出現意外情況,例如某位員工中暑了,店長可以立即協調一名員工補上他的位置,而不需要和其他角色的員工產生交互成本。

這樣的設計方式和微服務架構比較類似,下面這張圖是一個典型的傳統單體型服務架構模式:

從金拱門餐廳聯想到的分佈式系統設計思維

單體型架構比較適合小項目,缺點比較明顯:

  • 開發效率低:所有的開發在一個項目改代碼,遞交代碼相互等待,代碼衝突不斷

  • 代碼維護難:代碼功能耦合在一起,新人不知道何從下手

  • 部署不靈活:構建時間長,任何小修改必須重新構建整個項目,這個過程往往很長

  • 穩定性不高:一個微不足道的小問題,可以導致整個應用掛掉

  • 擴展性不夠:無法滿足高併發情況下的業務需求

微服務是指開發一個單個小型的但有業務功能的服務,每個服務都有自己的處理和輕量通訊機制,可以部署在單個或多個服務器上。微服務也指一種種鬆耦合的、有一定的有界上下文的面向服務架構。也就是說,如果每個服務都要同時修改,那麼它們就不是微服務,因為它們緊耦合在一起;如果你需要掌握一個服務太多的上下文場景使用條件,那麼它就是一個有上下文邊界的服務。

經過微服務架構的拆分,上面描述的單體型架構成了下面這張微服務架構圖:

從金拱門餐廳聯想到的分佈式系統設計思維

作者|周明耀

編輯|小智

其實在生活中,大到組織架構,小到瑣碎日常,都能學到一些經驗、總結出一些知識。共性往往隱藏在特性之中。你需要做的,可能僅僅只是細心觀察而已?

前幾天中午開會開到 12 點半,錯過了食堂吃飯時間,只能大家一起去麥當勞吃午飯。寫這篇文章,既是有感於麥當勞餐廳的管理模式,也是由於這次就餐在訂餐、取餐過程中發生了一些小事,讓我聯想到了分佈式軟件設計,我在 InfoQ 開設技術專欄之初就說過技術來源於生活細節,那麼今天就來系統性談談由麥當勞餐廳(這裡並不特指麥當勞,我相信也有類似的其他店家,這裡只是以麥當勞作為舉例)所想到的分佈式系統設計思維及問題。

結合現階段的觀察,我有以下觀點:

  • 店長負責制 ->主從模式(Master/Slave);

  • 訂單處理方式 ->兩階段提交;

  • 員工角色拆分 ->微服務設計;

  • 多個任務接收 ->微服務設計之後的服務橫向擴展;

  • 訂單處理過程上屏 ->任務隊列設計;

  • 座位設計模式 ->容器化管理。

我逐一聊聊我的看法,歡迎您留言指教。

店長負責制

每家麥當勞都會有一名當值經理,這位經理負責當前整個店的運營,如果遇到某個崗位繁忙的情況,那麼他會臨時調動其他相對空閒的員工支援,那麼這就是典型的 Master/Slave 架構。

從金拱門餐廳聯想到的分佈式系統設計思維

我們來看看較為典型的 Matser/Slave 架構設計。以 HBase 為例,看看 HBase 的 RegionServer 設計方式。在 HBase 內部,所有的用戶數據以及元數據的請求,在經過 Region 的定位,最終會落在 RegionServer 上,並由 RegionServer 實現數據的讀寫操作。RegionServer 是 HBase 集群運行在每個工作節點上的服務。它是整個 HBase 系統的關鍵所在,一方面它維護了 Region 的狀態,提供了對於 Region 的管理和服務;另一方面,它與 HMaster 交互,上傳 Region 的負載信息上傳,參與 HMaster 的分佈式協調管理。

從金拱門餐廳聯想到的分佈式系統設計思維

HRegionServer 與 HMaster 以及 Client 之間採用 RPC 協議進行通信。HRegionServer 向 HMaster 定期彙報節點的負載狀況,包括 RS 內存使用狀態、在線狀態的 Region 等信息。在該過程中 HRegionServer 扮演了 RPC 客戶端的角色,而 HMaster 扮演了 RPC 服務器端的角色。HRegionServer 內置的 RpcServer 實現了數據更新、讀取、刪除的操作,以及 Region 涉及到 Flush、Compaction、Open、Close、Load 文件等功能性操作。

Region 是 HBase 數據存儲和管理的基本單位。HBase 使用 RowKey 將表水平切割成多個 HRegion,從 HMaster 的角度,每個 HRegion 都紀錄了它的 StartKey 和 EndKey(第一個 HRegion 的 StartKey 為空,最後一個 HRegion 的 EndKey 為空),由於 RowKey 是排序的,因而 Client 可以通過 HMaster 快速的定位每個 RowKey 在哪個 HRegion 中。HRegion 由 HMaster 分配到相應的 HRegionServer 中,然後由 HRegionServer 負責 HRegion 的啟動和管理,和 Client 的通信,負責數據的讀 (使用 HDFS)。每個 HRegionServer 可以同時管理 1000 個左右的 HRegion。

如果我們需要把固定店長負責制改為全員負責制,有沒有類似案例?有,Apache Cassandra 就沒有固定的中心節點(無中心化設計),只有協調者節點(理論上所有節點都可以作為協調者,每次請求有且僅有一個),那麼它是怎麼做到的呢?

首先我們來看看 Gossip 協議。Cassandra 集群中的節點沒有主次之分,通過 Gossip 協議,可以知道集群中有哪些節點,以及這些節點的狀態如何等等。每一條 Gossip 消息上都有一個版本號,節點可以對接收到的消息進行版本比對,從而得知哪些消息是我需要更新的,哪些消息是我有而別人沒有的,然後互相傾聽吐槽,確保二者得到的信息相同,這很像現實生活中的八卦,一傳十,十傳百,最後盡人皆知。在 Cassandra 啟動時,會啟動 Gossip 服務,Gossip 服務啟動後會啟動一個任務 GossipTask,每秒鐘運行一次,這個任務會週期性與其他節點進行通信。回到麥當勞餐廳,是不是每個員工之間也需要有對講機,實時互相告知目前自己的工作狀態和訂單信息,這樣才能做到無固定店長管理制?

既然 Cassandra 是無中心化的,那麼如何來計算各個節點上存儲的數據之間的差異呢?如何判斷自己申請的那一份數據就是最新版本的?這裡我要引出 Snitch(告密者)機制了。

現實生活中的“告密者”會向別人告密誰有需要尋找的東西,Snitch 機制在 Cassandra 集群裡也起到了這樣的作用。Snitch 機制的功能是決定集群每個節點之間的相關性(值),這個值被用於決定從哪個節點讀取或寫入數據。此外,由於 Snitch 機制收集網絡拓撲內的信息,這樣 Cassandra 可以有效路由各類外部請求。Snitch 機制解決了節點與集群內其他節點之間的關係問題(前面介紹的 Gossip 解決了節點之間的消息交換問題)。當 Cassandra 收到一個讀請求,需要去做的是根據一致性級別聯繫對應的數據副本。為了支持最快速度的讀取請求,Cassandra 選擇單一副本讀取全部數據,然後要求附加數據副本的 hash 值,以此確保讀取的數據是最新的數據並返回。Snitch 的角色是幫助驗證返回最快的副本,這些副本又被用於讀取數據。回到麥當勞餐廳,如果互相都知道了誰有哪些產品,那麼完成訂單配單步驟就不難了。

正是有了 Gossip 協議和 Snitch 機制,才在通信層基本滿足無中心化的設計,當然還有很多其他因素一起參與,這裡不逐一介紹。

訂單處理方式

麥當勞和其他商家一樣,都在追求每日最大訂單處理量,你懂的,更多的訂單意味著更多的利潤。為了加快銷售速度,麥當勞採用了異步處理模式。當你在收銀臺下單後,收營員會進行下單,下單過程結束後你就進入到了製作訂單隊列。有了這個隊列,營業員和製作員之間的串行耦合方式被解耦了,這樣營業員就可以繼續接收訂單,即便製作員手上的訂單已經開始排隊了也沒關係。店裡忙的時候,會動態增加幾個製作員,他們之間本身也處在一個競爭環境下,就好像分佈式環境下會存在多個計算節點一樣。因此,麥當勞的這種製作過程,本質上就是一個分佈式處理過程:訂單隊列屬於中心節點的待運行任務隊列,每個製作員是一個計算節點,無論採用申請還是被分配策略,他們都可以正常幹活。這讓我聯想到了兩階段提交,為什麼麥當勞不採用兩階段提交協議呢?

以上我介紹的所有這些策略都不同於兩階段提交,什麼是兩階段提交?它是分佈式系統架構下的所有節點在進行事務提交時保持一致性而設計的一種算法。

兩階段提交協議一般將事務的提交過程分成了兩個階段:

  • 提交事務請求

  • 執行事務請求

核心是對每個事務都採用先嚐試後提交的處理方式,因此它是一個強一致性的算法。

雖然說兩階段提交算法具有原理簡單、實現方便的優點,但是它也有一些缺點:

  • 同步阻塞:所有參與該事務操作的邏輯都處於阻塞狀態;

  • 單點問題:協調者(營業員、訂單排隊隊列)如果出現問題,整個兩階段提交流程將無法運轉,參與者的資源將會處於鎖定狀態;

  • 數據不一致:協調者向所有的參與者發送 Commit 請求之後,由於局部網絡問題,會出現部分參與者沒有收到 Commit 請求,進一步造成數據不一致現象。麥當勞的例子中,如果有多個隊列排隊時,會存在問題;

  • 太過保守:參與者出現異常時,協調者只能通過其自身的超時機制來判斷是否需要中斷事務,即任意一個節點的失敗都會導致整個事務的失敗。

兩階段提交需要依賴於不同的準備和執行步驟。在麥當勞這個例子裡面,如果採用兩階段提交,顧客需要在收銀臺等待食物製作完畢,他得自己拿著錢。然後,錢、收據、食物會做一次一手交錢,一手交貨的交換。營業員和顧客在交易完成前都不能離開。兩階段提交可以讓生活更加簡單,但是也會傷害到消息的自由流通,因為本質上它是基於各方之間的有狀態交易資源的異步動作。

員工角色拆分

如果你去小型的快餐店,你會發現只有 1 名員工,他既要負責下單、收銀,也要負責事物製作、打包、售後,這樣自然速度就慢了。麥當勞把員工分為了幾個不同的角色,這些角色在幹活的時候互相不干擾,通過通信機制(訂單系統信息投影屏幕)方式協同工作,這樣可以最大化生產效率。即使出現意外情況,例如某位員工中暑了,店長可以立即協調一名員工補上他的位置,而不需要和其他角色的員工產生交互成本。

這樣的設計方式和微服務架構比較類似,下面這張圖是一個典型的傳統單體型服務架構模式:

從金拱門餐廳聯想到的分佈式系統設計思維

單體型架構比較適合小項目,缺點比較明顯:

  • 開發效率低:所有的開發在一個項目改代碼,遞交代碼相互等待,代碼衝突不斷

  • 代碼維護難:代碼功能耦合在一起,新人不知道何從下手

  • 部署不靈活:構建時間長,任何小修改必須重新構建整個項目,這個過程往往很長

  • 穩定性不高:一個微不足道的小問題,可以導致整個應用掛掉

  • 擴展性不夠:無法滿足高併發情況下的業務需求

微服務是指開發一個單個小型的但有業務功能的服務,每個服務都有自己的處理和輕量通訊機制,可以部署在單個或多個服務器上。微服務也指一種種鬆耦合的、有一定的有界上下文的面向服務架構。也就是說,如果每個服務都要同時修改,那麼它們就不是微服務,因為它們緊耦合在一起;如果你需要掌握一個服務太多的上下文場景使用條件,那麼它就是一個有上下文邊界的服務。

經過微服務架構的拆分,上面描述的單體型架構成了下面這張微服務架構圖:

從金拱門餐廳聯想到的分佈式系統設計思維

多個任務接收

你會發現,為了加快訂單接收速度,同時又要在有限的營業窗口的限制的前提下加快速度,怎麼辦?麥當勞引入了自動訂餐機器,這樣就允許客戶通過觸摸式接觸屏點選自己需要的食品,可以任意組合,然後點擊下單,通過“微信、支付寶”等手機支付方式正式下單,接著你的訂單就進入到了統一的任務排隊隊列,這和通過點單員下單是完全一樣的。

由於有了微服務架構的支撐及通信框架的交互設計,點單這個工作變成了一個完全獨立的工作,如圖所示:

從金拱門餐廳聯想到的分佈式系統設計思維

作者|周明耀

編輯|小智

其實在生活中,大到組織架構,小到瑣碎日常,都能學到一些經驗、總結出一些知識。共性往往隱藏在特性之中。你需要做的,可能僅僅只是細心觀察而已?

前幾天中午開會開到 12 點半,錯過了食堂吃飯時間,只能大家一起去麥當勞吃午飯。寫這篇文章,既是有感於麥當勞餐廳的管理模式,也是由於這次就餐在訂餐、取餐過程中發生了一些小事,讓我聯想到了分佈式軟件設計,我在 InfoQ 開設技術專欄之初就說過技術來源於生活細節,那麼今天就來系統性談談由麥當勞餐廳(這裡並不特指麥當勞,我相信也有類似的其他店家,這裡只是以麥當勞作為舉例)所想到的分佈式系統設計思維及問題。

結合現階段的觀察,我有以下觀點:

  • 店長負責制 ->主從模式(Master/Slave);

  • 訂單處理方式 ->兩階段提交;

  • 員工角色拆分 ->微服務設計;

  • 多個任務接收 ->微服務設計之後的服務橫向擴展;

  • 訂單處理過程上屏 ->任務隊列設計;

  • 座位設計模式 ->容器化管理。

我逐一聊聊我的看法,歡迎您留言指教。

店長負責制

每家麥當勞都會有一名當值經理,這位經理負責當前整個店的運營,如果遇到某個崗位繁忙的情況,那麼他會臨時調動其他相對空閒的員工支援,那麼這就是典型的 Master/Slave 架構。

從金拱門餐廳聯想到的分佈式系統設計思維

我們來看看較為典型的 Matser/Slave 架構設計。以 HBase 為例,看看 HBase 的 RegionServer 設計方式。在 HBase 內部,所有的用戶數據以及元數據的請求,在經過 Region 的定位,最終會落在 RegionServer 上,並由 RegionServer 實現數據的讀寫操作。RegionServer 是 HBase 集群運行在每個工作節點上的服務。它是整個 HBase 系統的關鍵所在,一方面它維護了 Region 的狀態,提供了對於 Region 的管理和服務;另一方面,它與 HMaster 交互,上傳 Region 的負載信息上傳,參與 HMaster 的分佈式協調管理。

從金拱門餐廳聯想到的分佈式系統設計思維

HRegionServer 與 HMaster 以及 Client 之間採用 RPC 協議進行通信。HRegionServer 向 HMaster 定期彙報節點的負載狀況,包括 RS 內存使用狀態、在線狀態的 Region 等信息。在該過程中 HRegionServer 扮演了 RPC 客戶端的角色,而 HMaster 扮演了 RPC 服務器端的角色。HRegionServer 內置的 RpcServer 實現了數據更新、讀取、刪除的操作,以及 Region 涉及到 Flush、Compaction、Open、Close、Load 文件等功能性操作。

Region 是 HBase 數據存儲和管理的基本單位。HBase 使用 RowKey 將表水平切割成多個 HRegion,從 HMaster 的角度,每個 HRegion 都紀錄了它的 StartKey 和 EndKey(第一個 HRegion 的 StartKey 為空,最後一個 HRegion 的 EndKey 為空),由於 RowKey 是排序的,因而 Client 可以通過 HMaster 快速的定位每個 RowKey 在哪個 HRegion 中。HRegion 由 HMaster 分配到相應的 HRegionServer 中,然後由 HRegionServer 負責 HRegion 的啟動和管理,和 Client 的通信,負責數據的讀 (使用 HDFS)。每個 HRegionServer 可以同時管理 1000 個左右的 HRegion。

如果我們需要把固定店長負責制改為全員負責制,有沒有類似案例?有,Apache Cassandra 就沒有固定的中心節點(無中心化設計),只有協調者節點(理論上所有節點都可以作為協調者,每次請求有且僅有一個),那麼它是怎麼做到的呢?

首先我們來看看 Gossip 協議。Cassandra 集群中的節點沒有主次之分,通過 Gossip 協議,可以知道集群中有哪些節點,以及這些節點的狀態如何等等。每一條 Gossip 消息上都有一個版本號,節點可以對接收到的消息進行版本比對,從而得知哪些消息是我需要更新的,哪些消息是我有而別人沒有的,然後互相傾聽吐槽,確保二者得到的信息相同,這很像現實生活中的八卦,一傳十,十傳百,最後盡人皆知。在 Cassandra 啟動時,會啟動 Gossip 服務,Gossip 服務啟動後會啟動一個任務 GossipTask,每秒鐘運行一次,這個任務會週期性與其他節點進行通信。回到麥當勞餐廳,是不是每個員工之間也需要有對講機,實時互相告知目前自己的工作狀態和訂單信息,這樣才能做到無固定店長管理制?

既然 Cassandra 是無中心化的,那麼如何來計算各個節點上存儲的數據之間的差異呢?如何判斷自己申請的那一份數據就是最新版本的?這裡我要引出 Snitch(告密者)機制了。

現實生活中的“告密者”會向別人告密誰有需要尋找的東西,Snitch 機制在 Cassandra 集群裡也起到了這樣的作用。Snitch 機制的功能是決定集群每個節點之間的相關性(值),這個值被用於決定從哪個節點讀取或寫入數據。此外,由於 Snitch 機制收集網絡拓撲內的信息,這樣 Cassandra 可以有效路由各類外部請求。Snitch 機制解決了節點與集群內其他節點之間的關係問題(前面介紹的 Gossip 解決了節點之間的消息交換問題)。當 Cassandra 收到一個讀請求,需要去做的是根據一致性級別聯繫對應的數據副本。為了支持最快速度的讀取請求,Cassandra 選擇單一副本讀取全部數據,然後要求附加數據副本的 hash 值,以此確保讀取的數據是最新的數據並返回。Snitch 的角色是幫助驗證返回最快的副本,這些副本又被用於讀取數據。回到麥當勞餐廳,如果互相都知道了誰有哪些產品,那麼完成訂單配單步驟就不難了。

正是有了 Gossip 協議和 Snitch 機制,才在通信層基本滿足無中心化的設計,當然還有很多其他因素一起參與,這裡不逐一介紹。

訂單處理方式

麥當勞和其他商家一樣,都在追求每日最大訂單處理量,你懂的,更多的訂單意味著更多的利潤。為了加快銷售速度,麥當勞採用了異步處理模式。當你在收銀臺下單後,收營員會進行下單,下單過程結束後你就進入到了製作訂單隊列。有了這個隊列,營業員和製作員之間的串行耦合方式被解耦了,這樣營業員就可以繼續接收訂單,即便製作員手上的訂單已經開始排隊了也沒關係。店裡忙的時候,會動態增加幾個製作員,他們之間本身也處在一個競爭環境下,就好像分佈式環境下會存在多個計算節點一樣。因此,麥當勞的這種製作過程,本質上就是一個分佈式處理過程:訂單隊列屬於中心節點的待運行任務隊列,每個製作員是一個計算節點,無論採用申請還是被分配策略,他們都可以正常幹活。這讓我聯想到了兩階段提交,為什麼麥當勞不採用兩階段提交協議呢?

以上我介紹的所有這些策略都不同於兩階段提交,什麼是兩階段提交?它是分佈式系統架構下的所有節點在進行事務提交時保持一致性而設計的一種算法。

兩階段提交協議一般將事務的提交過程分成了兩個階段:

  • 提交事務請求

  • 執行事務請求

核心是對每個事務都採用先嚐試後提交的處理方式,因此它是一個強一致性的算法。

雖然說兩階段提交算法具有原理簡單、實現方便的優點,但是它也有一些缺點:

  • 同步阻塞:所有參與該事務操作的邏輯都處於阻塞狀態;

  • 單點問題:協調者(營業員、訂單排隊隊列)如果出現問題,整個兩階段提交流程將無法運轉,參與者的資源將會處於鎖定狀態;

  • 數據不一致:協調者向所有的參與者發送 Commit 請求之後,由於局部網絡問題,會出現部分參與者沒有收到 Commit 請求,進一步造成數據不一致現象。麥當勞的例子中,如果有多個隊列排隊時,會存在問題;

  • 太過保守:參與者出現異常時,協調者只能通過其自身的超時機制來判斷是否需要中斷事務,即任意一個節點的失敗都會導致整個事務的失敗。

兩階段提交需要依賴於不同的準備和執行步驟。在麥當勞這個例子裡面,如果採用兩階段提交,顧客需要在收銀臺等待食物製作完畢,他得自己拿著錢。然後,錢、收據、食物會做一次一手交錢,一手交貨的交換。營業員和顧客在交易完成前都不能離開。兩階段提交可以讓生活更加簡單,但是也會傷害到消息的自由流通,因為本質上它是基於各方之間的有狀態交易資源的異步動作。

員工角色拆分

如果你去小型的快餐店,你會發現只有 1 名員工,他既要負責下單、收銀,也要負責事物製作、打包、售後,這樣自然速度就慢了。麥當勞把員工分為了幾個不同的角色,這些角色在幹活的時候互相不干擾,通過通信機制(訂單系統信息投影屏幕)方式協同工作,這樣可以最大化生產效率。即使出現意外情況,例如某位員工中暑了,店長可以立即協調一名員工補上他的位置,而不需要和其他角色的員工產生交互成本。

這樣的設計方式和微服務架構比較類似,下面這張圖是一個典型的傳統單體型服務架構模式:

從金拱門餐廳聯想到的分佈式系統設計思維

單體型架構比較適合小項目,缺點比較明顯:

  • 開發效率低:所有的開發在一個項目改代碼,遞交代碼相互等待,代碼衝突不斷

  • 代碼維護難:代碼功能耦合在一起,新人不知道何從下手

  • 部署不靈活:構建時間長,任何小修改必須重新構建整個項目,這個過程往往很長

  • 穩定性不高:一個微不足道的小問題,可以導致整個應用掛掉

  • 擴展性不夠:無法滿足高併發情況下的業務需求

微服務是指開發一個單個小型的但有業務功能的服務,每個服務都有自己的處理和輕量通訊機制,可以部署在單個或多個服務器上。微服務也指一種種鬆耦合的、有一定的有界上下文的面向服務架構。也就是說,如果每個服務都要同時修改,那麼它們就不是微服務,因為它們緊耦合在一起;如果你需要掌握一個服務太多的上下文場景使用條件,那麼它就是一個有上下文邊界的服務。

經過微服務架構的拆分,上面描述的單體型架構成了下面這張微服務架構圖:

從金拱門餐廳聯想到的分佈式系統設計思維

多個任務接收

你會發現,為了加快訂單接收速度,同時又要在有限的營業窗口的限制的前提下加快速度,怎麼辦?麥當勞引入了自動訂餐機器,這樣就允許客戶通過觸摸式接觸屏點選自己需要的食品,可以任意組合,然後點擊下單,通過“微信、支付寶”等手機支付方式正式下單,接著你的訂單就進入到了統一的任務排隊隊列,這和通過點單員下單是完全一樣的。

由於有了微服務架構的支撐及通信框架的交互設計,點單這個工作變成了一個完全獨立的工作,如圖所示:

從金拱門餐廳聯想到的分佈式系統設計思維

橫向可擴展能力是微服務化設計之後很容易實現的功能,可以通過服務發現方式實現各個進城之間的狀態信息交互,這裡就不多介紹了。

訂單處理過程上屏

訂單下單後,我們的訂單就進入到了麥當勞的任務排隊隊列,他們家的排隊隊列分為兩個,分別是待完成隊列、已完成隊列,如圖所示:

從金拱門餐廳聯想到的分佈式系統設計思維

作者|周明耀

編輯|小智

其實在生活中,大到組織架構,小到瑣碎日常,都能學到一些經驗、總結出一些知識。共性往往隱藏在特性之中。你需要做的,可能僅僅只是細心觀察而已?

前幾天中午開會開到 12 點半,錯過了食堂吃飯時間,只能大家一起去麥當勞吃午飯。寫這篇文章,既是有感於麥當勞餐廳的管理模式,也是由於這次就餐在訂餐、取餐過程中發生了一些小事,讓我聯想到了分佈式軟件設計,我在 InfoQ 開設技術專欄之初就說過技術來源於生活細節,那麼今天就來系統性談談由麥當勞餐廳(這裡並不特指麥當勞,我相信也有類似的其他店家,這裡只是以麥當勞作為舉例)所想到的分佈式系統設計思維及問題。

結合現階段的觀察,我有以下觀點:

  • 店長負責制 ->主從模式(Master/Slave);

  • 訂單處理方式 ->兩階段提交;

  • 員工角色拆分 ->微服務設計;

  • 多個任務接收 ->微服務設計之後的服務橫向擴展;

  • 訂單處理過程上屏 ->任務隊列設計;

  • 座位設計模式 ->容器化管理。

我逐一聊聊我的看法,歡迎您留言指教。

店長負責制

每家麥當勞都會有一名當值經理,這位經理負責當前整個店的運營,如果遇到某個崗位繁忙的情況,那麼他會臨時調動其他相對空閒的員工支援,那麼這就是典型的 Master/Slave 架構。

從金拱門餐廳聯想到的分佈式系統設計思維

我們來看看較為典型的 Matser/Slave 架構設計。以 HBase 為例,看看 HBase 的 RegionServer 設計方式。在 HBase 內部,所有的用戶數據以及元數據的請求,在經過 Region 的定位,最終會落在 RegionServer 上,並由 RegionServer 實現數據的讀寫操作。RegionServer 是 HBase 集群運行在每個工作節點上的服務。它是整個 HBase 系統的關鍵所在,一方面它維護了 Region 的狀態,提供了對於 Region 的管理和服務;另一方面,它與 HMaster 交互,上傳 Region 的負載信息上傳,參與 HMaster 的分佈式協調管理。

從金拱門餐廳聯想到的分佈式系統設計思維

HRegionServer 與 HMaster 以及 Client 之間採用 RPC 協議進行通信。HRegionServer 向 HMaster 定期彙報節點的負載狀況,包括 RS 內存使用狀態、在線狀態的 Region 等信息。在該過程中 HRegionServer 扮演了 RPC 客戶端的角色,而 HMaster 扮演了 RPC 服務器端的角色。HRegionServer 內置的 RpcServer 實現了數據更新、讀取、刪除的操作,以及 Region 涉及到 Flush、Compaction、Open、Close、Load 文件等功能性操作。

Region 是 HBase 數據存儲和管理的基本單位。HBase 使用 RowKey 將表水平切割成多個 HRegion,從 HMaster 的角度,每個 HRegion 都紀錄了它的 StartKey 和 EndKey(第一個 HRegion 的 StartKey 為空,最後一個 HRegion 的 EndKey 為空),由於 RowKey 是排序的,因而 Client 可以通過 HMaster 快速的定位每個 RowKey 在哪個 HRegion 中。HRegion 由 HMaster 分配到相應的 HRegionServer 中,然後由 HRegionServer 負責 HRegion 的啟動和管理,和 Client 的通信,負責數據的讀 (使用 HDFS)。每個 HRegionServer 可以同時管理 1000 個左右的 HRegion。

如果我們需要把固定店長負責制改為全員負責制,有沒有類似案例?有,Apache Cassandra 就沒有固定的中心節點(無中心化設計),只有協調者節點(理論上所有節點都可以作為協調者,每次請求有且僅有一個),那麼它是怎麼做到的呢?

首先我們來看看 Gossip 協議。Cassandra 集群中的節點沒有主次之分,通過 Gossip 協議,可以知道集群中有哪些節點,以及這些節點的狀態如何等等。每一條 Gossip 消息上都有一個版本號,節點可以對接收到的消息進行版本比對,從而得知哪些消息是我需要更新的,哪些消息是我有而別人沒有的,然後互相傾聽吐槽,確保二者得到的信息相同,這很像現實生活中的八卦,一傳十,十傳百,最後盡人皆知。在 Cassandra 啟動時,會啟動 Gossip 服務,Gossip 服務啟動後會啟動一個任務 GossipTask,每秒鐘運行一次,這個任務會週期性與其他節點進行通信。回到麥當勞餐廳,是不是每個員工之間也需要有對講機,實時互相告知目前自己的工作狀態和訂單信息,這樣才能做到無固定店長管理制?

既然 Cassandra 是無中心化的,那麼如何來計算各個節點上存儲的數據之間的差異呢?如何判斷自己申請的那一份數據就是最新版本的?這裡我要引出 Snitch(告密者)機制了。

現實生活中的“告密者”會向別人告密誰有需要尋找的東西,Snitch 機制在 Cassandra 集群裡也起到了這樣的作用。Snitch 機制的功能是決定集群每個節點之間的相關性(值),這個值被用於決定從哪個節點讀取或寫入數據。此外,由於 Snitch 機制收集網絡拓撲內的信息,這樣 Cassandra 可以有效路由各類外部請求。Snitch 機制解決了節點與集群內其他節點之間的關係問題(前面介紹的 Gossip 解決了節點之間的消息交換問題)。當 Cassandra 收到一個讀請求,需要去做的是根據一致性級別聯繫對應的數據副本。為了支持最快速度的讀取請求,Cassandra 選擇單一副本讀取全部數據,然後要求附加數據副本的 hash 值,以此確保讀取的數據是最新的數據並返回。Snitch 的角色是幫助驗證返回最快的副本,這些副本又被用於讀取數據。回到麥當勞餐廳,如果互相都知道了誰有哪些產品,那麼完成訂單配單步驟就不難了。

正是有了 Gossip 協議和 Snitch 機制,才在通信層基本滿足無中心化的設計,當然還有很多其他因素一起參與,這裡不逐一介紹。

訂單處理方式

麥當勞和其他商家一樣,都在追求每日最大訂單處理量,你懂的,更多的訂單意味著更多的利潤。為了加快銷售速度,麥當勞採用了異步處理模式。當你在收銀臺下單後,收營員會進行下單,下單過程結束後你就進入到了製作訂單隊列。有了這個隊列,營業員和製作員之間的串行耦合方式被解耦了,這樣營業員就可以繼續接收訂單,即便製作員手上的訂單已經開始排隊了也沒關係。店裡忙的時候,會動態增加幾個製作員,他們之間本身也處在一個競爭環境下,就好像分佈式環境下會存在多個計算節點一樣。因此,麥當勞的這種製作過程,本質上就是一個分佈式處理過程:訂單隊列屬於中心節點的待運行任務隊列,每個製作員是一個計算節點,無論採用申請還是被分配策略,他們都可以正常幹活。這讓我聯想到了兩階段提交,為什麼麥當勞不採用兩階段提交協議呢?

以上我介紹的所有這些策略都不同於兩階段提交,什麼是兩階段提交?它是分佈式系統架構下的所有節點在進行事務提交時保持一致性而設計的一種算法。

兩階段提交協議一般將事務的提交過程分成了兩個階段:

  • 提交事務請求

  • 執行事務請求

核心是對每個事務都採用先嚐試後提交的處理方式,因此它是一個強一致性的算法。

雖然說兩階段提交算法具有原理簡單、實現方便的優點,但是它也有一些缺點:

  • 同步阻塞:所有參與該事務操作的邏輯都處於阻塞狀態;

  • 單點問題:協調者(營業員、訂單排隊隊列)如果出現問題,整個兩階段提交流程將無法運轉,參與者的資源將會處於鎖定狀態;

  • 數據不一致:協調者向所有的參與者發送 Commit 請求之後,由於局部網絡問題,會出現部分參與者沒有收到 Commit 請求,進一步造成數據不一致現象。麥當勞的例子中,如果有多個隊列排隊時,會存在問題;

  • 太過保守:參與者出現異常時,協調者只能通過其自身的超時機制來判斷是否需要中斷事務,即任意一個節點的失敗都會導致整個事務的失敗。

兩階段提交需要依賴於不同的準備和執行步驟。在麥當勞這個例子裡面,如果採用兩階段提交,顧客需要在收銀臺等待食物製作完畢,他得自己拿著錢。然後,錢、收據、食物會做一次一手交錢,一手交貨的交換。營業員和顧客在交易完成前都不能離開。兩階段提交可以讓生活更加簡單,但是也會傷害到消息的自由流通,因為本質上它是基於各方之間的有狀態交易資源的異步動作。

員工角色拆分

如果你去小型的快餐店,你會發現只有 1 名員工,他既要負責下單、收銀,也要負責事物製作、打包、售後,這樣自然速度就慢了。麥當勞把員工分為了幾個不同的角色,這些角色在幹活的時候互相不干擾,通過通信機制(訂單系統信息投影屏幕)方式協同工作,這樣可以最大化生產效率。即使出現意外情況,例如某位員工中暑了,店長可以立即協調一名員工補上他的位置,而不需要和其他角色的員工產生交互成本。

這樣的設計方式和微服務架構比較類似,下面這張圖是一個典型的傳統單體型服務架構模式:

從金拱門餐廳聯想到的分佈式系統設計思維

單體型架構比較適合小項目,缺點比較明顯:

  • 開發效率低:所有的開發在一個項目改代碼,遞交代碼相互等待,代碼衝突不斷

  • 代碼維護難:代碼功能耦合在一起,新人不知道何從下手

  • 部署不靈活:構建時間長,任何小修改必須重新構建整個項目,這個過程往往很長

  • 穩定性不高:一個微不足道的小問題,可以導致整個應用掛掉

  • 擴展性不夠:無法滿足高併發情況下的業務需求

微服務是指開發一個單個小型的但有業務功能的服務,每個服務都有自己的處理和輕量通訊機制,可以部署在單個或多個服務器上。微服務也指一種種鬆耦合的、有一定的有界上下文的面向服務架構。也就是說,如果每個服務都要同時修改,那麼它們就不是微服務,因為它們緊耦合在一起;如果你需要掌握一個服務太多的上下文場景使用條件,那麼它就是一個有上下文邊界的服務。

經過微服務架構的拆分,上面描述的單體型架構成了下面這張微服務架構圖:

從金拱門餐廳聯想到的分佈式系統設計思維

多個任務接收

你會發現,為了加快訂單接收速度,同時又要在有限的營業窗口的限制的前提下加快速度,怎麼辦?麥當勞引入了自動訂餐機器,這樣就允許客戶通過觸摸式接觸屏點選自己需要的食品,可以任意組合,然後點擊下單,通過“微信、支付寶”等手機支付方式正式下單,接著你的訂單就進入到了統一的任務排隊隊列,這和通過點單員下單是完全一樣的。

由於有了微服務架構的支撐及通信框架的交互設計,點單這個工作變成了一個完全獨立的工作,如圖所示:

從金拱門餐廳聯想到的分佈式系統設計思維

橫向可擴展能力是微服務化設計之後很容易實現的功能,可以通過服務發現方式實現各個進城之間的狀態信息交互,這裡就不多介紹了。

訂單處理過程上屏

訂單下單後,我們的訂單就進入到了麥當勞的任務排隊隊列,他們家的排隊隊列分為兩個,分別是待完成隊列、已完成隊列,如圖所示:

從金拱門餐廳聯想到的分佈式系統設計思維

這種隊列設計是分佈式計算應用領域常用的設計和處理方案,看似簡單,其實相當複雜,內容處理過程容易出現很多問題,我這裡根據那天等待取餐時發生的幾件小事,注意聊聊出現的幾個異常情況:

異常數據干擾

我們在排隊取餐,由於取餐是通過一名員工人工喊話的,她既要喊人取餐,又要處理各種售後請求(例如拿番茄醬、詢問訂單情況等等),這樣她很容易被幹擾,進而出現手忙腳亂的情況。聯想到分佈式環境下,如果我們不對任務的信息進行校驗,那麼很容易混入異常任務(可能是任務信息被截斷了,也可能是非法攻擊),對於核心服務的保護是必不可少的,分佈式環境下無論是否存在中心服務,我們都需要仔細思考各種異常保護,所以貌似流程、業務簡單,其實並不是那麼容易的。

排在前面的訂單遲遲沒有做完

如上面這張圖所示,理想情況下是按照訂單號逐一完成,這樣也會讓所有顧客都滿意,先來先得嘛。但是實際情況是不是這樣呢?我一位同事等了很久,原因是由於他的訂單中有一樣食物遲遲沒有啟動製作過程,這是因為調度服務內部經過性價比評估,發現只有他存在這個需求,所以一直在等待其他顧客點選相同產品後再一起製作,現在先把那些很多顧客都在等待的產品做出來,避免更多的人等待。回到分佈式技術領域,對於這種情況我們其實也在一直遇到,比如先下派資源需求大的任務,還是先下派小的?這就相當於先讓大塊的石頭填滿盒子,然後讓小塊的石頭塞入細小的縫隙。所有設計都是需要根據實際業務情況和實際測試得出的,而不是拍腦袋。

訂單完成前已經發貨

我的那位同事,他等了很久還沒有拿到事物,所以有點怒了。這時候負責取餐的員工隨意給了他一份食品,但是沒有標註正在排隊的任務為“已完成”狀態,相當於人為多複製了一個任務,一個任務變成兩個任務了,數據庫內出現了髒數據。等真正的任務完成時,由於無人取餐,導致陷入了死循環,負責取餐的員工一直在喊“1104 號顧客請取餐”,但是無人應答。

對於這類問題,在分佈式環境下也是很容易發生的。例如,計算節點在執行任務過程中離線了,一直沒有上報任務執行狀態,中心節點認為它離線了,就把運行在它上面的任務標註為“未調度任務”,重新下派,這時候如果離線的計算節點又恢復了正常,那麼就會有兩個相同的任務同時運行。這種異常其實很難避免,它本身是分佈式環境下任務容錯的一種設計方案,需要整個數據流閉環內協同處理,特別是任務的下游系統儘可能參與。做好分佈式系統,真不容易。

座位設計模式

由於快餐的就餐時間一般都較短,所以顧客流動速度很快,看著每個座位上的顧客頻繁更換,讓我聯想到了 Kubernetes 管理容器的設計模式。Kubernetes 知道整個餐廳的可用資源(座位)數目及使用情況,每個座位上的顧客就是臨時啟動的一個容器,用完就銷燬,資源釋放後,其他顧客可以接著資源。

來看看 Kubernetes 的相關概念。Kubernetes 以 RESTFul 形式開放接口,用戶可操作的 REST 對象有三個:

  • pod:是 Kubernetes 最基本的部署調度單元,可以包含 container,邏輯上表示某種應用的一個實例。比如一個 web 站點應用由前端、後端及數據庫構建而成,這三個組件將運行在各自的容器中,那麼我們可以創建包含三個 container 的 pod。

  • service:是 pod 的路由代理抽象,用於解決 pod 之間的服務發現問題。因為 pod 的運行狀態可動態變化 (比如切換機器了、縮容過程中被終止了等),所以訪問端不能以寫死 IP 的方式去訪問該 pod 提供的服務。service 的引入旨在保證 pod 的動態變化對訪問端透明,訪問端只需要知道 service 的地址,由 service 來提供代理。

  • replicationController:是 pod 的複製抽象,用於解決 pod 的擴容縮容問題。通常,分佈式應用為了性能或高可用性的考慮,需要複製多份資源,並且根據負載情況動態伸縮。通過 replicationController,我們可以指定一個應用需要幾份複製,Kubernetes 將為每份複製創建一個 pod,並且保證實際運行 pod 數量總是與該複製數量相等 (例如,當前某個 pod 宕機時,自動創建新的 pod 來替換)。

可以看到,service 和 replicationController 只是建立在 pod 之上的抽象,最終是要作用於 pod 的,那麼它們如何跟 pod 聯繫起來呢?這就要引入 label 的概念:label 其實很好理解,就是為 pod 加上可用於搜索或關聯的一組 key/value 標籤,而 service 和 replicationController 正是通過 label 來與 pod 關聯的。如下圖所示,有三個 pod 都有 label 為"app=backend",創建 service 和 replicationController 時可以指定同樣的 label:"app=backend",再通過 label selector 機制,就將它們與這三個 pod 關聯起來了。例如,當有其他 frontend pod 訪問該 service 時,自動會轉發到其中的一個 backend pod。

從金拱門餐廳聯想到的分佈式系統設計思維

作者|周明耀

編輯|小智

其實在生活中,大到組織架構,小到瑣碎日常,都能學到一些經驗、總結出一些知識。共性往往隱藏在特性之中。你需要做的,可能僅僅只是細心觀察而已?

前幾天中午開會開到 12 點半,錯過了食堂吃飯時間,只能大家一起去麥當勞吃午飯。寫這篇文章,既是有感於麥當勞餐廳的管理模式,也是由於這次就餐在訂餐、取餐過程中發生了一些小事,讓我聯想到了分佈式軟件設計,我在 InfoQ 開設技術專欄之初就說過技術來源於生活細節,那麼今天就來系統性談談由麥當勞餐廳(這裡並不特指麥當勞,我相信也有類似的其他店家,這裡只是以麥當勞作為舉例)所想到的分佈式系統設計思維及問題。

結合現階段的觀察,我有以下觀點:

  • 店長負責制 ->主從模式(Master/Slave);

  • 訂單處理方式 ->兩階段提交;

  • 員工角色拆分 ->微服務設計;

  • 多個任務接收 ->微服務設計之後的服務橫向擴展;

  • 訂單處理過程上屏 ->任務隊列設計;

  • 座位設計模式 ->容器化管理。

我逐一聊聊我的看法,歡迎您留言指教。

店長負責制

每家麥當勞都會有一名當值經理,這位經理負責當前整個店的運營,如果遇到某個崗位繁忙的情況,那麼他會臨時調動其他相對空閒的員工支援,那麼這就是典型的 Master/Slave 架構。

從金拱門餐廳聯想到的分佈式系統設計思維

我們來看看較為典型的 Matser/Slave 架構設計。以 HBase 為例,看看 HBase 的 RegionServer 設計方式。在 HBase 內部,所有的用戶數據以及元數據的請求,在經過 Region 的定位,最終會落在 RegionServer 上,並由 RegionServer 實現數據的讀寫操作。RegionServer 是 HBase 集群運行在每個工作節點上的服務。它是整個 HBase 系統的關鍵所在,一方面它維護了 Region 的狀態,提供了對於 Region 的管理和服務;另一方面,它與 HMaster 交互,上傳 Region 的負載信息上傳,參與 HMaster 的分佈式協調管理。

從金拱門餐廳聯想到的分佈式系統設計思維

HRegionServer 與 HMaster 以及 Client 之間採用 RPC 協議進行通信。HRegionServer 向 HMaster 定期彙報節點的負載狀況,包括 RS 內存使用狀態、在線狀態的 Region 等信息。在該過程中 HRegionServer 扮演了 RPC 客戶端的角色,而 HMaster 扮演了 RPC 服務器端的角色。HRegionServer 內置的 RpcServer 實現了數據更新、讀取、刪除的操作,以及 Region 涉及到 Flush、Compaction、Open、Close、Load 文件等功能性操作。

Region 是 HBase 數據存儲和管理的基本單位。HBase 使用 RowKey 將表水平切割成多個 HRegion,從 HMaster 的角度,每個 HRegion 都紀錄了它的 StartKey 和 EndKey(第一個 HRegion 的 StartKey 為空,最後一個 HRegion 的 EndKey 為空),由於 RowKey 是排序的,因而 Client 可以通過 HMaster 快速的定位每個 RowKey 在哪個 HRegion 中。HRegion 由 HMaster 分配到相應的 HRegionServer 中,然後由 HRegionServer 負責 HRegion 的啟動和管理,和 Client 的通信,負責數據的讀 (使用 HDFS)。每個 HRegionServer 可以同時管理 1000 個左右的 HRegion。

如果我們需要把固定店長負責制改為全員負責制,有沒有類似案例?有,Apache Cassandra 就沒有固定的中心節點(無中心化設計),只有協調者節點(理論上所有節點都可以作為協調者,每次請求有且僅有一個),那麼它是怎麼做到的呢?

首先我們來看看 Gossip 協議。Cassandra 集群中的節點沒有主次之分,通過 Gossip 協議,可以知道集群中有哪些節點,以及這些節點的狀態如何等等。每一條 Gossip 消息上都有一個版本號,節點可以對接收到的消息進行版本比對,從而得知哪些消息是我需要更新的,哪些消息是我有而別人沒有的,然後互相傾聽吐槽,確保二者得到的信息相同,這很像現實生活中的八卦,一傳十,十傳百,最後盡人皆知。在 Cassandra 啟動時,會啟動 Gossip 服務,Gossip 服務啟動後會啟動一個任務 GossipTask,每秒鐘運行一次,這個任務會週期性與其他節點進行通信。回到麥當勞餐廳,是不是每個員工之間也需要有對講機,實時互相告知目前自己的工作狀態和訂單信息,這樣才能做到無固定店長管理制?

既然 Cassandra 是無中心化的,那麼如何來計算各個節點上存儲的數據之間的差異呢?如何判斷自己申請的那一份數據就是最新版本的?這裡我要引出 Snitch(告密者)機制了。

現實生活中的“告密者”會向別人告密誰有需要尋找的東西,Snitch 機制在 Cassandra 集群裡也起到了這樣的作用。Snitch 機制的功能是決定集群每個節點之間的相關性(值),這個值被用於決定從哪個節點讀取或寫入數據。此外,由於 Snitch 機制收集網絡拓撲內的信息,這樣 Cassandra 可以有效路由各類外部請求。Snitch 機制解決了節點與集群內其他節點之間的關係問題(前面介紹的 Gossip 解決了節點之間的消息交換問題)。當 Cassandra 收到一個讀請求,需要去做的是根據一致性級別聯繫對應的數據副本。為了支持最快速度的讀取請求,Cassandra 選擇單一副本讀取全部數據,然後要求附加數據副本的 hash 值,以此確保讀取的數據是最新的數據並返回。Snitch 的角色是幫助驗證返回最快的副本,這些副本又被用於讀取數據。回到麥當勞餐廳,如果互相都知道了誰有哪些產品,那麼完成訂單配單步驟就不難了。

正是有了 Gossip 協議和 Snitch 機制,才在通信層基本滿足無中心化的設計,當然還有很多其他因素一起參與,這裡不逐一介紹。

訂單處理方式

麥當勞和其他商家一樣,都在追求每日最大訂單處理量,你懂的,更多的訂單意味著更多的利潤。為了加快銷售速度,麥當勞採用了異步處理模式。當你在收銀臺下單後,收營員會進行下單,下單過程結束後你就進入到了製作訂單隊列。有了這個隊列,營業員和製作員之間的串行耦合方式被解耦了,這樣營業員就可以繼續接收訂單,即便製作員手上的訂單已經開始排隊了也沒關係。店裡忙的時候,會動態增加幾個製作員,他們之間本身也處在一個競爭環境下,就好像分佈式環境下會存在多個計算節點一樣。因此,麥當勞的這種製作過程,本質上就是一個分佈式處理過程:訂單隊列屬於中心節點的待運行任務隊列,每個製作員是一個計算節點,無論採用申請還是被分配策略,他們都可以正常幹活。這讓我聯想到了兩階段提交,為什麼麥當勞不採用兩階段提交協議呢?

以上我介紹的所有這些策略都不同於兩階段提交,什麼是兩階段提交?它是分佈式系統架構下的所有節點在進行事務提交時保持一致性而設計的一種算法。

兩階段提交協議一般將事務的提交過程分成了兩個階段:

  • 提交事務請求

  • 執行事務請求

核心是對每個事務都採用先嚐試後提交的處理方式,因此它是一個強一致性的算法。

雖然說兩階段提交算法具有原理簡單、實現方便的優點,但是它也有一些缺點:

  • 同步阻塞:所有參與該事務操作的邏輯都處於阻塞狀態;

  • 單點問題:協調者(營業員、訂單排隊隊列)如果出現問題,整個兩階段提交流程將無法運轉,參與者的資源將會處於鎖定狀態;

  • 數據不一致:協調者向所有的參與者發送 Commit 請求之後,由於局部網絡問題,會出現部分參與者沒有收到 Commit 請求,進一步造成數據不一致現象。麥當勞的例子中,如果有多個隊列排隊時,會存在問題;

  • 太過保守:參與者出現異常時,協調者只能通過其自身的超時機制來判斷是否需要中斷事務,即任意一個節點的失敗都會導致整個事務的失敗。

兩階段提交需要依賴於不同的準備和執行步驟。在麥當勞這個例子裡面,如果採用兩階段提交,顧客需要在收銀臺等待食物製作完畢,他得自己拿著錢。然後,錢、收據、食物會做一次一手交錢,一手交貨的交換。營業員和顧客在交易完成前都不能離開。兩階段提交可以讓生活更加簡單,但是也會傷害到消息的自由流通,因為本質上它是基於各方之間的有狀態交易資源的異步動作。

員工角色拆分

如果你去小型的快餐店,你會發現只有 1 名員工,他既要負責下單、收銀,也要負責事物製作、打包、售後,這樣自然速度就慢了。麥當勞把員工分為了幾個不同的角色,這些角色在幹活的時候互相不干擾,通過通信機制(訂單系統信息投影屏幕)方式協同工作,這樣可以最大化生產效率。即使出現意外情況,例如某位員工中暑了,店長可以立即協調一名員工補上他的位置,而不需要和其他角色的員工產生交互成本。

這樣的設計方式和微服務架構比較類似,下面這張圖是一個典型的傳統單體型服務架構模式:

從金拱門餐廳聯想到的分佈式系統設計思維

單體型架構比較適合小項目,缺點比較明顯:

  • 開發效率低:所有的開發在一個項目改代碼,遞交代碼相互等待,代碼衝突不斷

  • 代碼維護難:代碼功能耦合在一起,新人不知道何從下手

  • 部署不靈活:構建時間長,任何小修改必須重新構建整個項目,這個過程往往很長

  • 穩定性不高:一個微不足道的小問題,可以導致整個應用掛掉

  • 擴展性不夠:無法滿足高併發情況下的業務需求

微服務是指開發一個單個小型的但有業務功能的服務,每個服務都有自己的處理和輕量通訊機制,可以部署在單個或多個服務器上。微服務也指一種種鬆耦合的、有一定的有界上下文的面向服務架構。也就是說,如果每個服務都要同時修改,那麼它們就不是微服務,因為它們緊耦合在一起;如果你需要掌握一個服務太多的上下文場景使用條件,那麼它就是一個有上下文邊界的服務。

經過微服務架構的拆分,上面描述的單體型架構成了下面這張微服務架構圖:

從金拱門餐廳聯想到的分佈式系統設計思維

多個任務接收

你會發現,為了加快訂單接收速度,同時又要在有限的營業窗口的限制的前提下加快速度,怎麼辦?麥當勞引入了自動訂餐機器,這樣就允許客戶通過觸摸式接觸屏點選自己需要的食品,可以任意組合,然後點擊下單,通過“微信、支付寶”等手機支付方式正式下單,接著你的訂單就進入到了統一的任務排隊隊列,這和通過點單員下單是完全一樣的。

由於有了微服務架構的支撐及通信框架的交互設計,點單這個工作變成了一個完全獨立的工作,如圖所示:

從金拱門餐廳聯想到的分佈式系統設計思維

橫向可擴展能力是微服務化設計之後很容易實現的功能,可以通過服務發現方式實現各個進城之間的狀態信息交互,這裡就不多介紹了。

訂單處理過程上屏

訂單下單後,我們的訂單就進入到了麥當勞的任務排隊隊列,他們家的排隊隊列分為兩個,分別是待完成隊列、已完成隊列,如圖所示:

從金拱門餐廳聯想到的分佈式系統設計思維

這種隊列設計是分佈式計算應用領域常用的設計和處理方案,看似簡單,其實相當複雜,內容處理過程容易出現很多問題,我這裡根據那天等待取餐時發生的幾件小事,注意聊聊出現的幾個異常情況:

異常數據干擾

我們在排隊取餐,由於取餐是通過一名員工人工喊話的,她既要喊人取餐,又要處理各種售後請求(例如拿番茄醬、詢問訂單情況等等),這樣她很容易被幹擾,進而出現手忙腳亂的情況。聯想到分佈式環境下,如果我們不對任務的信息進行校驗,那麼很容易混入異常任務(可能是任務信息被截斷了,也可能是非法攻擊),對於核心服務的保護是必不可少的,分佈式環境下無論是否存在中心服務,我們都需要仔細思考各種異常保護,所以貌似流程、業務簡單,其實並不是那麼容易的。

排在前面的訂單遲遲沒有做完

如上面這張圖所示,理想情況下是按照訂單號逐一完成,這樣也會讓所有顧客都滿意,先來先得嘛。但是實際情況是不是這樣呢?我一位同事等了很久,原因是由於他的訂單中有一樣食物遲遲沒有啟動製作過程,這是因為調度服務內部經過性價比評估,發現只有他存在這個需求,所以一直在等待其他顧客點選相同產品後再一起製作,現在先把那些很多顧客都在等待的產品做出來,避免更多的人等待。回到分佈式技術領域,對於這種情況我們其實也在一直遇到,比如先下派資源需求大的任務,還是先下派小的?這就相當於先讓大塊的石頭填滿盒子,然後讓小塊的石頭塞入細小的縫隙。所有設計都是需要根據實際業務情況和實際測試得出的,而不是拍腦袋。

訂單完成前已經發貨

我的那位同事,他等了很久還沒有拿到事物,所以有點怒了。這時候負責取餐的員工隨意給了他一份食品,但是沒有標註正在排隊的任務為“已完成”狀態,相當於人為多複製了一個任務,一個任務變成兩個任務了,數據庫內出現了髒數據。等真正的任務完成時,由於無人取餐,導致陷入了死循環,負責取餐的員工一直在喊“1104 號顧客請取餐”,但是無人應答。

對於這類問題,在分佈式環境下也是很容易發生的。例如,計算節點在執行任務過程中離線了,一直沒有上報任務執行狀態,中心節點認為它離線了,就把運行在它上面的任務標註為“未調度任務”,重新下派,這時候如果離線的計算節點又恢復了正常,那麼就會有兩個相同的任務同時運行。這種異常其實很難避免,它本身是分佈式環境下任務容錯的一種設計方案,需要整個數據流閉環內協同處理,特別是任務的下游系統儘可能參與。做好分佈式系統,真不容易。

座位設計模式

由於快餐的就餐時間一般都較短,所以顧客流動速度很快,看著每個座位上的顧客頻繁更換,讓我聯想到了 Kubernetes 管理容器的設計模式。Kubernetes 知道整個餐廳的可用資源(座位)數目及使用情況,每個座位上的顧客就是臨時啟動的一個容器,用完就銷燬,資源釋放後,其他顧客可以接著資源。

來看看 Kubernetes 的相關概念。Kubernetes 以 RESTFul 形式開放接口,用戶可操作的 REST 對象有三個:

  • pod:是 Kubernetes 最基本的部署調度單元,可以包含 container,邏輯上表示某種應用的一個實例。比如一個 web 站點應用由前端、後端及數據庫構建而成,這三個組件將運行在各自的容器中,那麼我們可以創建包含三個 container 的 pod。

  • service:是 pod 的路由代理抽象,用於解決 pod 之間的服務發現問題。因為 pod 的運行狀態可動態變化 (比如切換機器了、縮容過程中被終止了等),所以訪問端不能以寫死 IP 的方式去訪問該 pod 提供的服務。service 的引入旨在保證 pod 的動態變化對訪問端透明,訪問端只需要知道 service 的地址,由 service 來提供代理。

  • replicationController:是 pod 的複製抽象,用於解決 pod 的擴容縮容問題。通常,分佈式應用為了性能或高可用性的考慮,需要複製多份資源,並且根據負載情況動態伸縮。通過 replicationController,我們可以指定一個應用需要幾份複製,Kubernetes 將為每份複製創建一個 pod,並且保證實際運行 pod 數量總是與該複製數量相等 (例如,當前某個 pod 宕機時,自動創建新的 pod 來替換)。

可以看到,service 和 replicationController 只是建立在 pod 之上的抽象,最終是要作用於 pod 的,那麼它們如何跟 pod 聯繫起來呢?這就要引入 label 的概念:label 其實很好理解,就是為 pod 加上可用於搜索或關聯的一組 key/value 標籤,而 service 和 replicationController 正是通過 label 來與 pod 關聯的。如下圖所示,有三個 pod 都有 label 為"app=backend",創建 service 和 replicationController 時可以指定同樣的 label:"app=backend",再通過 label selector 機制,就將它們與這三個 pod 關聯起來了。例如,當有其他 frontend pod 訪問該 service 時,自動會轉發到其中的一個 backend pod。

從金拱門餐廳聯想到的分佈式系統設計思維

技術來源於生活,我希望自己能夠從生活中感悟出更多的技術設想,每天的思考肯定有利於自己的技術進步,與君共勉!

今日話題

在你的日常生活中,有沒有哪些從前忽略的小細節,意外地和軟件開發領域的經驗有異曲同工之妙的?

快快留言告訴我們吧!

作者介紹

周明耀,2004 年畢業於浙江大學,工學碩士。13 年軟件研發經驗,近 10 年技術團隊管理經驗,4 年分佈式計算、大數據技術經驗。出版書籍包括《大話 Java 性能優化》、《深入理解 JVM&G1 GC》、《技術領導力 - 碼農如何才能帶團隊》,個人公眾號“麥克叔叔每晚 10 點說”出品人。個人微信號 michael_tec。

今日薦文

點擊下方圖片即可閱讀

從金拱門餐廳聯想到的分佈式系統設計思維

作者|周明耀

編輯|小智

其實在生活中,大到組織架構,小到瑣碎日常,都能學到一些經驗、總結出一些知識。共性往往隱藏在特性之中。你需要做的,可能僅僅只是細心觀察而已?

前幾天中午開會開到 12 點半,錯過了食堂吃飯時間,只能大家一起去麥當勞吃午飯。寫這篇文章,既是有感於麥當勞餐廳的管理模式,也是由於這次就餐在訂餐、取餐過程中發生了一些小事,讓我聯想到了分佈式軟件設計,我在 InfoQ 開設技術專欄之初就說過技術來源於生活細節,那麼今天就來系統性談談由麥當勞餐廳(這裡並不特指麥當勞,我相信也有類似的其他店家,這裡只是以麥當勞作為舉例)所想到的分佈式系統設計思維及問題。

結合現階段的觀察,我有以下觀點:

  • 店長負責制 ->主從模式(Master/Slave);

  • 訂單處理方式 ->兩階段提交;

  • 員工角色拆分 ->微服務設計;

  • 多個任務接收 ->微服務設計之後的服務橫向擴展;

  • 訂單處理過程上屏 ->任務隊列設計;

  • 座位設計模式 ->容器化管理。

我逐一聊聊我的看法,歡迎您留言指教。

店長負責制

每家麥當勞都會有一名當值經理,這位經理負責當前整個店的運營,如果遇到某個崗位繁忙的情況,那麼他會臨時調動其他相對空閒的員工支援,那麼這就是典型的 Master/Slave 架構。

從金拱門餐廳聯想到的分佈式系統設計思維

我們來看看較為典型的 Matser/Slave 架構設計。以 HBase 為例,看看 HBase 的 RegionServer 設計方式。在 HBase 內部,所有的用戶數據以及元數據的請求,在經過 Region 的定位,最終會落在 RegionServer 上,並由 RegionServer 實現數據的讀寫操作。RegionServer 是 HBase 集群運行在每個工作節點上的服務。它是整個 HBase 系統的關鍵所在,一方面它維護了 Region 的狀態,提供了對於 Region 的管理和服務;另一方面,它與 HMaster 交互,上傳 Region 的負載信息上傳,參與 HMaster 的分佈式協調管理。

從金拱門餐廳聯想到的分佈式系統設計思維

HRegionServer 與 HMaster 以及 Client 之間採用 RPC 協議進行通信。HRegionServer 向 HMaster 定期彙報節點的負載狀況,包括 RS 內存使用狀態、在線狀態的 Region 等信息。在該過程中 HRegionServer 扮演了 RPC 客戶端的角色,而 HMaster 扮演了 RPC 服務器端的角色。HRegionServer 內置的 RpcServer 實現了數據更新、讀取、刪除的操作,以及 Region 涉及到 Flush、Compaction、Open、Close、Load 文件等功能性操作。

Region 是 HBase 數據存儲和管理的基本單位。HBase 使用 RowKey 將表水平切割成多個 HRegion,從 HMaster 的角度,每個 HRegion 都紀錄了它的 StartKey 和 EndKey(第一個 HRegion 的 StartKey 為空,最後一個 HRegion 的 EndKey 為空),由於 RowKey 是排序的,因而 Client 可以通過 HMaster 快速的定位每個 RowKey 在哪個 HRegion 中。HRegion 由 HMaster 分配到相應的 HRegionServer 中,然後由 HRegionServer 負責 HRegion 的啟動和管理,和 Client 的通信,負責數據的讀 (使用 HDFS)。每個 HRegionServer 可以同時管理 1000 個左右的 HRegion。

如果我們需要把固定店長負責制改為全員負責制,有沒有類似案例?有,Apache Cassandra 就沒有固定的中心節點(無中心化設計),只有協調者節點(理論上所有節點都可以作為協調者,每次請求有且僅有一個),那麼它是怎麼做到的呢?

首先我們來看看 Gossip 協議。Cassandra 集群中的節點沒有主次之分,通過 Gossip 協議,可以知道集群中有哪些節點,以及這些節點的狀態如何等等。每一條 Gossip 消息上都有一個版本號,節點可以對接收到的消息進行版本比對,從而得知哪些消息是我需要更新的,哪些消息是我有而別人沒有的,然後互相傾聽吐槽,確保二者得到的信息相同,這很像現實生活中的八卦,一傳十,十傳百,最後盡人皆知。在 Cassandra 啟動時,會啟動 Gossip 服務,Gossip 服務啟動後會啟動一個任務 GossipTask,每秒鐘運行一次,這個任務會週期性與其他節點進行通信。回到麥當勞餐廳,是不是每個員工之間也需要有對講機,實時互相告知目前自己的工作狀態和訂單信息,這樣才能做到無固定店長管理制?

既然 Cassandra 是無中心化的,那麼如何來計算各個節點上存儲的數據之間的差異呢?如何判斷自己申請的那一份數據就是最新版本的?這裡我要引出 Snitch(告密者)機制了。

現實生活中的“告密者”會向別人告密誰有需要尋找的東西,Snitch 機制在 Cassandra 集群裡也起到了這樣的作用。Snitch 機制的功能是決定集群每個節點之間的相關性(值),這個值被用於決定從哪個節點讀取或寫入數據。此外,由於 Snitch 機制收集網絡拓撲內的信息,這樣 Cassandra 可以有效路由各類外部請求。Snitch 機制解決了節點與集群內其他節點之間的關係問題(前面介紹的 Gossip 解決了節點之間的消息交換問題)。當 Cassandra 收到一個讀請求,需要去做的是根據一致性級別聯繫對應的數據副本。為了支持最快速度的讀取請求,Cassandra 選擇單一副本讀取全部數據,然後要求附加數據副本的 hash 值,以此確保讀取的數據是最新的數據並返回。Snitch 的角色是幫助驗證返回最快的副本,這些副本又被用於讀取數據。回到麥當勞餐廳,如果互相都知道了誰有哪些產品,那麼完成訂單配單步驟就不難了。

正是有了 Gossip 協議和 Snitch 機制,才在通信層基本滿足無中心化的設計,當然還有很多其他因素一起參與,這裡不逐一介紹。

訂單處理方式

麥當勞和其他商家一樣,都在追求每日最大訂單處理量,你懂的,更多的訂單意味著更多的利潤。為了加快銷售速度,麥當勞採用了異步處理模式。當你在收銀臺下單後,收營員會進行下單,下單過程結束後你就進入到了製作訂單隊列。有了這個隊列,營業員和製作員之間的串行耦合方式被解耦了,這樣營業員就可以繼續接收訂單,即便製作員手上的訂單已經開始排隊了也沒關係。店裡忙的時候,會動態增加幾個製作員,他們之間本身也處在一個競爭環境下,就好像分佈式環境下會存在多個計算節點一樣。因此,麥當勞的這種製作過程,本質上就是一個分佈式處理過程:訂單隊列屬於中心節點的待運行任務隊列,每個製作員是一個計算節點,無論採用申請還是被分配策略,他們都可以正常幹活。這讓我聯想到了兩階段提交,為什麼麥當勞不採用兩階段提交協議呢?

以上我介紹的所有這些策略都不同於兩階段提交,什麼是兩階段提交?它是分佈式系統架構下的所有節點在進行事務提交時保持一致性而設計的一種算法。

兩階段提交協議一般將事務的提交過程分成了兩個階段:

  • 提交事務請求

  • 執行事務請求

核心是對每個事務都採用先嚐試後提交的處理方式,因此它是一個強一致性的算法。

雖然說兩階段提交算法具有原理簡單、實現方便的優點,但是它也有一些缺點:

  • 同步阻塞:所有參與該事務操作的邏輯都處於阻塞狀態;

  • 單點問題:協調者(營業員、訂單排隊隊列)如果出現問題,整個兩階段提交流程將無法運轉,參與者的資源將會處於鎖定狀態;

  • 數據不一致:協調者向所有的參與者發送 Commit 請求之後,由於局部網絡問題,會出現部分參與者沒有收到 Commit 請求,進一步造成數據不一致現象。麥當勞的例子中,如果有多個隊列排隊時,會存在問題;

  • 太過保守:參與者出現異常時,協調者只能通過其自身的超時機制來判斷是否需要中斷事務,即任意一個節點的失敗都會導致整個事務的失敗。

兩階段提交需要依賴於不同的準備和執行步驟。在麥當勞這個例子裡面,如果採用兩階段提交,顧客需要在收銀臺等待食物製作完畢,他得自己拿著錢。然後,錢、收據、食物會做一次一手交錢,一手交貨的交換。營業員和顧客在交易完成前都不能離開。兩階段提交可以讓生活更加簡單,但是也會傷害到消息的自由流通,因為本質上它是基於各方之間的有狀態交易資源的異步動作。

員工角色拆分

如果你去小型的快餐店,你會發現只有 1 名員工,他既要負責下單、收銀,也要負責事物製作、打包、售後,這樣自然速度就慢了。麥當勞把員工分為了幾個不同的角色,這些角色在幹活的時候互相不干擾,通過通信機制(訂單系統信息投影屏幕)方式協同工作,這樣可以最大化生產效率。即使出現意外情況,例如某位員工中暑了,店長可以立即協調一名員工補上他的位置,而不需要和其他角色的員工產生交互成本。

這樣的設計方式和微服務架構比較類似,下面這張圖是一個典型的傳統單體型服務架構模式:

從金拱門餐廳聯想到的分佈式系統設計思維

單體型架構比較適合小項目,缺點比較明顯:

  • 開發效率低:所有的開發在一個項目改代碼,遞交代碼相互等待,代碼衝突不斷

  • 代碼維護難:代碼功能耦合在一起,新人不知道何從下手

  • 部署不靈活:構建時間長,任何小修改必須重新構建整個項目,這個過程往往很長

  • 穩定性不高:一個微不足道的小問題,可以導致整個應用掛掉

  • 擴展性不夠:無法滿足高併發情況下的業務需求

微服務是指開發一個單個小型的但有業務功能的服務,每個服務都有自己的處理和輕量通訊機制,可以部署在單個或多個服務器上。微服務也指一種種鬆耦合的、有一定的有界上下文的面向服務架構。也就是說,如果每個服務都要同時修改,那麼它們就不是微服務,因為它們緊耦合在一起;如果你需要掌握一個服務太多的上下文場景使用條件,那麼它就是一個有上下文邊界的服務。

經過微服務架構的拆分,上面描述的單體型架構成了下面這張微服務架構圖:

從金拱門餐廳聯想到的分佈式系統設計思維

多個任務接收

你會發現,為了加快訂單接收速度,同時又要在有限的營業窗口的限制的前提下加快速度,怎麼辦?麥當勞引入了自動訂餐機器,這樣就允許客戶通過觸摸式接觸屏點選自己需要的食品,可以任意組合,然後點擊下單,通過“微信、支付寶”等手機支付方式正式下單,接著你的訂單就進入到了統一的任務排隊隊列,這和通過點單員下單是完全一樣的。

由於有了微服務架構的支撐及通信框架的交互設計,點單這個工作變成了一個完全獨立的工作,如圖所示:

從金拱門餐廳聯想到的分佈式系統設計思維

橫向可擴展能力是微服務化設計之後很容易實現的功能,可以通過服務發現方式實現各個進城之間的狀態信息交互,這裡就不多介紹了。

訂單處理過程上屏

訂單下單後,我們的訂單就進入到了麥當勞的任務排隊隊列,他們家的排隊隊列分為兩個,分別是待完成隊列、已完成隊列,如圖所示:

從金拱門餐廳聯想到的分佈式系統設計思維

這種隊列設計是分佈式計算應用領域常用的設計和處理方案,看似簡單,其實相當複雜,內容處理過程容易出現很多問題,我這裡根據那天等待取餐時發生的幾件小事,注意聊聊出現的幾個異常情況:

異常數據干擾

我們在排隊取餐,由於取餐是通過一名員工人工喊話的,她既要喊人取餐,又要處理各種售後請求(例如拿番茄醬、詢問訂單情況等等),這樣她很容易被幹擾,進而出現手忙腳亂的情況。聯想到分佈式環境下,如果我們不對任務的信息進行校驗,那麼很容易混入異常任務(可能是任務信息被截斷了,也可能是非法攻擊),對於核心服務的保護是必不可少的,分佈式環境下無論是否存在中心服務,我們都需要仔細思考各種異常保護,所以貌似流程、業務簡單,其實並不是那麼容易的。

排在前面的訂單遲遲沒有做完

如上面這張圖所示,理想情況下是按照訂單號逐一完成,這樣也會讓所有顧客都滿意,先來先得嘛。但是實際情況是不是這樣呢?我一位同事等了很久,原因是由於他的訂單中有一樣食物遲遲沒有啟動製作過程,這是因為調度服務內部經過性價比評估,發現只有他存在這個需求,所以一直在等待其他顧客點選相同產品後再一起製作,現在先把那些很多顧客都在等待的產品做出來,避免更多的人等待。回到分佈式技術領域,對於這種情況我們其實也在一直遇到,比如先下派資源需求大的任務,還是先下派小的?這就相當於先讓大塊的石頭填滿盒子,然後讓小塊的石頭塞入細小的縫隙。所有設計都是需要根據實際業務情況和實際測試得出的,而不是拍腦袋。

訂單完成前已經發貨

我的那位同事,他等了很久還沒有拿到事物,所以有點怒了。這時候負責取餐的員工隨意給了他一份食品,但是沒有標註正在排隊的任務為“已完成”狀態,相當於人為多複製了一個任務,一個任務變成兩個任務了,數據庫內出現了髒數據。等真正的任務完成時,由於無人取餐,導致陷入了死循環,負責取餐的員工一直在喊“1104 號顧客請取餐”,但是無人應答。

對於這類問題,在分佈式環境下也是很容易發生的。例如,計算節點在執行任務過程中離線了,一直沒有上報任務執行狀態,中心節點認為它離線了,就把運行在它上面的任務標註為“未調度任務”,重新下派,這時候如果離線的計算節點又恢復了正常,那麼就會有兩個相同的任務同時運行。這種異常其實很難避免,它本身是分佈式環境下任務容錯的一種設計方案,需要整個數據流閉環內協同處理,特別是任務的下游系統儘可能參與。做好分佈式系統,真不容易。

座位設計模式

由於快餐的就餐時間一般都較短,所以顧客流動速度很快,看著每個座位上的顧客頻繁更換,讓我聯想到了 Kubernetes 管理容器的設計模式。Kubernetes 知道整個餐廳的可用資源(座位)數目及使用情況,每個座位上的顧客就是臨時啟動的一個容器,用完就銷燬,資源釋放後,其他顧客可以接著資源。

來看看 Kubernetes 的相關概念。Kubernetes 以 RESTFul 形式開放接口,用戶可操作的 REST 對象有三個:

  • pod:是 Kubernetes 最基本的部署調度單元,可以包含 container,邏輯上表示某種應用的一個實例。比如一個 web 站點應用由前端、後端及數據庫構建而成,這三個組件將運行在各自的容器中,那麼我們可以創建包含三個 container 的 pod。

  • service:是 pod 的路由代理抽象,用於解決 pod 之間的服務發現問題。因為 pod 的運行狀態可動態變化 (比如切換機器了、縮容過程中被終止了等),所以訪問端不能以寫死 IP 的方式去訪問該 pod 提供的服務。service 的引入旨在保證 pod 的動態變化對訪問端透明,訪問端只需要知道 service 的地址,由 service 來提供代理。

  • replicationController:是 pod 的複製抽象,用於解決 pod 的擴容縮容問題。通常,分佈式應用為了性能或高可用性的考慮,需要複製多份資源,並且根據負載情況動態伸縮。通過 replicationController,我們可以指定一個應用需要幾份複製,Kubernetes 將為每份複製創建一個 pod,並且保證實際運行 pod 數量總是與該複製數量相等 (例如,當前某個 pod 宕機時,自動創建新的 pod 來替換)。

可以看到,service 和 replicationController 只是建立在 pod 之上的抽象,最終是要作用於 pod 的,那麼它們如何跟 pod 聯繫起來呢?這就要引入 label 的概念:label 其實很好理解,就是為 pod 加上可用於搜索或關聯的一組 key/value 標籤,而 service 和 replicationController 正是通過 label 來與 pod 關聯的。如下圖所示,有三個 pod 都有 label 為"app=backend",創建 service 和 replicationController 時可以指定同樣的 label:"app=backend",再通過 label selector 機制,就將它們與這三個 pod 關聯起來了。例如,當有其他 frontend pod 訪問該 service 時,自動會轉發到其中的一個 backend pod。

從金拱門餐廳聯想到的分佈式系統設計思維

技術來源於生活,我希望自己能夠從生活中感悟出更多的技術設想,每天的思考肯定有利於自己的技術進步,與君共勉!

今日話題

在你的日常生活中,有沒有哪些從前忽略的小細節,意外地和軟件開發領域的經驗有異曲同工之妙的?

快快留言告訴我們吧!

作者介紹

周明耀,2004 年畢業於浙江大學,工學碩士。13 年軟件研發經驗,近 10 年技術團隊管理經驗,4 年分佈式計算、大數據技術經驗。出版書籍包括《大話 Java 性能優化》、《深入理解 JVM&G1 GC》、《技術領導力 - 碼農如何才能帶團隊》,個人公眾號“麥克叔叔每晚 10 點說”出品人。個人微信號 michael_tec。

今日薦文

點擊下方圖片即可閱讀

從金拱門餐廳聯想到的分佈式系統設計思維

禪與互聯網技術:龍泉寺的程序員們

小貼士

正如本文開頭所言,生活中處處可見軟件開發的思維,生活中也日漸充斥著 AI 技術帶來的新變化。時下最火熱的技術非 AI 莫屬,那麼,目前到底都有哪些 AI 落地案例呢?機器學習、深度學習、NLP、圖像識別等技術又該如何用來解決業務問題?

AICon2018 全球人工智能技術大會上,我們邀請了來自 360、京東、騰訊、百度、Etsy、微軟、餓了麼、摩拜、搜狗、攜程、微博等國內外知名企業的 AI 應用落地負責人,前來分享他們可供參考的最新 AI 落地案例和技術探索,或許可以給你一些啟發,目前大會 8 折報名火熱進行中,點擊“閱讀原文”瞭解詳情!

相關推薦

推薦中...