架構師圖譜之微服務 - 消息隊列

概述

“架構師圖譜” 是一個很宏大的命題,特別是優秀的架構師自身也是 “由點到面再到圖”,一點點成長積累起來,嘗試寫這篇文章的目的更多的是結合自身的一些架構、研發、管理經驗對現階段做一個覆盤總結,所以這裏更偏向於後端圖譜,依賴於開源技術、雲原生或者其他第三方服務。這裏會重點介紹一些技術棧、設計理念以及適應場景,這些可以作爲我們選型時的依據。所謂 “架構即決策”,是在一個有約束的盒子中尋求最優解。這個有約束的盒子是團隊經驗、成本、資源、進度、業務所處階段等編織、摻雜在一起的綜合體。本質上無優劣,但是存在恰當的架構用在合適的軟件系統中,而這些就是決策的結果。

序章

一個技術圖譜:

計劃會分上、中、下三個篇章來介紹:

完整的思維導圖:

微服務(英語:Microservices)是一種軟件架構風格,它是以專注於單一責任與功能的小型功能區塊(Small Building Blocks)爲基礎,利用模塊化的方式組合出複雜的大型應用程序,各功能區塊使用與語言無關(Language-Independent/Language agnostic)的 API 集相互通信。

微服務架構有別於更爲傳統的單體服務,可將應用拆分成多個核心功能。每個功能都被稱爲一項服務,可以單獨構建和部署。這也體現了可擴展的基本思想:將原本大一統的系統拆成多個小部分,擴展時只修改其中一部分,通過這種方式減少改動範圍,降低改動風險。微服務架構涵蓋了服務的多個方面,包括網關、通信協議、服務註冊 / 發現、可觀察性、如何合理的劃分等等。

理論基礎

微服務的理論基礎主要用來指導微服務架構設計、服務拆分,確定合適的服務粒度和邊界。在做微服務之前我們首先要想明白我們現有系統面臨什麼樣的問題,爲什麼需要微服務,隨後纔是怎麼做。微服務很多核心理念其實在半個世紀前的一篇文章中就被闡述過了,而且這篇文章中的很多論點在軟件開發飛速發展的這半個世紀中竟然一再被驗證,這就是康威定律(Conway’s Law)。在康威的這篇文章中,最有名的一句話就是:

Organizations which design systems are constrained to produce designs which are copies of the communication structures of these organizations.

中文直譯大概的意思就是:設計系統的組織,其產生的設計等同於組織之內、組織之間的溝通結構。最初這篇文章只是描述作者自己的發現和總結,後來 “人月神話” 中,引用這個觀點,並將其 “吹捧” 成現在熟知的“高位定律”,其中的一些核心觀點可以概括如下:

但是當我們的業務和組織架構複雜度比較高的時候,很多概念只從技術角度很難去抽象,這就需要我們自上而下,建立起通用語言,讓業務人員和研發人員說一樣的話,把思考層次從代碼細節拉到業務層面。越高層的抽象越穩定,越細節的東西越容易變化。通過對不同領域的建模,逐步確定領域範圍和業務邊界,這也就是領域驅動設計(DDD)。DDD 是一種在面向高度複雜的軟件系統時,關於如何去建模的方法論,它的關鍵點是根據系統的複雜程度建立合適的模型,DDD 中的界限上下文也完美匹配了微服務的高內聚、低耦合特性,這也爲我們微服務的劃分提供了強有力的基礎。DDD 實施的一般步驟是:

但是 DDD 也不是銀彈,特別是在一些新業務場景,本身就充滿了很多的不確定性,一次性把邊界劃清楚並不是一件很容易的事。大家在一個進程裏,調整起來會相對容易,然後讓不同的界限上下文各自演化,等到了一定程度之後再考慮微服務也是一個不錯的選擇。

網關

作爲微服務的統一入口,也肩負着整個微服務的流量接入、管理、聚合、安全等,從服務分層的角度可以劃分爲接入網關和業務網關。接入網關接入網關提供最基礎的流量接入和安全防護能力,側重於全局,與業務無關。

