|
導(dǎo)讀: 同程藝龍的機票、火車票、汽車票、酒店相關(guān)業(yè)務(wù)已經(jīng)接入了RocketMQ,用于流量高峰時候的削峰,以減少后端的壓力。同時,對常規(guī)的系統(tǒng)進行解耦,將一些同步處理改成異步處理,每天處理的數(shù)據(jù)達1500億條。 在近期的Apache RockeMQ Meetup上,同程藝龍機票事業(yè)部架構(gòu)師查江,分享了同程藝龍的消息系統(tǒng)如何應(yīng)對每天1500億條的數(shù)據(jù)處理,通過此文,您將了解到:
同程藝龍在消息方面的使用情況RocketMQ集群分為 Name Server 和Broker兩部分,Name Server用的是雙主模式,一個是考慮性能,另一個考慮安全性。 在純數(shù)據(jù)的Broker分成很多組,每個組里面分為Master和Slave。目前,我們的機票、火車票、汽車票、酒店相關(guān)業(yè)務(wù)已經(jīng)接入了RocketMQ,用于流量高峰時候的削峰,以減少后端的壓力。同時,對常規(guī)的系統(tǒng)進行解耦,將一些同步處理改成異步處理,每天處理的數(shù)據(jù)達1500億條。 選擇RocketMQ的原因是:
消息在同程藝龍的應(yīng)用場景退訂系統(tǒng) 這個是我們退訂系統(tǒng)中的一個應(yīng)用場景。用戶點擊前端的退訂按鈕,系統(tǒng)調(diào)用退訂接口,再去調(diào)用供應(yīng)商的退訂接口,從而完成一個退訂功能。 如果供應(yīng)商的系統(tǒng)接口不可靠,就會導(dǎo)致用戶退訂失敗,如果系統(tǒng)設(shè)置為同步操作,會導(dǎo)致用戶需要再去點一次。所以,我們引入RocketMQ將同步改為異步,當(dāng)前端用戶發(fā)出退訂需求,退訂系統(tǒng)接收到請求后就會記錄到退訂系統(tǒng)的數(shù)據(jù)庫里面,表示這個用戶正在退訂,同時通過消息引擎把這條退訂消息發(fā)送到和供應(yīng)商對接的系統(tǒng),去調(diào)用供應(yīng)商的接口。 如果調(diào)用成功,就會把數(shù)據(jù)庫進行標(biāo)識,表示已經(jīng)退訂成功。同時,加了一個補償?shù)哪_本,去數(shù)據(jù)庫撈那些未退訂成功的消息,重新退訂,避免消息丟失引起的退訂失敗的情況。 房倉系統(tǒng) 第二個應(yīng)用場景是我們的房倉系統(tǒng)。這是一個比較常規(guī)的消息使用場景,我們從供應(yīng)商處采集一些酒店的基本信息數(shù)據(jù)和詳情數(shù)據(jù),然后接入到消息系統(tǒng),由后端的分銷系統(tǒng)、最小價系統(tǒng)和庫存系統(tǒng)來進行計算。同時當(dāng)供應(yīng)商有變價的時候,變價事件也會通過消息系統(tǒng)傳遞給我們的后端業(yè)務(wù)系統(tǒng),來保證數(shù)據(jù)的實時性和準(zhǔn)確性。 供應(yīng)庫的訂閱系統(tǒng) 數(shù)據(jù)庫的訂閱系統(tǒng)也用到了消息的應(yīng)用。一般情況下做數(shù)據(jù)庫同步,都是通過binlog去讀里面的數(shù)據(jù),然后搬運到數(shù)據(jù)庫。搬運過程中我們最關(guān)注的是數(shù)據(jù)的順序性,因此在數(shù)據(jù)庫row模式的基礎(chǔ)上,新增了一個功能,以確保每一個Queue里面的順序是唯一的。 雖然Queue里面的順序天然都是唯一的,但我們在使用上有一個特點,就是把相同ID的消息都是放在同一個Queue里面的。例如,圖中右上角id1的消息,數(shù)據(jù)庫主字段是id1,就統(tǒng)一放在Queue1里面,而且是順序的。在Queue2里,兩個id3之間被兩個順序的id2間隔開來了,但實際消費讀出來的時候,也會是順序的,由此,可以用多隊列的順序來提高整體的并發(fā)度。 我們踩過哪些坑供應(yīng)商系統(tǒng)的場景 上圖中,一個MQ對應(yīng)有兩個消費者,他們是在同一個Group1中,起初大家都只有Topic1,這時候是正常消費的。但如果在第一個消費者里面加入一個Topic2,這時候是無法消費或消費不正常了。這是RocketMQ本身的機制引起的問題,需要在第二個消費者里面加入Topic2才能正常消費。 支付交易系統(tǒng)的場景 另外一個是支付交易系統(tǒng),這個場景下也是有兩個應(yīng)用,他們都是在同一Group和同一Topic下,一個是消費Tag1的數(shù)據(jù),另一個是消費Tag2的數(shù)據(jù)。正常情況下,啟動應(yīng)該是沒問題的,但是有一天我們發(fā)現(xiàn)一個應(yīng)用起不來了,另外一個應(yīng)用,因為他只消費Tag2的數(shù)據(jù),但是因為RocketMQ的機制會把Tag1的數(shù)據(jù)拿過來,拿過來過后會把Tag1的數(shù)據(jù)丟棄。這會導(dǎo)致用戶在支付過程中出現(xiàn)支付失敗的情況。 對此,我們把Tag2放到Group2里面,兩個Group就不會消費相同的消息了。個人建議RocketMQ能夠?qū)崿F(xiàn)一個機制,即只接受自己的Tag消息,非相關(guān)的Tag不去接收。 大量老數(shù)據(jù)讀取的場景 在火車票消費的場景中,我們發(fā)現(xiàn)有200億條老數(shù)據(jù)沒有被消費。當(dāng)我們消費啟動的時候,RocketMQ會默認從第0個數(shù)據(jù)開始讀,這時候磁盤IO飆升到100%,從而影響到其他消費端數(shù)據(jù)的讀取,但這些老數(shù)據(jù)被加載后后,并沒有實際作用。因此,對于大量老數(shù)據(jù)讀取的改進方式是: - > 對于新消費組,默認從LAST_OFFSET消費; - > Broker中單Topic堆積超過1000萬時,禁止消費,需聯(lián)系管理員開啟消費; - > 監(jiān)控要到位,磁盤IO飆升時,能立刻聯(lián)系到消費方處理。 服務(wù)端的場景 CentOS 6.6中 Futex Kernel bug, 導(dǎo)致Name Server, Broker進程經(jīng)常掛起,無法正常工作; - > 升級到6.7 服務(wù)端2個線程會創(chuàng)建相同CommitLog放入List,導(dǎo)致計算消息offset錯誤,解析消息失敗,無法消費,重啟沒法解決問題。 - > 線程安全問題,改為單線程 Pull模式下重置消費進度,導(dǎo)致服務(wù)端填充大量數(shù)據(jù)到Map中,broker cpu使用率飆升100%。 - > Map局部變量場景用不到,刪除 Master建議客戶端到Slave消費時,若數(shù)據(jù)還沒同步到Slave, 會重置pullOffset, 導(dǎo)致大量重復(fù)消費。 - > 不重置offset 同步?jīng)]有MagicCode,安全組掃描同步端口時,Master解析錯誤,導(dǎo)致一些問題。 - > 同步時添加magicCode校驗 基于RocketMQ,我們做了哪些改進新增客戶端 新增.net客戶端,基于Java源代碼原生開發(fā); 新增HTTP客戶端,實現(xiàn)部分功能,并通過Netty Server連接RocketMQ; 新增消息限流功能 如果客戶端代碼寫錯產(chǎn)生死循環(huán),就會產(chǎn)生大量的重復(fù)數(shù)據(jù),這時候會把生產(chǎn)線程打滿,導(dǎo)致隊列溢出,嚴(yán)重影響我們MQ集群的穩(wěn)定性,從而影響其他業(yè)務(wù)。 上圖是限流的模型圖,我們把限流功能加在Topic之前。通過限流功能可以設(shè)置rate limit和size limit等。其中rate limit是通過令牌桶算法來實現(xiàn)的,即每秒往桶里放多少個令牌,每秒就消費多少速度,或者是往Topic里寫多少數(shù)據(jù)。以上的兩個配置是支持動態(tài)修改的。 后臺監(jiān)控 我們還做了一個監(jiān)控后臺,用于監(jiān)控消息的全鏈路過程,包括:
其他功能
以上便是同程藝龍在消息系統(tǒng)建設(shè)方面的實踐。 |
|
|