崛起的 Kafka

本文譯自 Braedon Vickers 發佈在 Movio 上的一篇文章,詳盡的探討了在微服務架構升級的過程中,如何使用 Kafka 將微服務之間耦合降到最低,同時能讓整個系統在保證高可用的前提下做到高可擴展。

隨著微服務的流行,很多公司都在嘗試將現有的系統進行架構升級。促成 Movio 公司架構改造的一項關鍵技術就是 Kafka 消息隊列。Kafka 是一個開源的分佈式消息隊列,在可靠性和可擴展性方面有非常大的優勢。 我們正處於把系統遷移到微服務新世界的過程中。

促成這次架構改造的一項關鍵的技術就是 Apache Kafka 消息隊列。它不僅成為了我們基礎架構的關鍵組成部分,還為我們正在創建的系統架構提供了依據。

1.Kafka 概覽

雖然這篇文章的目的不是在宣揚 Kafka 比其他消息隊列系統更優秀,但是本文討論的某些部分是專門針對它的。對於外行來說,Kafka 是一個開源的分佈式消息隊列系統。它最初是由 LinkedIn 研發,現在由 Apache 軟件基金會維護。和其他的消息隊列系統一樣,你可以給它發送消息,同時也可以讀取消息。用 Kafka 的說法就是 “生產者” 發送消息,“消費者”接收它們。

Kafka 的獨特性在於它同時提供簡單的文件系統和橋接這兩種功能。一個 Kafka 的代理器(broker)最基本的任務就是儘可能快地將消息寫到磁盤上的日誌中,並從中讀取消息。消息隊列中的消息在持久化之後就不會丟失了,這是整個項目的核心所在。

隊列(Queue)在 Kafka 中被稱為 “主題”(topic),同一個主題共享 1 個或多個 “分區”(partition)。每條消息都有一個 “偏移”(offset)- 一個代表它所在分區位置的偏移量。這使得消費者可以記錄它們當前讀到的位置,並向代理(broker)請求讀取接下來一條(或多條)消息。多個消費者可以同時讀取同一個分區的數據,每個消費者從某個位置上讀取數據都是獨立於其他消費者的。它們甚至可以在整個分區內隨意跳躍式前進或後退。多個 Kafka 代理組合到一起成為一個集群。分區是在分散在整個集群中的,這樣就提供了可擴展性。它們也可以複製到集群中的多個節點上,實現了高可用性。綜合複製與分區持久化特點,進而達到高可靠性。

2. 構建微服務的系統架構

為了幫助我們理解 Kafka 的用途和影響力,想象一個通過 REST API 從外部接收數據的系統,進而用某種方式進行變換,最後將結果保存到數據庫中。我們想要把這個系統升級為微服務架構,所以首先把這個系統切分為兩個服務 - 一個用來提供外部的 REST 接口 (Alpha 服務),另外一個用來做數據變換(Beta 服務)。簡單起見,Beta 服務同時會負責存儲變換後的數據。

由一對微服務組成的系統會比提供整體服務(往往是糟糕的)的系統要好一點,所以我們定義一個接口用於從 Alpha 服務將數據發送到 Beta 服務,用來減少耦合。和使用 REST 接口實現的系統相比,讓我們看看使用 Kafka 實現這個接口將會如何影響該系統的設計和運行。

崛起的 Kafka

3. 系統的可用性和性能

當數據被提交到 Alpha 的服務,它在響應客戶端之前需要確保數據安全地存儲在某個地方,或者已經失敗 - 客戶端需要知道,因為如果出現了錯誤它可以重新發送(或用一些其他的方式進行恢復), 使用 REST 接口,Alpha 服務在響應客戶端之前將需要一直等待 Beta 服務的響應,直到 Beta 服務將數據存儲到數據庫中。

這種做法存在兩個問題。首先,Alpha 服務現在要求 Beta 服務是啟動狀態並且可以響應請求 - 它的正常運行依賴於 Beta 服務。其次,Alpha 服務要等到 Beta 服務的響應才能響應客戶端 - 它的性能也依賴於 Beta 服務!

在這兩種情況下,Alpha 服務耦合於 Beta 服務。Beta 服務失敗的頻率是多少?它需要停機維護的頻率是多少?它的峰值性能是多少?難道真的只有在數據被安全的存儲之後才可以響應,或者說提前響應就是不可行的?如果它依賴於其他服務,相應的會需要進一步延伸耦合鏈?像這樣的系統好壞程度取決於其最薄弱(weakest)的服務。

如果我們使用 Kafaka 消息隊列做為接口替換上面描述的,我們將會得到一個完全不同的結果,Kafka 有一招:一個 Kafka 消息隊列是持久化存儲的。當數據已安全地放到隊列中時,Alpha 服務就可以響應請求了;我們可以確信數據將最終會存儲到數據庫中,因為 Beta 服務致力於處理隊列中的消息。Alpha 服務現在僅僅依賴於 Kafka 的正常運行和性能了 - 這兩個指標都可能遠遠好於系統中的其他微服務。它是如此松耦合於 Beta 服務,它甚至都不需要知道它的存在!

當然,消息隊列從來就不是性能問題的靈丹妙藥 - 它並不能神奇的改善系統的整體性能(舉起手來,如果在你曾經參加的會議中有人相信的話)。然而,它確實允許你應對來自外部系統的可變負載,或者甚至是你自己系統中的微服務(例如那些必須做某種形式的批處理)。消息隊列能夠應對峰值負載,從而使下游服務可以平滑的速度處理。一個給定的服務的性能只需要大於系統的平均負載,而不是峰值負載。