業務網關:

業務網關作爲業務的最上層出口,一般承擔起業務接入或者 BFF 的工作,例如基礎的路由、鑑權、限流、熔斷降級、服務聚合、插件化能力,並可以通過可視化界面管理網關配置。可選框架有基於 OpenResty 的 Kong、APISIX 以及其他語言相關的 Spring Cloud Gateway、gRPC-Gateway 等等,國內開源的 Goku、Kratos、go-zero go 框架,有很多比較有意思的組件實現,我們日常業務上也可以借鑑。

協議

服務間的通信方式是在採用微服務架構時需要做出一個最基本的決策,統一的協議標準也能大大降低服務的聯調和維護成本。

RPC vs HTTP REST 優點:

在一些特定場景,例如:OpenAPI、BFF 等,HTTP REST 可以更大程度上降低外部團隊的接入成本。並且 RPC 也有調試不便、多語言互通需要對應的 SDK 支持這些問題,各有利弊。綜合考慮來看,除了一些特定場景,如果我們已經有相對完善的基礎設施支撐(RPC 框架、服務治理),RPC 可以爲一個更合適的選擇。

服務註冊 / 發現

服務註冊主要是通過將微服務的後端機器 IP、端口、地域等信息註冊起來,並結合一定的發現機制使客戶端的請求能夠直連具體的後端機器。從實現方式上可以分爲服務端模式與客戶端模式:

從兩種模式的實現方式上可以看出:

配置中心

配置中心從使用場景來講,一類是前邊講到的服務註冊、發現和 KV 存儲,例如 etcd/ZooKeeper/Consul,在 Kubernetes 場景下也可以通過 ConfigMap/Secret 將配置寫入本地文件、環境變量或者共享的 Volume 中,這樣沒有了中心服務的依賴和客戶端的接入,可以實現一些老舊服務的無侵入式改造。但是作爲配置中心,除了基礎的配置數據,一些情況下還要開放給非開發人員(測試、運維、產品)使用,完善的控制檯、權限管理、Dashbord 的支持,也非常重要,這類可以參考 Nacos(阿里開源)/Apollo(攜程開源)。Nacos 在讀寫性能上優於 Apollo,但是功能特性(例如權限管理)稍遜於 Apollo。

可觀察性

在控制論中,可觀察性是用系統輸出到外部的信息來推斷系統內部運運行狀態的一種度量方式

在雲原生時代,容器和服務的生命週期是緊密聯繫在一起的,相較在傳統的單體服務運行在物理主機或者虛擬機當中,排查問題的時候顯得非常不便,這種複雜性導致了一個定義研發運營效率的 MTTR(平均故障修復時間)指標急劇增加。所以這裏更強調的是微服務的可觀察性,需要提前想好我們要如何觀察容器內的服務以及服務之間的拓撲信息、各式指標的蒐集等,這些監測能力相當重要。可觀察性三大支柱圍繞 Tracing(鏈路追蹤)、Logging(日誌)和 Metrics(度量)展開,這三個維度幾乎涵蓋了應用程序的各種表徵行爲,開發人員通過收集並查看這三個維度的數據時刻掌握應用程序的運行情況。很長一段時間,這三者是獨立存在的,隨着時間的推移,這三者已經相互關聯,相輔相成。

鏈路追蹤

鏈路追蹤爲分佈式應用的開發者提供了完整的調用鏈路還原、調用請求量統計、鏈路拓撲、應用依賴分析等工具,可以幫助開發者快速分析和診斷分佈式應用架構下的性能瓶頸,提高微服務時代下的開發診斷效率以及系統的可觀察性。爲了解決不同的分佈式系統 API 不兼容的問題,誕生了 OpenTracing 規範,OpenTracing 中的 Trace 可以被認爲是由多個 Spacn 組成的 DAG 圖。

[Span A]  ←←←(the root span)
        |
 +------+------+
 |             |
[Span B]      [Span C] ←←←(Span C 是 Span A 的孩子節點, ChildOf)
 |             |
[Span D]      +---+-------+
           |           |
       [Span E]    [Span F] >>> [Span G] >>> [Span H]
                                   ↑
                                   ↑
                                   ↑
                     (Span G在Span F後被調用,FollowsFrom)

OpenTracing 專注在 tracing,除此之外還有包含了 Metrics 的 OpenCensus 標準,以及由 CNCF 推出,融合 OpenTracing 和 OpenCensus 的 OpenTelemetry。OpenTelemetry 旨在實現雲原生時代可觀察性指標(Tracing、Logging、Metrics)的統一收集和處理,同時提供推動這些標準實施的組件和工具。OpenTracing 中的佼佼者當屬 Jaeger、Zipkin、Skywalking。他們之間的一些對比:

Zipkin 開源時間長,社區相對豐富,Jaeger 更加輕量,也是 Istio 推薦方案,SkyWalking 支持部分語言(Java、PHP、Python 等)的無侵入式接入。另外 APM(應用性能)監控的支持也會影響到我們的選型。除此之外,面對線上海量請求,如果採用抽樣採樣策略,那就需要支持一定的流量染色,把我們核心關注的請求(例如鏈路中發生了錯誤、部分請求耗時過高等)都進行採樣,可以通過結合 opentelemetry-collector 以及開箱即用的 tailsamplingprocessor[1] 構建 Pipeline 插件實現。

日誌

服務間的鏈路日誌能否幫助我們判斷錯誤發生的具體位置,這類業務日誌主要集中在訪問日誌 / 打點日誌等等。隨着大數據的興起,我們對數據的分析解讀能力越來越強,日誌作爲原始數據則體現出了更大的價值,例如用戶的行爲分析,反垃圾,輿情分析等等。

業務日誌:這類日誌重點在於通過不同級別的日誌,及時發現分析系統存在的異常,RFC 5424 定義的 8 中日誌級別:

在實際使用過程中可能會對日誌級別進行簡化和調整,一般來講 Warning 及以上的日誌是需要重點關注的,需要做好及時的監控告警,Warning 以下的日誌也可以輔助問題的定位。日誌寫入可以選擇寫入消息隊列,也可以選擇落地磁盤,將關心的結構化或非結構化日誌、業務模塊信息(如果是細粒度的微服務,可以選擇將日誌放同一模塊收集),以及級別、時間(who、when、where、how、what)等要素正確的寫入正確寫入後再收集到日誌服務。寫入消息隊列需要考慮消息隊列的選型以及做好可用性和積壓監控,寫入磁盤需要考慮寫入性能以及日誌的切割清理,例如 Golang 的 zap+rotatelogs 組合。日誌收集的話,由於 Logstash 資源消耗相對比較大,虛擬機環境中可以使用 Filebeat 來替代,更嚴苛的線上或容器環境,可以使用 Fluentd/Fluentd Bit。日誌最終彙總到 ES 和 Kibana 做展示,通過 Esalert 定製告警策略。

大數據日誌:大數據日誌本質上也對應着我們一定的業務場景,但大多是海量日誌、高吞吐量場景,所以對海量日誌的收集和存儲是較大的挑戰。實現方案我們可以採用高吞吐量的流式中間件,例如 Kafka/Plusar 等,在結合流式處理(Flink)或者批處理(Spark)系統,將數據彙總到 Hadoop 進行分析,這裏涉及到的中間件和數據庫可參考後續章節。

指標

指標是有關係統的離散的數據點,這些指標通常表示爲計數或度量,並且通常在一段時間內進行彙總或計算,一般用來做基礎的資源監控和業務監控:

Zabbix 作爲老牌的監控系統,適合更復雜的物理機、虛擬機、數據庫等更復雜的場景,同時也擁有更豐富的圖形化界面,但是 Prometheus 作爲雲原生的代表作,與 Kubernetes、容器等能更好的結合,協同 Grafana 實現可定製化的界面,另外存儲基於 TSDB,相比於關係型數據庫也有更好的擴展性。以 Prometheus 爲例,支持的數據類型有:

Service Mesh

Service Mesh 這個服務網格專注於處理服務和服務之間的通信,包括我們前邊講的服務發現、熔斷降級、安全、流量控制、可觀察性等能力。這些通用能力在 Service Mesh 出現之前,由 Lib/Framework 完成,這樣就可以在開發層面上很容易地集成到我們的應用服務中。但是並沒有辦法實現跨語言編程,有什麼改動後,也需要重新編譯重新發布服務。理論上應該有一個專門的層來幹這事,於是出現了 Sidecar,Sidecar 集羣就成了 Service Mesh,加上對整個集羣的管理控制面板,就成了現在的 Service Mesh 架構,可以說 Service Mesh 是雲原生時代的必然產物。

目前比較流行的 Service Mesh 開源軟件是 Istio 和 Linkerd,還有更加輕量級的 Conduit,它們都可以在 Kubernetes 中集成。Istio 基於 Golang 編寫,使用 Envoy 作爲 Sidecar,在服務治理方面職責分明,國內落地案例相較 Linkerd 、Conduit 更加廣泛。由於 Service Mesh 承擔了服務核心的流量調度環節,再給我們帶來便利的同時,也引入很多的不可控因素,例如:Sidecar 組件不可用,將直接導致系統出現致命問題。所以在充分做好服務可觀察性的前提下,也要保證 Service Mesh 的高可用,一種比較好的方式是,除了在本機有 Sidecar,我們還可以部署一下稍微集中一點的 Sidecar——比如爲某個服務集羣部署一個集中式的 Sidecar。一旦本機的有問題,可以走集中的。

消息隊列

在計算機科學中,消息隊列(英語:Message queue)是一種進程間通信或同一進程的不同線程間的通信方式,軟件的貯列用來處理一系列的輸入,通常是來自用戶。消息隊列提供了異步的通信協議,每一個貯列中的紀錄包含詳細說明的數據,包含發生的時間,輸入設備的種類,以及特定的輸入參數,也就是說:消息的發送者和接收者不需要同時與消息隊列交互。消息會保存在隊列中,直到接收者取回它。

實際應用場景中,消息隊列也經常作爲中間件,用於異步解耦、削峯填谷、數據廣播、錯峯與流控、最終一致性等,在一些核心的大數據分析、交易支付等場景也經常扮演重要角色,消息隊列的選型主要側重以下幾點:

這裏着重對比一下 Redis、RabbitMQ/RocketMQ、Kafka、Plusar。

Redis

Redis 實現消息隊列可以通過 List 類型、Pub/Sub、Stream(Redis 5.0)類型來實現,HA 使用多副本或者集羣的方式。作爲消息隊列使用起來非常方便,但是也有很多的弊端:

除此之外,List 類型無法支持消息廣播,和 Pub/Sub 一樣也不支持重複消費。結合整體來看 Redis 作爲消息隊列大多數只應用在數據量小,對丟失數據不敏感的業務場景,適用範圍較小,複雜業務並且有一定運維支撐的情況下,可以直接考慮企業級消息中間件。

RabbitMQ vs Kafka vs RocketMQ

這幾個可以作爲企業級消息中間件的代表,RabbitMQ 和 kafka 的一些詳細對比,可以參考之前寫的這篇文章《消息隊列 RabbitMQ 與 Kafka 對比分析 [2]》。而 RocketMQ 在設計之初就借鑑了很多 RabbitMQ、Kafka 的設計理念,例如:Routing、多副本、順序寫(IO),也廣泛應用在淘寶雙十一等場景。

HA

在 HA 方面他們都是通過副本的方式,區別是 RabbitMQ 是集羣級別的副本,Kafka 是多 partiton 和 ISR、選舉機制,而 RocketMQ 通過多(master/slave)副本同時保障 NameServer 和 Broker。

高吞吐

Kafka 和 RocketMQ 通過直接操作文件系統,相比於 RabbitMQ,順序寫能大幅度提升數據的處理速度。Kafka 爲了進一步提升消息的吞吐量,可以採用客戶端緩衝隊列的方式批量發送,但也會存在宕機丟失數據的可能性,可以通過設置 batch.size 與 linger.ms 來動態調整,相比於 RocketMQ 更加靈活。Kafka 的 partition 機制的確會帶來性能的提升,但是在 Topic 不斷增多的情況下,衆多的 partition 及副本也將順序寫逐步退化爲隨機寫,並且擴容時,由於 hash 值的變化,也會涉及到大量 partiton 數據的遷移。RocketMQ 採用 commitlog 的方式實現全局寫,所以能支持更多的 Topic,擴容也不涉及大量數據的遷移。

功能豐富性

Kafka 只有基礎的消息類型,RabbitMQ 支持優先級隊列,通過 TTL 和死信隊列可以實現消息的延遲和重試,但是需要提前創建好對應重試頻率的隊列,例如:1s 重試隊列,10s 重試隊列,RocketMQ 則內置了 18 個重試頻率 “1s 5s 10s 30s 1m 2m……”,另外也具有獨有的 2PL 事務消息,很好的保障業務邏輯與消息發送的一致性。

重複消費

他們三者都採用 ACK 機制保障了單條消息重複消費的能力,Kafka 通過 offset 和 partition 特殊的 ttl 機制(segment 過期,按文件名順序清理),能支持通過重置 offset 來回溯歷史數據。

消息順序性

RabbitMQ 和 RocketMQ 可以保證寫入同一 topic 的順序性,但是在多個消費者同時消費的情況下還是會出現亂序的情況,在數據量較大的時候,我們也可以通過單個消費者消費,再按照一定的分發策略分配給多個消費者執行,只不過會提升整體複雜度,同時會帶來更多的 HA、維護成本考量。Kafka 可以保障單個 partition 的順序性,並且每個 partiton 只允許一個消費者來消費(N:1),這就從策略上避免了多消費者的情況,在數據量較大的情況下,可以通過劃分更多的 partition 提升數據處理能力。綜合來講,RabbitMQ、RocketMQ 使用 Queue 模型,豐富的消息隊列功能,更多的應用在業務場景,Kafka 基於 Streaming 模型,結合批處理、流式處理,更多的應用在大數據分析場景。

Pulsar

Pulsar 作爲 Apache 開源、雲原生的消息中間件,誕生之初就引發了很大的關注。設計上避免了 Kafka 遇到的功能豐富性、擴容等方面的問題,採用計算、存儲分離的架構,broker 層只作爲 “API 接口層”,存儲交給更專業的 bookeeper,由於 broker 層的無狀態性,結合 Kubernetes 等非常方便的進行擴容。並且 Pulsar 支持多個消費模型提升消費者處理能力,例如:exclusive、failover、shared、key-shared 等,可以說綜合了 Kafka 和其他消息中間件的衆多優點。

像能量守恆定律一樣,系統的複雜度往往也是守恆的,實現即高性能又高可用的消息中間件需要的技術複雜性,不會憑空消失,只會從一個地方轉移到另一個地方,消息隊列本質上可以理解爲 feature+fs,只不過存儲、計算分離架構,將各層間的職責分離,使每一層都能專注在自身領域,以應對海量數據和更加複雜多變的環境,這也是現在新技術發展的一個趨勢。作爲後起之秀,的確可以站在巨人的肩膀上,避免很多設計上的不足,同時引入一些新的架構理念,但是要成功的在其中分一杯羹,同樣也要面臨用戶學習成本高、缺少殺手級應用、如何遷移等等這些現實性的問題,不過依靠良好的社區和技術先驅,隨着時間的變遷,這些短板也會逐步補齊,真正適應當前時代的技術一定會脫穎而出。ps:騰訊雲最近開源 Rop[3],支持 RocketMQ 相對平滑的遷移至 Pulsar。

相關鏈接:

  1. https://github.com/open-telemetry/opentelemetry-collector-contrib/tree/main/processor/tailsamplingprocessor

  2. https://blog.xstudio.mobi/a/60.html

  3. https://github.com/streamnative/rop

作者:李東輝

來源:https://blog.xstudio.mobi/a/230.html

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/fX-vp7cDbgL8wrz6TD7B_w