網易蜂巢微服務架構:用RabbitMQ實現輕量級通信


本次分享內容由三個部分組成:

  • 微服務架構與MQ

  • RabbitMQ場景分析與優化

  • RabbitMQ在網易蜂巢中的應用和案例分享

 

1微服務架構與MQ

 

微服務架構是一種架構模式,它將單體應用划分成一組微小的服務,各服務之間使用輕量級的通信機制交互。

 

 

上圖左邊是單體架構應用,把所有業務功能放在單個進程中,需要擴展時以進程為單位水平復制到多台機器。

 

上圖右邊是微服務架構應用,將每個業務功能以獨立進程(服務)的方式部署,可以按需將微服務分別部署在多台機器,實現水平擴展。

 

微服務各服務之間使用“輕量級”的通信機制。所謂輕量級,是指通信協議與語言無關、與平台無關。

 

微服務通信方式:

  1. 同步:RPC,REST等

  2. 異步:消息隊列

 

同步通信方式

優點:

  1. 實現方便。

  2. 協議通用,比如HTTP大家都非常熟知。

  3. 系統架構簡單,無需中間件代理。

缺點:

  1. 客戶端耦合服務方。

  2. 通信雙方必須同時在線,否則會造成阻塞。

  3. 客戶端需要知道服務方的Endpoint,或者需要支持服務發現機制。

 

異步通信方式

優點:

  1. 系統解耦和。

  2. 通信雙方可以互相對對方無感知。

缺點:

  1. 額外的編程復雜性。比如需要考慮消息可靠傳輸、高性能,以及編程模型的變化等。

  2. 額外的運維復雜性。比如消息中間件的穩定性、高可用性、擴展性等非功能特性。

 

今天的主題是消息隊列在微服務架構中的應用與實踐。

 

消息隊列中間件如何選型?主要會考慮以下幾點:

  1. 協議:AMQP、STOMP、MQTT、私有協議等

  2. 消息是否需要持久化

  3. 吞吐量

  4. 高可用支持,是否單點

  5. 分布式擴展能力

  6. 消息堆積能力和重放能力

  7. 開發便捷,易於維護

  8. 社區成熟度

 

選擇RabbitMQ的原因:

  1. 開源,跨平台

  2. 靈活的消息路由策略

  3. 持久化,消息可靠傳輸

  4. 透明集群,HA支持

  5. 支持高性能高並發訪問

  6. 支持多種消息協議

  7. 豐富的客戶端、插件和平台支持

  8. 支持RPC解決方案

 

2RabbitMQ場景分析與優化

 

RabbitMQ是一個實現了AMQP(高級消息隊列協議)協議的消息隊列中間件。

 

AMQP基本模型:

 

1. Queue

2. Exchange: Direct, Fanout, Topic, Header

3. Binding: BindingKey, RouteKey

 

 

總結:生產者將消息發送到Exchange,Exchange通過匹配BindingKey和消息中的RouteKey來將消息路由到隊列,最后隊列將消息投遞給消費者。

 

消息可靠傳輸是業務系統接入MQ時首先要考慮的問題。一般消息可靠性有三個等級:

  1. At most once: 最多一次

  2. At least once: 最少一次

  3. Exactly once: 恰好一次

 

RabbitMQ支持其中的“最多一次”和“最少一次”兩種。其中“最少一次”投遞實現機制:

 

  1. 生產者confirm。如何開啟:使用confirm.select

  2. 消息持久化。

  3. 消費者ack。如何開啟:使用basic.consume(…, no-ack=false)

 

 

這里說下RabbitMQ消息持久化(寫磁盤)的兩個場景:

  1. 顯式指定消息屬性:delivery-mode=2

  2. 內存吃緊時,消息(包括非持久化消息)轉存到磁盤,由memory_high_watermark_paging_ratio參數指定閾值。

 

RabbitMQ的消息持久化是通過以下機制實現的:

  1. 消息體寫文件

  2. 異步刷盤,合並請求,減少fsync系統調用次數

  3. 當進程mailbox沒有新消息時,實時刷盤

  4. confirm機制下,等fsync系統調用完成后返回basic.ack確認

 

RabbitMQ開啟消息可靠性參數需要注意:

  1. unack消息在服務器端沒有超時,只能等待客戶端連接斷開,重新入隊等待投遞。

  2. 消息存在重復投遞的情況,需客戶端去重:

    a)基於業務層的MsgId。

    b)基於RabbitMQ的Redelivered flag標記: 不完全靠譜,仍舊可能收到重復消息。

  3. 保障性能:

    a)批量publish, ack。

    b)更快的磁盤(SSD,RAID等)。

    c)少堆積。

  4. 消息亂序。

 