使用 REST 接口時,你可以通過在每個服務中存儲數據來打破依賴鏈並實現類似的效果。然而,為了達到這一點,在每一個服務中你都需要自己實現消息代理模塊。使用現有的服務你自己不必再設計、構建和維護一套(或多套)類似的系統。

4. 服務間松耦合

在設計系統的時候,我們發現如果 Alpha 服務返回的結果是轉換後的數據,一些客戶可能會發現它會很方便。

如果使用 REST 接口這將變得非常容易 - Beta 服務可以返回轉換後的數據,Alpha 服務直接透傳給客戶端。同時我們在兩個服務之間增加了一個新的依賴關係 - Alpha 服務現在依賴於 Beta 服務的轉換功能。這和上面小節中 Alpha 服務依賴於 Beta 服務存儲數據是類似的情景。同樣的問題出現了:如果 Beta 服務不能及時並且正確執行其功能,Alpha 服務同樣的也不能返回結果給其客戶端。

和存儲數據不同的是 Kafka 針對這個問題沒有提供有效的解決方案。事實上,用 Kafka 接口來達到這個目的將會更復雜(但絕不是不可能)。因為消息隊列是單向通信信道,從 Beta 服務獲取轉換後的數據需要相反方向的第二條信道。匹配這些異步響應結果和等待的客戶端也會需要一些額外的工作。

然而,用消息隊列不容易做到這個事實的給了我們一個啟發,那就是我們新生的系統有些地方做的並不好,我們應該重新評估。縱觀我們加諸於系統的額外功能與數據流的關係,結果表明不管它是如何實現的,是該功能引入了服務之間耦合。要想讓 Alpha 返回轉換後的數據則他必須依賴於真正做數據轉換的服務,我們架構的系統不可能繞過這個事實。

這讓我們停下來檢查我們是否確實需要這個功能。有沒有其他的方法可以提供訪問轉換後的結果數據並且不會引入這個問題?客戶端是否需要所有轉換後的數據?如果這個功能是必要,表明我們在切分微服務的時候並不是最優的,我們應該將數據轉換這步放到 Alpha 服務中。如果不是必要的,我們只是阻止了自己添加不必要的或設計不當的功能,這些功能未來可能會影響我們系統的發展和性能。系統的功能一旦添加上往往很難移除掉了。

為了避免在系統中引入沉重的包袱,在實現之前辨別有疑問的功能(或者功能設計)是非常必要的。Kafka 消息隊列的自身的侷限性可以指導我們設計出更好的系統。當你試圖砍掉一個緊急的功能時,他們可能會感到沮喪,但是從長遠來看是值得被稱讚的。

5. 提高可用性和擴展性

隨著負載的增加,我們的系統應該保持高可用性,可擴展性。作為實現這一目標的一部分,我們希望能夠運行多個 Beta 實例。如果其中一個實例宕機,其他的實例會(希望)仍然能夠正常運行。可以增加更多的實例來處理增加的負載。

使用 REST 接口時,我們可以在 Beta 服務實例前面加一個負載均衡器,並且把 Alpha 服務從直接指向 Beta 服務的實例替換為指向這個負載均衡器。負載均衡器需要能夠自動檢測宕機的實例,從而一旦有實例宕機可以將負載轉移到別的實例上。

一個 Kafka 消息隊列默認支持可變數量的消費者(例如 Beta 服務),不需要額外的基礎架構的支持。多個消費者可以組成一個 “消費組”,只需要在連接集群的時候指定一個相同的組名。Kafka 會將每個主題所有分區的數據共享傳輸給整個組的消費者。只要我們使用的主題有足夠多的分區,我們可以持續增加 Beta 服務的實例,這麼它們將會分擔一部分負載。如果某些實例不可用,他們的分區將由剩餘的實例處理。

6. 增加更多服務

我們需要在我們的系統中添加一些新的功能(這並不重要),我們將把它放在一個新的 Gamma 服務中。它依賴 Alpha 服務的數據的方式和 Beta 服務依賴 Alpha 服務的數據的方式是一樣的,並且數據是用同樣的接口提供的。數據必須經過 Gamma 服務處理才算被整個系統完整的處理。

崛起的 Kafka

如果使用 REST 接口,則 Alpha 服務將會耦合一個額外的服務,加劇前面討論的服務間耦合的問題。隨著下游服務的增加,依賴會變得原來越多。

此外,一組數據可能成功的被一個下游的服務處理,但是在另外一個服務中卻處理失敗。這種情況下,可能很難通知到客戶端和做到自動恢復,特別是如果它們可以在狀態不一致的情況下就離開。

使用 Kafka 接口允許新的 Gamma 服務和 Beta 服務一樣簡單地從同一消息隊列中讀取數據。只要它使用一個不同於 Beta 服務的消費組,兩者之間不會互相干擾。由於 Alpha 服務不需要知道它寫入數據的消息隊列有什麼服務在使用,我們可以持續的添加服務而不會對它造成任何影響。

數據被安全地存儲在 Kafka 中,所以各個服務可以在瞬時故障的情況下重試,例如數據庫死鎖或者是網絡問題。非瞬時錯誤,例如髒數據,和使用 REST 接口一樣都會存在類似一些問題。檢查數據合法性越早越好,例如在阿爾法服務,是減少這些問題的關鍵。

7. 總結

以上的例子都是直接取自於我們在 Movio 推進微服務化過程中的真實案例。在這個過程中它已經證明了是一個非常寶貴的工具,催生了優秀的系統架構,並可以簡單和快速的實現它。我們將繼續探索使用它的新方法,並期望我們的使用會促進系統的進一步發展。LinkedIn 和 Apache 的團隊創造了一件偉大的作品。


分享到:


相關文章: