架構師圖譜之微服務 - 消息隊列
概述
“架構師圖譜” 是一個很宏大的命題,特別是優秀的架構師自身也是 “由點到面再到圖”,一點點成長積累起來,嘗試寫這篇文章的目的更多的是結合自身的一些架構、研發、管理經驗對現階段做一個覆盤總結,所以這裏更偏向於後端圖譜,依賴於開源技術、雲原生或者其他第三方服務。這裏會重點介紹一些技術棧、設計理念以及適應場景,這些可以作爲我們選型時的依據。所謂 “架構即決策”,是在一個有約束的盒子中尋求最優解。這個有約束的盒子是團隊經驗、成本、資源、進度、業務所處階段等編織、摻雜在一起的綜合體。本質上無優劣,但是存在恰當的架構用在合適的軟件系統中,而這些就是決策的結果。
序章
一個技術圖譜:
計劃會分上、中、下三個篇章來介紹:
-
上篇:重點聚焦在微服務和常用的消息隊列,包括相關的選型以及一些理論基礎
-
中篇:主要集中在數據庫、分佈式(一致性 / 鎖 / 緩存 / 發號 / 任務調度等),以及流媒體
-
下篇:分享一些 DevOps、項目管理、團隊建設方向的一些經驗
完整的思維導圖:
微服務(英語: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 也不是銀彈,特別是在一些新業務場景,本身就充滿了很多的不確定性,一次性把邊界劃清楚並不是一件很容易的事。大家在一個進程裏,調整起來會相對容易,然後讓不同的界限上下文各自演化,等到了一定程度之後再考慮微服務也是一個不錯的選擇。
作爲微服務的統一入口,也肩負着整個微服務的流量接入、管理、聚合、安全等,從服務分層的角度可以劃分爲接入網關和業務網關。接入網關接入網關提供最基礎的流量接入和安全防護能力,側重於全局,與業務無關。
-
域名 & DNS,作爲服務的流量入口,對外通過域名和 DNS 提供服務,國內域名廠商一般都依託於共有云或被共有云廠商收購,用來完善自由的雲生態,像阿里的萬網,騰訊的 DNSPod 等,也有國外的 AWS,GoDaddy 和 Namecheap 等,可以用作. me 等國內無法託管或備案域名的管理,其次也可以藉助 DNS(HTTPDNS、EDNS)實現跨地域、運營商網絡等負載均衡,實現異地多活、就近訪問、容災等。
-
負載均衡(LB),主要負責請求的轉發代理,按機器負載來分配流量等,對外提供 VIP,這裏的負載可以寬泛的理解爲系統的壓力,可以用 CPU 負載來衡量,也可以用連接數、I/O 使用率、網卡吞吐量等來衡量。負載均衡器按服務層級來劃分,除了前邊提到的 DNS,還有集羣級別的硬件負載均衡,以及機器級別的軟件負載均衡。
-
DNS / 硬件負載均衡(F5/A10)主要用來應對海量用戶的訪問,中小量用戶使用無疑會增加更多的維護和採購成本。
-
軟件負載均衡可以選擇自研或上雲,LVS、Keepalived 主要用於四層(IP + 端口)的負載均衡,在四層的基礎之上如果要實現應用層(域名 / URL / 用戶會話)等的 7 層負載均衡,可以使用 Nginx、Keepalived 的組合。
-
除此之外,網關也負責服務整體的安全防護,SSL,IPV6 等。
-
安全防護目的是保護服務數據以及可用性,例如防範常見的 DDOS/CC 網絡攻擊,反爬蟲,自定義訪問控制,自研成本往往比較高,可以藉助雲上一系列的高防、防火牆服務。
-
SSL(TLS)用來提供外部 https 訪問,https 可以防止數據在傳輸過程中不被竊取、改變,確保數據的完整性,在支付或者用戶登錄等敏感數據場景,可以起到一定的保護作用,同時 https 頁面對搜索引擎也比較友好。
-
IPV6,全球 43 億 IPV4 地址已經在 2019 年年底耗盡,網信辦在 2018 年開始就已經推行各大運營商、CDN 廠商、互聯網核心產品支持 IPV6,我們公司之前也是試點之一。IPV6 的支持只需要增加一條 “AAAA”DNS 記錄,將域名解析到自持 IPV6 的 IP/VIP 即可。IPV4 到 IPV6 由於存在兼容性等問題,一定是長期共存的,過渡方案可以採用 IPV6 代理(IPV6 代理轉發到 IPV4 服務)或者雙棧(同時支持 IPV6 和 IPV4)。
業務網關:
業務網關作爲業務的最上層出口,一般承擔起業務接入或者 BFF 的工作,例如基礎的路由、鑑權、限流、熔斷降級、服務聚合、插件化能力,並可以通過可視化界面管理網關配置。可選框架有基於 OpenResty 的 Kong、APISIX 以及其他語言相關的 Spring Cloud Gateway、gRPC-Gateway 等等,國內開源的 Goku、Kratos、go-zero go 框架,有很多比較有意思的組件實現,我們日常業務上也可以借鑑。
-
鑑權,鑑權的目的是爲了驗證用戶、請求等的有效性,例如用戶身份鑑權(JWT/Oauth2/Cookie),請求鑑權(請求籤名、請求加密),鑑權邏輯也花樣繁多,大多需要基於業務定製化,通過網關插件能很好的集成進來。
-
限流,限流是爲了做一定的流量控制,防止對系統產生過大壓力從而影響整個服務。可以基於單臺機器或整個集羣限流,常見的方式有限制總量和限制速率,超過的則排隊或丟棄,例如令牌桶(彈性)/ 漏桶(勻速)算法。
-
熔斷降級,熔斷作爲服務斷路器,當下遊的服務因爲某種原因突然變得不可用或響應過慢(這裏既可以指單次請求也可以指一段時間),上游服務爲了保證自己整體服務的可用性,不再繼續調用目標服務,直接返回,這樣也能對整體鏈路起到保護作用。如果目標服務情況好轉則恢復調用,同時結合降級策略提升服務的魯棒性。常見的有 Hystrix/Resilience4J(Hystrix 雖然已停止更新,但現有功能已經能滿足大多業務場景)。
-
重試,大量網絡 IO,避免不了會出現因網絡抖動,出現連接失敗或者超時,重試可以提高請求的最終成功率,削平服務毛刺。但重試也有可能放大故障,所以可以結合退避策略(backoff)、限制單點重試、限制鏈路重試這些策略進行優雅的重試,同時也可以採用更加激進的 “對沖請求” 提前(tp99 時間未響應時)發起重試請求,降低系統時延。
-
插件化,各個網關集成插件的方式盡不相同,但是目的都是爲了集成技術人員編寫的一些業務相關的通用能力,例如前邊提到的身份鑑權、請求鑑權等等。另外作爲業務網關插件,也可以編寫一些基礎業務(API 鑑權、請求格式化)邏輯,直接透傳請求到服務層,省去很多 BFF 和上下游對接的工作。
-
BFF,Backend For Frontend,可以按照業務邏輯,以串行、並行和分支等結構編排多個服務 API,爲服務提供聚合、適配、裁剪(只返回需要的字段)功能,核心是 API 的動態編排以滿足日益增長的業務邏輯,降低前端與微服務之間的對接成本。BFF 並不意味着只能由後端實現,也可以在前端通過 GraphQL 等 API 查詢語言實現。
服務間的通信方式是在採用微服務架構時需要做出一個最基本的決策,統一的協議標準也能大大降低服務的聯調和維護成本。
-
HTTP REST,REST 更確切的講是指的 API 設計風格,而不是協議標準。通常基於使用 HTTP,URL,和 JSON 這些現有的廣泛流行的協議和標準。符合 REST 設計風格的 API 稱作 RESTful API。在實際應用中大多實現的是僞 REST API,例如用 POST 請求同時實現資源的增刪改,或者爲了請求的擴展性,資源的增刪改查都使用 POST JSON。
-
RPC,RPC 協議描繪了客戶端與服務端之間的點對點調用流程,包括 stub、通信、RPC 消息協議部分。可以基於 TCP,也可以基於 http。在實際應用中,還需要考慮服務的高可用、負載均衡等問題,所以產品級的 RPC 框架除了點對點的 RPC 協議的具體實現外,還應包括服務的發現與註冊、提供服務的多臺 Server 的負載均衡、服務的高可用等更多的功能。目前的 RPC 框架大致有兩種不同的側重方向,一種偏重於服務治理(Dubbo、Motan),另一種偏重於跨語言調用(Thrift/GRPC)。
RPC vs HTTP REST 優點:
-
更清晰的 API 定義,例如 gRPC 協議的定義文件 proto,自身就可以作爲很好的 API 文檔,日常開發中也可以把 proto 文件獨立版本庫管理,精簡目錄結構,方便不同的服務引用。
-
更好的傳輸效率,通過序列化和反序列化進一步壓縮網絡傳輸數據,不過序列化、反序列化也會有一定的性能損耗,protobuf 可以說很好的兼顧了這兩點。
-
更合適的容錯機制,可以基於實際的業務場景,實現更合適的超時控制與異常重試機制,以應對網絡抖動等對服務造成的影響。
在一些特定場景,例如:OpenAPI、BFF 等,HTTP REST 可以更大程度上降低外部團隊的接入成本。並且 RPC 也有調試不便、多語言互通需要對應的 SDK 支持這些問題,各有利弊。綜合考慮來看,除了一些特定場景,如果我們已經有相對完善的基礎設施支撐(RPC 框架、服務治理),RPC 可以爲一個更合適的選擇。
服務註冊 / 發現
服務註冊主要是通過將微服務的後端機器 IP、端口、地域等信息註冊起來,並結合一定的發現機制使客戶端的請求能夠直連具體的後端機器。從實現方式上可以分爲服務端模式與客戶端模式:
-
服務端模式,也可以說是傳統模式,通過藉助負載均衡器和 DNS 實現,負載均衡器負責健康檢查、負載均衡策略,DNS 負責實現訪問域名到負載均衡器 IP/VIP 的映射。通過直接暴露域名和端口的方式提供客戶端訪問。
-
客戶端模式,可以藉助註冊中心實現,註冊中心負責服務的註冊與健康檢查,客戶端通過監聽配置變更的方式及時把配置中心維護的配置同步到本地,通過客戶端負載均衡策略直接向後端機器發起請求。
從兩種模式的實現方式上可以看出:
-
服務端模式註冊與發現都由服務端完成,這樣可以使客戶端專注在自身的業務實現,但是由於依賴負載均衡器,也就是集中式的 proxy,proxy 需要維護雙向連接,也很容易使自己成爲系統瓶頸,可用性的高低直接決定了服務質量,並且 DNS 緩存機制也會導致故障發生時,遷移並不能及時完成。當然在服務量少,且負載均衡器有 VIP 的情況下,我們也可以不使用 DNS。
-
客戶端模式註冊與發現由配置中心和客戶端共同完成,通過分佈式的方式,可以避免出現 proxy 節點性能瓶頸問題,但是可靠性與性能瓶頸很容器出現在配置中心上,並且客戶端的也需要一定的接入成本。好在開源的已經有很成熟的架構方案與豐富的客戶端 SDK,例如 etcd/ZooKeeper/Consul,Consul 提供開箱即用的功能,etcd 社區和接入易用性方面更優一些,他們之間的一些具體區別:
配置中心
配置中心從使用場景來講,一類是前邊講到的服務註冊、發現和 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 中日誌級別:
-
Emergency:system is unusable
-
Alert:action must be taken immediately
-
Critical:critical conditions
-
Error:error conditions
-
Warning:warning conditions
-
Notice:normal but significant condition
-
Informational:informational messages
-
Debug:debug-level messages
在實際使用過程中可能會對日誌級別進行簡化和調整,一般來講 Warning 及以上的日誌是需要重點關注的,需要做好及時的監控告警,Warning 以下的日誌也可以輔助問題的定位。日誌寫入可以選擇寫入消息隊列,也可以選擇落地磁盤,將關心的結構化或非結構化日誌、業務模塊信息(如果是細粒度的微服務,可以選擇將日誌放同一模塊收集),以及級別、時間(who、when、where、how、what)等要素正確的寫入正確寫入後再收集到日誌服務。寫入消息隊列需要考慮消息隊列的選型以及做好可用性和積壓監控,寫入磁盤需要考慮寫入性能以及日誌的切割清理,例如 Golang 的 zap+rotatelogs 組合。日誌收集的話,由於 Logstash 資源消耗相對比較大,虛擬機環境中可以使用 Filebeat 來替代,更嚴苛的線上或容器環境,可以使用 Fluentd/Fluentd Bit。日誌最終彙總到 ES 和 Kibana 做展示,通過 Esalert 定製告警策略。
大數據日誌:大數據日誌本質上也對應着我們一定的業務場景,但大多是海量日誌、高吞吐量場景,所以對海量日誌的收集和存儲是較大的挑戰。實現方案我們可以採用高吞吐量的流式中間件,例如 Kafka/Plusar 等,在結合流式處理(Flink)或者批處理(Spark)系統,將數據彙總到 Hadoop 進行分析,這裏涉及到的中間件和數據庫可參考後續章節。
指標
指標是有關係統的離散的數據點,這些指標通常表示爲計數或度量,並且通常在一段時間內進行彙總或計算,一般用來做基礎的資源監控和業務監控:
-
資源監控:CPU、內存、IO、fd、GC 等
-
業務監控:QPS、模調、耗時分佈等
Zabbix 作爲老牌的監控系統,適合更復雜的物理機、虛擬機、數據庫等更復雜的場景,同時也擁有更豐富的圖形化界面,但是 Prometheus 作爲雲原生的代表作,與 Kubernetes、容器等能更好的結合,協同 Grafana 實現可定製化的界面,另外存儲基於 TSDB,相比於關係型數據庫也有更好的擴展性。以 Prometheus 爲例,支持的數據類型有:
-
Counter 只增不減的計數器,例如請求數(http_requests_total)。基於此數據模型,使用 Prometheus 提供的強大 PromQL 表達式能夠拓展出更加適合開發觀察的指標數據。分鐘增量請求:increase(http_requests_total[1m]) 分鐘 QPS:rate(http_requests_total[1m])
-
Gauge 可增可減的時刻量,例如 Go 語言協程數(go_goroutines) 波動量:delta(go_goroutines[10m])
-
Histogram 直方圖,不同區間內樣本的個數。例如,耗時 50ms-100ms 每分鐘請求量,100ms-150ms 每分鐘請求量。
-
Summary 概要,反應百分位值。例如,某 RPC 接口,95% 的請求耗時低於 150ms,99% 的請求耗時低於 200ms。
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)是一種進程間通信或同一進程的不同線程間的通信方式,軟件的貯列用來處理一系列的輸入,通常是來自用戶。消息隊列提供了異步的通信協議,每一個貯列中的紀錄包含詳細說明的數據,包含發生的時間,輸入設備的種類,以及特定的輸入參數,也就是說:消息的發送者和接收者不需要同時與消息隊列交互。消息會保存在隊列中,直到接收者取回它。
實際應用場景中,消息隊列也經常作爲中間件,用於異步解耦、削峯填谷、數據廣播、錯峯與流控、最終一致性等,在一些核心的大數據分析、交易支付等場景也經常扮演重要角色,消息隊列的選型主要側重以下幾點:
-
HA:自身的高可用性保障,避免消息隊列的引入而影響整體服務的可用性
-
高吞吐:在面對海量數據寫入能否保持一個相對穩定、高效的數據處理能力
-
功能豐富性:是否支持延遲消息、事務消息、死信隊列、優先級隊列等
-
消息廣播:是否支持將消息廣播給消費者組或者一組消費者
-
消息堆積能力:在數據量過大時,是否允許一定消息堆積到 broker
-
數據持久性:數據持久化策略的採用,也決定着數據在宕機恢復後是否會丟失數據
-
重複消費:是否支持 ack 機制,在消費者未正確處理消息時,支持重新消費
-
消息順序性:針對順序消費的場景保證數據按寫入時間的順序性
這裏着重對比一下 Redis、RabbitMQ/RocketMQ、Kafka、Plusar。
Redis
Redis 實現消息隊列可以通過 List 類型、Pub/Sub、Stream(Redis 5.0)類型來實現,HA 使用多副本或者集羣的方式。作爲消息隊列使用起來非常方便,但是也有很多的弊端:
-
功能豐富性:只支持普通的消息類型
-
數據持久性:Pub/Sub 只提供緩衝區廣播能力,不進行持久化,List/Stream 即使基於 aof 和 rdb 持久化策略,但是並沒有事務性保障,在宕機恢復後還是存在丟失數據的可能性
-
消息堆積能力:List 隨長度增大,內存不斷增長;Pub/Sub 只在緩衝區內堆積,緩衝區滿消費者強制下線;Stream 創建時可以指定隊列最大長度,寫滿後剔除舊消息
除此之外,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 和其他消息中間件的衆多優點。
-
HA、高吞吐:和 Kafka 類似,通過多 partition 和選舉機制功,除此之外,還支持豐富的跨地域複製能力
-
功能豐富性:可以支持秒級的延遲消息,以及獨特的重試隊列和私信隊列
-
消息順序性:爲了實現 partition 消息的順序性,和 Kafka 一樣,都需要將消息寫入到同一 broker,區別是 Kafka 會同時存儲消息在該 broker,broker 和 partiton 綁定在一起,而 Pulsar 可以將消息分塊(segment)後,更加均勻的分散到 bookeeper 節點上,broker 只需要記錄映射關係即可,這樣在資源擴容時,可以更加快速便捷
像能量守恆定律一樣,系統的複雜度往往也是守恆的,實現即高性能又高可用的消息中間件需要的技術複雜性,不會憑空消失,只會從一個地方轉移到另一個地方,消息隊列本質上可以理解爲 feature+fs,只不過存儲、計算分離架構,將各層間的職責分離,使每一層都能專注在自身領域,以應對海量數據和更加複雜多變的環境,這也是現在新技術發展的一個趨勢。作爲後起之秀,的確可以站在巨人的肩膀上,避免很多設計上的不足,同時引入一些新的架構理念,但是要成功的在其中分一杯羹,同樣也要面臨用戶學習成本高、缺少殺手級應用、如何遷移等等這些現實性的問題,不過依靠良好的社區和技術先驅,隨着時間的變遷,這些短板也會逐步補齊,真正適應當前時代的技術一定會脫穎而出。ps:騰訊雲最近開源 Rop[3],支持 RocketMQ 相對平滑的遷移至 Pulsar。
相關鏈接:
-
https://github.com/open-telemetry/opentelemetry-collector-contrib/tree/main/processor/tailsamplingprocessor
-
https://blog.xstudio.mobi/a/60.html
-
https://github.com/streamnative/rop
作者:李東輝
來源:https://blog.xstudio.mobi/a/230.html
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/fX-vp7cDbgL8wrz6TD7B_w