生產者confirm機制是三個可靠性參數中對性能影響最大的。一般來說有三種編程方式:

  1. 普通confirm模式。每發送一條消息后,調用waitForConfirms()方法,等待服務器端confirm。實際上是一種串行confirm。

  2. 批量confirm模式。每次發送一批消息后,調用waitForConfirms()方法,等待服務器端confirm。

  3. 異步confirm模式。注冊一個回調方法,服務器端confirm了一條(或多條)消息后SDK會回調這個方法。

 

下面是一些生產者confirm機制的專項性能測試數據:

 

 

總結:

  1. 遵循線程數越大,吞吐量越大的規律。當線程數量達到一個閾值之后,吞吐量開始下降。

  2. 不論哪種confirm模式,通過調整客戶端線程數量,都可以達到一個最大吞吐量值。

  3. 異步和批量confirm模式兩者沒有明顯的性能差距。所以,只需從可編程性的角度選擇異步或批量或者兩者結合的模式即可。相比而言,普通confirm模式只剩編程簡單這個理由了。

 

下面講下RabbitMQ的高可用機制。

 

官方提供的高可用方案:cluster + ha policy

 

cluster機制:多個全聯通節點之間元信息(exchange、queue、binding等)保持強一致,但是隊列消息只會存儲在其中一個節點。

 

 

優點:提高吞吐量,部分解決擴展性問題。

缺點:不能提升數據可靠性和系統可用性。

 

ha policy機制:在cluster機制基礎上可以指定集群內任意數量隊列組成鏡像隊列,隊列消息會在多節點間復制。實現數據高可靠和系統高可用。

 

 

設置參數:ha-mode和ha-params可以細粒度(哪些節點,哪些隊列)設置鏡像隊列。

設置參數:ha-sync-mode=manual(默認)/automatic可以指定集群中新節點的數據同步策略。

 

有一點需要注意:鏡像隊列對網絡抖動非常敏感,默認參數配置下,出現腦裂后RabbitMQ集群不會自我恢復,需要人工介入恢復,務必加好監控和報警。

 

RabbitMQ流控機制  流控類型:

  1. 內存流控:由vm_memory_high_watermark參數控制,默認0.4

  2. 磁盤流控:由disk_free_limit參數控制,默認50M

  3. 單條連接流控:觸發條件是下游進程的處理速度跟不上上游進程。

 

RabbitMQ內部進程關系調用圖

 

注意:當觸發流控(全局內存/磁盤流控,單條連接流控)時,生產者端的publish方法會被阻塞,生產者需要做的是:

  1. 注冊block事件,被流控時,會收到一個回調通知。

  2. 異步化處理生產者發送消息,不要阻塞主流程。

 

3RabbitMQ在網易蜂巢中的應用和案例分享

 

網易蜂巢平台的服務化架構如下,服務間通過RabbitMQ實現通信:

 

 

 

網易蜂巢消息服務器設計目標:實現一個路由靈活、數據可靠傳輸、高可用、可擴展的消息服務器。

 

 

設計要點:

  1. Exchange類型為topic。

  2. BindingKey就是隊列名。

  3. 每個服務(圖例中的REPO/CTRL/WEB等)啟動后會初始化一條AMQP連接,由3個channel復用:一個channel負責生產消息,一個channel從TYPE(REPO/CTRL/WEB等)類型的隊列消費消息,一個channel從TYPE.${hostname}類型的隊列消費消息。

 

應用場景舉例:

 

  1. 點對點(P2P)或請求有狀態服務:消息的RouteKey設置為TYPE.${HOSTNAME}。如host1上的WEB服務需要向host2上的REPO服務發送消息,只需將消息的RouteKey設置為REPO.host2投遞到Exchange即可。

  2. 請求無狀態服務:如果服務提供方是無狀態服務,服務調用方不關心由哪個服務進行響應,那么只需將消息的RouteKey設置為TYPE。比如CTRL是無狀態服務,host1上的WEB服務只需將消息的RouteKey設置為CTRL即可。CTRL隊列會以Round-Robin的均衡算法將消息投遞給其中的一個消費者。

  3. 組播:如果服務調用方需要與某類服務的所有節點通信,可以將消息的RouteKey設置為TYPE.*,Exchange會將消息投遞到所有TYPE.${HOSTNAME}隊列。比如WEB服務需通知所有CTRL服務更新配置,只需將消息的RouteKey設置為CTRL.*。

  4. 廣播:如果服務調用方需要與所有服務的所有節點通信,也就是說對當前系統內所有節點廣播消息,可以將消息的RouteKey設置為*.*。

 

總結:通過對RouteKey和BindingKey的精心設計,可以滿足點對點(私信)、組播、廣播等業務場景的通信需求。

 

優缺點分析:

優點:

  1. 路由策略靈活。 

  2. 支持負載均衡。

  3. 支持高可用部署。

  4. 支持消息可靠傳輸(生產者confirm,消費者ack,消息持久化)。

  5. 支持prefetch count,支持流控。

缺點:

  1. 存在消息重復投遞的可能性。

  2. 超時管理,錯誤處理等需業務層處理。

  3. 對於多服務協作的場景支持度有限。比如以下場景:WEB=> CTRL=>REPO=>CTRL=> WEB。這個時候就需要CTRL緩存WEB請求,直至REPO響應。

 

實踐案例分享

案例一:GC引起的MQ crash

1.環境參數:

 

 

2.現象:

RabbitMQ崩潰,產生的erl_crash.dump文件內容如下:

 

 

3.直接原因:

從數據來看,虛擬機內存共4G,Erlang虛擬機已占用約1.98G內存(其中分配給Erlang進程的占1.56G),此時仍向操作系統申請1.82G,因為操作系統本身以及其他服務也占用一些內存,當前系統已經分不出足夠的內存了,所以Erlang虛擬機崩潰。

 

4.分析:

本例有兩個不符合預期的數據:
1. 內存流控閾值控制在1.67G左右(4G*0.4),為什么崩潰時顯示占用了1.98G?
2. 為什么Erlang虛擬機會額外再申請1.82G內存?

 

因為:

  1. RabbitMQ每個隊列設計為一個Erlang進程,由於進程GC也是采用分代策略,當新老生代一起參與Major GC時,Erlang虛擬機會新開內存,根據root set將存活的對象拷貝至新空間,這個過程會造成新老內存空間同時存在,極端情況下,一個隊列可能短期內需要兩倍的內存占用量。這就是RabbitMQ將內存流控的安全閾值默認設置為0.4的原因。

  2. 內存流控參數vm_memory_high_watermark 為0.4的意思是,當RabbitMQ的內存使用量大於40%時,開始進行生產者流控,但是該參數並不承諾RabbitMQ的內存使用率不大於40%。

 

5.如何解決(規避)?

  1. RabbitMQ獨立部署,不與其他Server共享內存資源。

  2. 進一步降低vm_memory_high_watermark值,比如設置成0.3,但是這種方式會造成內存資源利用率太低。

  3. 升級RabbitMQ至新版(3.4+),新版RabbitMQ對內存管理有過改進。

 

案例二:鏡像隊列的單節點磁盤故障造成消息丟失

1.環境參數:RabbitMQ: 3.1.5 (ha policy, ha-mode:all)

 

2.現象:

a)節點A的數據盤故障(磁盤控制器故障、無法讀寫),所有原本A上的生產者消費者failover到B節點。

b)從節點B的WebUI上看,所有隊列信息(包括隊列元信息和數據)丟失,但是exchange、binding、vhost等依舊存在。

c)節點B的日志中出現大量關於消費請求的錯誤日志:

 

 

d)從生產者端看來一切正常,依舊會收到來自節點B的confirm消息(basic.ack amqp方法)。

 

3.分析:

上述現象實際上有兩個坑在里面:

  1. 在數據可靠傳輸方面,鏡像隊列也不完全可靠。這是一個Bug。RabbitMQ 3.5.1版本以下都存在這個問題。

  2. 要保證消息可靠性,生產者端僅僅采用confirm機制還不夠。對於那些路由不可達的消息(根據RouteKey匹配不到相應隊列),RabbitMQ會直接丟棄消息,同時confirm客戶端。

 

4.如何解決(規避)?

  1. 升級RabbitMQ到新版,至少3.5.1及以上。

  2. 生產者basic.publish方法設置mandatory參數,它的作用是:對於那些路由不可達的消息,RabbitMQ會先通過basic.return消息向生產者返回消息內容,然后再發送basic.ack消息進行確認

 

 

Q & A

 

 

Q1為保障消息隊列穩定性,消息隊列監控點主要有哪些?

A1首先是服務器的基礎監控是要的。CPU,內存,磁盤IO都是敏感項。特別是內存,報警閾值可能要設置在50%以下,原因前面也說過,極端情況下一個隊列的內存占用量可能是兩倍當前值。

然后MQ業務的監控也需要加,比如消息堆積數,unack的消息數,連接數,channel數等等。RabbitMQ有提供rest api可以拿到這些監控。

還有因為RabbitMQ對網絡分區非常敏感,所以日志監控也要加。RabbitMQ出現網絡分區或者觸發流控都會在它的運行日志中有輸出。

大致就是這么幾點。

 

Q2rabbitmq有嘗試結合netty提高網絡處理能力?

A2erlang是actor模型代表,我覺得它的網絡處理能力也不比netty差的。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM