談起消息隊(duì)列问潭,內(nèi)心還是會(huì)有些波瀾。
消息隊(duì)列婚被,緩存狡忙,分庫(kù)分表是高并發(fā)解決方案三劍客,而消息隊(duì)列是我最喜歡址芯,也是思考最多的技術(shù)灾茁。
我想按照下面的四個(gè)階段分享我與消息隊(duì)列的故事,同時(shí)也是對(duì)我技術(shù)成長(zhǎng)經(jīng)歷的回顧谷炸。
- 初識(shí):ActiveMQ
- 進(jìn)階:Redis&RabbitMQ
- 升華:MetaQ
- 鐘情:RocketMQ
1 初識(shí)ActiveMQ
1.1 異步&解耦
2011年初删顶,我在一家互聯(lián)網(wǎng)彩票公司做研發(fā)。
我負(fù)責(zé)的是用戶(hù)中心系統(tǒng)淑廊,提供用戶(hù)注冊(cè)逗余,查詢(xún),修改等基礎(chǔ)功能季惩。用戶(hù)注冊(cè)成功之后录粱,需要給用戶(hù)發(fā)送短信。
因?yàn)樵瓉?lái)都是面向過(guò)程編程画拾,我就把新增用戶(hù)模塊和發(fā)送短信模塊都揉在一起了啥繁。
起初都還好,但問(wèn)題慢慢的顯現(xiàn)出來(lái)青抛。
- 短信渠道不夠穩(wěn)定旗闽,發(fā)送短信會(huì)達(dá)到5秒左右,這樣用戶(hù)注冊(cè)接口耗時(shí)很大,影響前端用戶(hù)體驗(yàn);
- 短信渠道接口發(fā)生變化适室,用戶(hù)中心代碼就必須修改了嫡意。但用戶(hù)中心是核心系統(tǒng)。每次上線都必要謹(jǐn)小慎微捣辆。這種感覺(jué)很別扭蔬螟,非核心功能影響到核心系統(tǒng)了。
第一個(gè)問(wèn)題汽畴,我可以采取線程池的方法來(lái)做旧巾,主要是異步化。但第二個(gè)問(wèn)題卻讓我束手無(wú)措忍些。
于是我向技術(shù)經(jīng)理請(qǐng)教鲁猩,他告訴我引入消息隊(duì)列去解決這個(gè)問(wèn)題。
- 將發(fā)送短信功能單獨(dú)拆成獨(dú)立的Job服務(wù);
- 用戶(hù)中心用戶(hù)注冊(cè)成功后罢坝,發(fā)送一條消息到消息隊(duì)列廓握,Job服務(wù)收到消息調(diào)用短信服務(wù)發(fā)送短信即可。
這時(shí)炸客,我才明白: 消息隊(duì)列最核心的功能就是異步和解耦疾棵。
1.2 調(diào)度中心
彩票系統(tǒng)的業(yè)務(wù)是比較復(fù)雜的。在彩票訂單的生命周期里痹仙,經(jīng)過(guò)創(chuàng)建是尔,拆分子訂單,出票开仰,算獎(jiǎng)等諸多環(huán)節(jié)拟枚。每一個(gè)環(huán)節(jié)都需要不同的服務(wù)處理,每個(gè)系統(tǒng)都有自己獨(dú)立的表众弓,業(yè)務(wù)功能也相對(duì)獨(dú)立恩溅。假如每個(gè)應(yīng)用都去修改訂單主表的信息,那就會(huì)相當(dāng)混亂了谓娃。
公司的架構(gòu)師設(shè)計(jì)了調(diào)度中心的服務(wù)脚乡,調(diào)度中心的職責(zé)是維護(hù)訂單核心狀態(tài)機(jī),訂單返獎(jiǎng)流程滨达,彩票核心數(shù)據(jù)生成奶稠。
調(diào)度中心通過(guò)消息隊(duì)列和出票網(wǎng)關(guān),算獎(jiǎng)服務(wù)等系統(tǒng)傳遞和交換信息捡遍。
這種設(shè)計(jì)在那個(gè)時(shí)候青澀的我的眼里锌订,簡(jiǎn)直就是水滴vs人類(lèi)艦隊(duì),降維打擊画株。
隨著我對(duì)業(yè)務(wù)理解的不斷深入辆飘,我隱約覺(jué)得:“好的架構(gòu)是簡(jiǎn)潔的啦辐,也是應(yīng)該易于維護(hù)的”。
當(dāng)彩票業(yè)務(wù)日均千萬(wàn)交易額的時(shí)候蜈项,調(diào)度中心的研發(fā)維護(hù)人員也只有兩個(gè)人芹关。調(diào)度中心的源碼里業(yè)務(wù)邏輯,日志战得,代碼規(guī)范都是極好的充边。
在我日后的程序人生里庸推,我也會(huì)下意識(shí)模仿調(diào)度中心的編碼方式常侦,“不玩奇技淫巧,代碼是給人閱讀的”贬媒。
1.3 重啟大法
隨著彩票業(yè)務(wù)的爆炸增長(zhǎng)聋亡,每天的消息量從30萬(wàn)激增到150~200萬(wàn)左右,一切看起來(lái)似乎很平穩(wěn)际乘。
某一天雙色球投注截止坡倔,調(diào)度中心無(wú)法從消息隊(duì)列中消費(fèi)數(shù)據(jù)。消息總線處于只能發(fā)脖含,不能收的狀態(tài)下罪塔。整個(gè)技術(shù)團(tuán)隊(duì)都處于極度的焦慮狀態(tài),“要是出不了票养葵,那可是幾百萬(wàn)的損失呀征堪,要是用戶(hù)中了兩個(gè)雙色球?那可是千萬(wàn)呀”关拒。大家急得像熱鍋上的螞蟻佃蚜。
這也是整個(gè)技術(shù)團(tuán)隊(duì)第一次遇到消費(fèi)堆積的情況,大家都沒(méi)有經(jīng)驗(yàn)着绊。
首先想到的是多部署幾臺(tái)調(diào)度中心服務(wù)谐算,部署完成之后,調(diào)度中心消費(fèi)了幾千條消息后還是Hang住了归露。這時(shí)洲脂,架構(gòu)師只能采用重啟的策略。你沒(méi)有看錯(cuò)剧包,就是重啟大法恐锦。說(shuō)起來(lái)真的很慚愧,但當(dāng)時(shí)真的只能采用這種方式玄捕。
調(diào)度中心重啟后踩蔚,消費(fèi)了一兩萬(wàn)后又Hang住了。只能又重啟一次枚粘。來(lái)來(lái)回回持續(xù)20多次馅闽,像擠牙膏一樣。而且隨著出票截止時(shí)間的臨近,這種思想上的緊張和恐懼感更加強(qiáng)烈福也。終于局骤,通過(guò)1小時(shí)的手工不斷重啟,消息終于消費(fèi)完了暴凑。
我當(dāng)時(shí)正好在讀畢玄老師的《分布式j(luò)ava應(yīng)用基礎(chǔ)與實(shí)踐》峦甩,猜想是不是線程阻塞了,于是我用Jstack命令查看堆棧情況现喳。果然不出所料凯傲,線程都阻塞在提交數(shù)據(jù)的方法上。
我們馬上和DBA溝通嗦篱,發(fā)現(xiàn)oracle數(shù)據(jù)庫(kù)執(zhí)行了非常多的大事務(wù)冰单,每次大的事務(wù)執(zhí)行都需要30分鐘以上,導(dǎo)致調(diào)度中心的調(diào)度出票線程阻塞了灸促。
技術(shù)部后來(lái)采取了如下的方案規(guī)避堆積問(wèn)題:
- 生產(chǎn)者發(fā)送消息的時(shí)候诫欠,將超大的消息拆分成多批次的消息,減少調(diào)度中心執(zhí)行大事務(wù)的幾率;
- 數(shù)據(jù)源配置參數(shù)浴栽,假如事務(wù)執(zhí)行超過(guò)一定時(shí)長(zhǎng)荒叼,自動(dòng)拋異常,回滾典鸡。
1.4 復(fù)盤(pán)
Spring封裝的ActiveMQ的API非常簡(jiǎn)潔易用被廓,使用過(guò)程中真的非常舒服。
受限于當(dāng)時(shí)彩票技術(shù)團(tuán)隊(duì)的技術(shù)水平和視野椿每,我們?cè)谑褂肁ctiveMQ中遇到了一些問(wèn)題伊者。
高吞吐下,堆積到一定消息量易Hang住
技術(shù)團(tuán)隊(duì)發(fā)現(xiàn)在吞吐量特別高的場(chǎng)景下间护,假如消息堆積越大亦渗,ActiveMQ有較小幾率會(huì)Hang住的。
出票網(wǎng)關(guān)的消息量特別大汁尺,有的消息并不需要馬上消費(fèi)法精,但是為了規(guī)避消息隊(duì)列Hang住的問(wèn)題,出票網(wǎng)關(guān)消費(fèi)數(shù)據(jù)的時(shí)候痴突,先將消息先持久化到本地磁盤(pán)搂蜓,生成本地XML文件,然后異步定時(shí)執(zhí)行消息辽装。通過(guò)這種方式帮碰,我們大幅度提升了出票網(wǎng)關(guān)的消費(fèi)速度,基本杜絕了出票網(wǎng)關(guān)隊(duì)列的堆積拾积。
但這種方式感覺(jué)也挺怪的殉挽,消費(fèi)消息的時(shí)候丰涉,還要本地再存儲(chǔ)一份數(shù)據(jù),消息存儲(chǔ)在本地斯碌,假如磁盤(pán)壞了一死,也有丟消息的風(fēng)險(xiǎn)。
高可用機(jī)制待完善
我們采用的master/slave部署模式傻唾,一主一從投慈,服務(wù)器配置是4核8G 。
這種部署方式可以同時(shí)運(yùn)行兩個(gè)ActiveMQ冠骄, 只允許一個(gè)slave連接到Master上面伪煤,也就是說(shuō)只能有2臺(tái)MQ做集群,這兩個(gè)服務(wù)之間有一個(gè)數(shù)據(jù)備份通道猴抹,利用這個(gè)通道Master向Slave單向地?cái)?shù)據(jù)備份带族。這個(gè)方案在實(shí)際生產(chǎn)線上不方便锁荔, 因?yàn)楫?dāng)Master掛了之后蟀给, Slave并不能自動(dòng)地接收Client發(fā)來(lái)的請(qǐng)來(lái),需要手動(dòng)干預(yù)阳堕,且要停止Slave再重啟Master才能恢復(fù)負(fù)載集群跋理。
還有一些很詭異丟消息的事件,生產(chǎn)者發(fā)送消息成功恬总,但master控制臺(tái)查詢(xún)不到前普,但slave控制臺(tái)竟然能查詢(xún)到該消息。
但消費(fèi)者沒(méi)有辦法消費(fèi)slave上的消息壹堰,還得通過(guò)人工介入的方式去處理拭卿。
2 進(jìn)階Redis&RabbitMQ
2014年,我在藝龍網(wǎng)從事紅包系統(tǒng)和優(yōu)惠券系統(tǒng)優(yōu)化相關(guān)工作贱纠。
2.1 Redis可以做消息隊(duì)列嗎
酒店優(yōu)惠券計(jì)算服務(wù)使用的是初代流式計(jì)算框架Storm峻厚。Storm這里就不詳細(xì)介紹,可以參看下面的邏輯圖:
這里我們的Storm集群的水源頭(數(shù)據(jù)源)是redis集群谆焊,使用list數(shù)據(jù)結(jié)構(gòu)實(shí)現(xiàn)了消息隊(duì)列的push/pop功能惠桃。
流式計(jì)算的整體流程:
- 酒店信息服務(wù)發(fā)送酒店信息到Redis集群A/B;
- Storm的spout組件從Redis集群A/B獲取數(shù)據(jù), 獲取成功后,發(fā)送tuple消息給Bolt組件;
- Bolt組件收到消息后辖试,通過(guò)運(yùn)營(yíng)配置的規(guī)則對(duì)數(shù)據(jù)進(jìn)行清洗;
- 最后Storm把處理好的數(shù)據(jù)發(fā)送到Redis集群C;
- 入庫(kù)服務(wù)從Redis集群C獲取數(shù)據(jù),存儲(chǔ)數(shù)據(jù)到數(shù)據(jù)庫(kù);
- 搜索團(tuán)隊(duì)掃描數(shù)據(jù)庫(kù)表辜王,生成索引。
<figcaption style="text-align: center; color: rgb(136, 136, 136); font-family: PingFangSC-Light; font-size: 12px; margin-top: 5px;">storm說(shuō)明</figcaption>
這套流式計(jì)算服務(wù)每天處理千萬(wàn)條數(shù)據(jù)罐孝,處理得還算順利呐馆。但方案在團(tuán)隊(duì)內(nèi)部還是有不同聲音:
- storm的拓?fù)渖?jí)時(shí)候,或者優(yōu)惠券服務(wù)重啟的時(shí)候莲兢,偶爾出現(xiàn)丟消息的情況汹来。但消息的丟失辫继,對(duì)業(yè)務(wù)來(lái)講沒(méi)有那么敏感,而且我們也提供了手工刷新的功能俗慈,也在業(yè)務(wù)的容忍范圍內(nèi);
- 團(tuán)隊(duì)需要經(jīng)常關(guān)注Redis的緩存使用量姑宽,擔(dān)心Redis隊(duì)列堆積, 導(dǎo)致out of memory;
- 架構(gòu)師認(rèn)為搜索團(tuán)隊(duì)直接掃描數(shù)據(jù)庫(kù)不夠解耦,建議將Redis集群C替換成Kafka闺阱,搜索團(tuán)隊(duì)從kafka直接消費(fèi)消息炮车,生成索引;
我認(rèn)為使用Redis做消息隊(duì)列應(yīng)該滿(mǎn)足如下條件:
- 容忍小概率消息丟失,通過(guò)定時(shí)任務(wù)/手工觸發(fā)達(dá)到最終一致的業(yè)務(wù)場(chǎng)景;
- 消息堆積概率低酣溃,有相關(guān)的報(bào)警監(jiān)控;
- 消費(fèi)者的消費(fèi)模型要足夠簡(jiǎn)單瘦穆。
2.2 RabbitMQ是管子不是池子
RabbitMQ是用erlang語(yǔ)言編寫(xiě)的。RabbitMQ滿(mǎn)足了我的兩點(diǎn)需求:
- 高可用機(jī)制赊豌。藝龍內(nèi)部是使用的鏡像高可用模式扛或,而且這種模式在藝龍已經(jīng)使用了較長(zhǎng)時(shí)間了,穩(wěn)定性也得到了一定的驗(yàn)證碘饼。
- 我負(fù)責(zé)的紅包系統(tǒng)里熙兔,RabbitMQ每天的吞吐也在百萬(wàn)條消息左右,消息的發(fā)送和消費(fèi)都還挺完美艾恼。
優(yōu)惠券服務(wù)原使用SqlServer住涉,由于數(shù)據(jù)量太大,技術(shù)團(tuán)隊(duì)決定使用分庫(kù)分表的策略钠绍,使用公司自主研發(fā)的分布式數(shù)據(jù)庫(kù)DDA舆声。
因?yàn)槭堑谝淮问褂梅植际綌?shù)據(jù)庫(kù),為了測(cè)試DDA的穩(wěn)定性柳爽,我們模擬發(fā)送1000萬(wàn)條消息到RabbitMQ媳握,然后優(yōu)惠券重構(gòu)服務(wù)消費(fèi)消息后,按照用戶(hù)編號(hào)hash到不同的mysql庫(kù)磷脯。
RabbitMQ集群模式是鏡像高可用蛾找,3臺(tái)服務(wù)器,每臺(tái)配置是4核8G 争拐。
我們以每小時(shí)300萬(wàn)條消息的速度發(fā)送消息腋粥,最開(kāi)始1個(gè)小時(shí)生產(chǎn)者和消費(fèi)者表現(xiàn)都很好,但由于消費(fèi)者的速度跟不上生產(chǎn)者的速度架曹,導(dǎo)致消息隊(duì)列有積壓情況產(chǎn)生隘冲。第三個(gè)小時(shí),消息隊(duì)列已堆積了500多萬(wàn)條消息了绑雄, 生產(chǎn)者發(fā)送消息的速度由最開(kāi)始的2毫秒激增到500毫秒左右展辞。RabbitMQ的控制臺(tái)已血濺當(dāng)場(chǎng),標(biāo)紅報(bào)警万牺。
這是一次無(wú)意中的測(cè)試罗珍,從測(cè)試的情況來(lái)看洽腺,RabbitMQ很優(yōu)秀,但RabbitMQ對(duì)消息堆積的支持并不好覆旱,當(dāng)大量消息積壓的時(shí)候蘸朋,會(huì)導(dǎo)致 RabbitMQ 的性能急劇下降。
有的朋友對(duì)我講:“RabbitMQ明明是管子扣唱,你非得把他當(dāng)池子藕坯?”
隨著整個(gè)互聯(lián)網(wǎng)數(shù)據(jù)量的激增, 很多業(yè)務(wù)場(chǎng)景下是允許適當(dāng)堆積的,只要保證消費(fèi)者可以平穩(wěn)消費(fèi)噪沙,整個(gè)業(yè)務(wù)沒(méi)有大的波動(dòng)即可炼彪。
我心里面越來(lái)越相信:消息隊(duì)列既可以做管子,也可以當(dāng)做池子正歼。
3 升華MetaQ
Metamorphosis的起源是我從對(duì)linkedin的開(kāi)源MQ–現(xiàn)在轉(zhuǎn)移到apache的kafka的學(xué)習(xí)開(kāi)始的辐马,這是一個(gè)設(shè)計(jì)很獨(dú)特的MQ系統(tǒng),它采用pull機(jī)制局义,而 不是一般MQ的push模型喜爷,它大量利用了zookeeper做服務(wù)發(fā)現(xiàn)和offset存儲(chǔ),它的設(shè)計(jì)理念我非常欣賞并贊同旭咽,強(qiáng)烈建議你閱讀一下它的設(shè)計(jì)文檔贞奋,總體上說(shuō)metamorphosis的設(shè)計(jì)跟它是完全一致的。--- MetaQ的作者莊曉丹
3.1 驚艷消費(fèi)者模型
2015年穷绵,我主要從事神州專(zhuān)車(chē)訂單研發(fā)工作。
MetaQ滿(mǎn)足了我對(duì)于消息隊(duì)列的幻想:“分布式特愿,高吞吐仲墨,高堆積”。
MetaQ支持兩種消費(fèi)模型:集群消費(fèi)和廣播消費(fèi) 揍障,因?yàn)橐郧笆褂眠^(guò)的消費(fèi)者模型都是用隊(duì)列模型目养,當(dāng)我第一次接觸到這種發(fā)布訂閱模型的時(shí)候還是被驚艷到了。
集群消費(fèi)
訂單創(chuàng)建成功后毒嫡,發(fā)送一條消息給MetaQ癌蚁。這條消息可以被派單服務(wù)消費(fèi),也可以被BI服務(wù)消費(fèi)兜畸。
廣播消費(fèi)
派單服務(wù)在講訂單指派給司機(jī)的時(shí)候努释,會(huì)給司機(jī)發(fā)送一個(gè)推送消息。推送就是用廣播消費(fèi)的模式實(shí)現(xiàn)的咬摇。
大體流程是:
- 司機(jī)端推送服務(wù)是一個(gè)TCP服務(wù)伐蒂,啟動(dòng)后,采用的是廣播模式消費(fèi)MetaQ的PushTopic;
- 司機(jī)端會(huì)定時(shí)發(fā)送TCP請(qǐng)求到推送服務(wù)肛鹏,鑒權(quán)成功后逸邦,推送服務(wù)會(huì)保存司機(jī)編號(hào)和channel的引用恩沛;
- 派單服務(wù)發(fā)送推送消息到MetaQ;
- 推送服務(wù)的每一臺(tái)機(jī)器都會(huì)收到該消息缕减,然后判斷內(nèi)存中是否存在該司機(jī)的channel引用雷客,若存在,則推送消息桥狡。
這是非常經(jīng)典的廣播消費(fèi)的案例佛纫。我曾經(jīng)研讀京麥TCP網(wǎng)關(guān)的設(shè)計(jì),它的推送也是采用類(lèi)似的方式总放。
3.2 激進(jìn)的消峰
2015年是打車(chē)大戰(zhàn)硝煙彌漫的一年呈宇。
對(duì)神州專(zhuān)車(chē)來(lái)講,隨著訂單量的不斷增長(zhǎng)局雄,欣喜的同時(shí)甥啄,性能的壓力與日俱增。早晚高峰期炬搭,用戶(hù)打車(chē)的時(shí)候蜈漓,經(jīng)常點(diǎn)擊下單經(jīng)常無(wú)響應(yīng)。在系統(tǒng)層面來(lái)看宫盔,專(zhuān)車(chē)api網(wǎng)關(guān)發(fā)現(xiàn)大規(guī)模超時(shí)融虽,訂單服務(wù)的性能急劇下降。數(shù)據(jù)庫(kù)層面壓力更大灼芭,高峰期一條記錄插入竟然需要8秒的時(shí)間有额。
整個(gè)技術(shù)團(tuán)隊(duì)需要盡快提升專(zhuān)車(chē)系統(tǒng)的性能,此前已經(jīng)按照模塊領(lǐng)域做了數(shù)據(jù)庫(kù)的拆分彼绷。但系統(tǒng)的瓶頸依然很明顯巍佑。
我們?cè)O(shè)計(jì)了現(xiàn)在看來(lái)有點(diǎn)激進(jìn)的方案:
- 設(shè)計(jì)訂單緩存。緩存方案大家要有興趣寄悯,我們可以以后再聊萤衰,里面有很多可以詳聊的點(diǎn);
- 在訂單的載客生命周期里,訂單的修改操作先修改緩存猜旬,然后發(fā)送消息到MetaQ脆栋,訂單落盤(pán)服務(wù)消費(fèi)消息,并判斷訂單信息是否正常(比如有無(wú)亂序)洒擦,若訂單數(shù)據(jù)無(wú)誤椿争,則存儲(chǔ)到數(shù)據(jù)庫(kù)中。
這里有兩個(gè)細(xì)節(jié):
消費(fèi)者消費(fèi)的時(shí)候需要順序消費(fèi)秘遏,實(shí)現(xiàn)的方式是按照訂單號(hào)路由到不同的partition丘薛,同一個(gè)訂單號(hào)的消息,每次都發(fā)到同一個(gè)partition;[圖片上傳中...(image-5c6295-1668478881924-1)]
一個(gè)守護(hù)任務(wù)邦危,定時(shí)輪詢(xún)當(dāng)前正在進(jìn)行的訂單洋侨,當(dāng)緩存與數(shù)據(jù)不一致時(shí)候舍扰,修復(fù)數(shù)據(jù),并發(fā)送報(bào)警希坚。
這次優(yōu)化大大提升訂單服務(wù)的整體性能边苹,也為后來(lái)訂單服務(wù)庫(kù)分庫(kù)分表以及異構(gòu)打下了堅(jiān)實(shí)的基礎(chǔ),根據(jù)我們的統(tǒng)計(jì)數(shù)據(jù)裁僧,基本沒(méi)有發(fā)生過(guò)緩存和數(shù)據(jù)庫(kù)最后不一致的場(chǎng)景个束。但這種方案對(duì)緩存高可用有較高的要求,還是有點(diǎn)小激進(jìn)吧聊疲。
3.3 消息SDK封裝
做過(guò)基礎(chǔ)架構(gòu)的同學(xué)可能都有經(jīng)驗(yàn):“三方組件會(huì)封裝一層”茬底,神州架構(gòu)團(tuán)隊(duì)也是將metaq-client封裝了一層。
在我的思維里面获洲,封裝一層可以減少研發(fā)人員使用第三方組件的心智投入阱表,統(tǒng)一技術(shù)棧,也就如此了贡珊。
直到發(fā)生一次意外最爬,我的思維升級(jí)了。那是一天下午门岔,整個(gè)專(zhuān)車(chē)服務(wù)崩潰較長(zhǎng)時(shí)間爱致。技術(shù)團(tuán)隊(duì)發(fā)現(xiàn):"專(zhuān)車(chē)使用zookeeper做服務(wù)發(fā)現(xiàn)。zk集群的leader機(jī)器掛掉了寒随,一直在選主糠悯。"
臨時(shí)解決后,我們發(fā)現(xiàn)MetaQ和服務(wù)發(fā)現(xiàn)都使用同一套zk集群牢裳,而且consumer的offset提交逢防,以及負(fù)載均衡都會(huì)對(duì)zk集群進(jìn)行大量的寫(xiě)操作。
為了減少M(fèi)etaQ對(duì)zk集群的影響蒲讯,我們的目標(biāo)是:“MetaQ使用獨(dú)立的zk集群”。
- 需要部署新的zk集群灰署;
- MetaQ的zk數(shù)據(jù)需要同步到新的集群判帮;
- 保證切換到新的集群,應(yīng)用服務(wù)基本無(wú)感知溉箕。
我很好奇向架構(gòu)部同學(xué)請(qǐng)教晦墙,他說(shuō)新的集群已經(jīng)部署好了,但需要同步zk數(shù)據(jù)到新的集群肴茄。他在客戶(hù)端里添加了雙寫(xiě)的操作晌畅。也就是說(shuō):我們除了會(huì)寫(xiě)原有的zk集群一份數(shù)據(jù),同時(shí)也會(huì)在新的zk集群寫(xiě)一份寡痰。過(guò)了幾周后抗楔,MetaQ使用獨(dú)立的zk集群這個(gè)任務(wù)已經(jīng)完成了棋凳。
這一次的經(jīng)歷帶給我很大的感慨:“還可以這么玩?” 连躏,也讓我思考著:三方組件封裝沒(méi)有想像中那么簡(jiǎn)單剩岳。
我們可以看下快手消息的SDK封裝策略:
- 對(duì)外只提供最基本的 API,所有訪問(wèn)必須經(jīng)過(guò)SDK提供的接口入热。簡(jiǎn)潔的 API 就像冰山的一個(gè)角拍棕,除了對(duì)外的簡(jiǎn)單接口,下面所有的東西都可以升級(jí)更換勺良,而不會(huì)破壞兼容性 ;
- 業(yè)務(wù)開(kāi)發(fā)起來(lái)也很簡(jiǎn)單绰播,只要需要提供 Topic(全局唯一)和 Group 就可以生產(chǎn)和消費(fèi),不用提供環(huán)境尚困、NameServer 地址等蠢箩。SDK 內(nèi)部會(huì)根據(jù) Topic 解析出集群 NameServer 的地址,然后連接相應(yīng)的集群尾组。生產(chǎn)環(huán)境和測(cè)試環(huán)境環(huán)境會(huì)解析出不同的地址忙芒,從而實(shí)現(xiàn)了隔離;
- 上圖分為 3 層讳侨,第二層是通用的呵萨,第三層才對(duì)應(yīng)具體的 MQ 實(shí)現(xiàn),因此跨跨,理論上可以更換為其它消息中間件潮峦,而客戶(hù)端程序不需要修改;
- SDK 內(nèi)部集成了熱變更機(jī)制勇婴,可以在不重啟 Client 的情況下做動(dòng)態(tài)配置忱嘹,比如下發(fā)路由策略(更換集群 NameServer 的地址,或者連接到別的集群去)耕渴,Client 的線程數(shù)拘悦、超時(shí)時(shí)間等。通過(guò) Maven 強(qiáng)制更新機(jī)制橱脸,可以保證業(yè)務(wù)使用的 SDK 基本上是最新的础米。
3.4 重構(gòu)MetaQ , 自成體系
我有一個(gè)習(xí)慣 : "經(jīng)常找運(yùn)維,DBA添诉,架構(gòu)師了解當(dāng)前系統(tǒng)是否有什么問(wèn)題屁桑,以及他們解決問(wèn)題的思路。這樣栏赴,我就有另外一個(gè)視角來(lái)審視公司的系統(tǒng)運(yùn)行情況"蘑斧。
MetaQ也有他的缺點(diǎn)。
- MetaQ的基層通訊框架是gecko,MetaQ偶爾會(huì)出現(xiàn)rpc無(wú)響應(yīng)竖瘾,應(yīng)用假死的情況沟突,不太好定位問(wèn)題;
- MetaQ的運(yùn)維能力薄弱准浴,只有簡(jiǎn)單的Dashboard界面事扭,無(wú)法實(shí)現(xiàn)自動(dòng)化主題申請(qǐng),消息追蹤等功能乐横。
有一天求橄,我發(fā)現(xiàn)測(cè)試環(huán)境的一臺(tái)消費(fèi)者服務(wù)器啟動(dòng)后,不斷報(bào)鏈接異常的問(wèn)題葡公,而且cpu占用很高罐农。我用netstat命令馬上查一下,發(fā)現(xiàn)已經(jīng)創(chuàng)建了幾百個(gè)鏈接催什。出于好奇心涵亏,我打開(kāi)了源碼,發(fā)現(xiàn)網(wǎng)絡(luò)通訊框架gecko已經(jīng)被替換成了netty蒲凶。我們馬上和架構(gòu)部的同學(xué)聯(lián)系气筋。
我這才明白:他們已經(jīng)開(kāi)始重構(gòu)MetaQ了。我從來(lái)沒(méi)有想過(guò)重構(gòu)一個(gè)開(kāi)源軟件旋圆,因?yàn)榫嚯x我太遠(yuǎn)了宠默。或者那個(gè)時(shí)候灵巧,我覺(jué)得自己的能力還達(dá)不到搀矫。
后來(lái),神州自研的消息隊(duì)列自成體系了刻肄,已經(jīng)在生產(chǎn)環(huán)境運(yùn)行的挺好瓤球。
時(shí)至今天,我還是很欣賞神州架構(gòu)團(tuán)隊(duì)敏弃。他們自研了消息隊(duì)列卦羡,DataLink(數(shù)據(jù)異構(gòu)中間件),分庫(kù)分表中間件等麦到。他們?cè)敢馊?chuàng)新咸这,有勇氣去做一個(gè)更好的技術(shù)產(chǎn)品瘤睹。
我從他們身上學(xué)到很多。
也許在看到他們重構(gòu)MetaQ的那一刻研底,我的心里埋下了種子董济。
4 鐘情RocketMQ
4.1 開(kāi)源的盛宴
2014年步清,我搜羅了很多的淘寶的消息隊(duì)列的資料,我知道MetaQ的版本已經(jīng)升級(jí)MetaQ 3.0,只是開(kāi)源版本還沒(méi)有放出來(lái)廓啊。
大約秋天的樣子欢搜,我加入了RocketMQ技術(shù)群。誓嘉(RocketMQ創(chuàng)始人)在群里說(shuō):“最近要開(kāi)源了谴轮,放出來(lái)后炒瘟,大家趕緊fork呀”。他的這句話發(fā)在群里之后第步,群里都炸開(kāi)了鍋疮装。我更是歡喜雀躍,期待著能早日見(jiàn)到阿里自己內(nèi)部的消息中間件粘都。終于廓推,RocketMQ終于開(kāi)源了。我迫不及待想一窺他的風(fēng)采翩隧。
因?yàn)槲蚁雽W(xué)網(wǎng)絡(luò)編程樊展,而RocketMQ的通訊模塊remoting底層也是Netty寫(xiě)的。所以堆生,RocketMQ的通訊層是我學(xué)習(xí)切入的點(diǎn)专缠。
我模仿RocketMQ的remoting寫(xiě)了一個(gè)玩具的rpc,這更大大提高我的自信心淑仆。正好涝婉,藝龍舉辦技術(shù)創(chuàng)新活動(dòng)。我想想糯景,要不嘗試一下用Netty改寫(xiě)下Cobar的通訊模塊嘁圈。于是參考Cobar的源碼花了兩周寫(xiě)了個(gè)netty版的proxy,其實(shí)非常粗糙蟀淮,很多功能不完善最住。后來(lái),這次活動(dòng)頒給我一個(gè)鼓勵(lì)獎(jiǎng)怠惶,現(xiàn)在想想都很好玩涨缚。
因?yàn)樵谏裰輧?yōu)車(chē)使用MetaQ的關(guān)系,我學(xué)習(xí)RocketMQ也比較得心應(yīng)手策治。為了真正去理解源碼脓魏,我時(shí)常會(huì)參考RocketMQ的源碼,寫(xiě)一些輪子來(lái)驗(yàn)證我的學(xué)習(xí)效果通惫。
雖然自己做了一些練習(xí)茂翔,但一直沒(méi)有在業(yè)務(wù)環(huán)境使用過(guò)。2018年是我真正使用RocketMQ的一年履腋,也是有所得的一年珊燎。
短信服務(wù)
短信服務(wù)應(yīng)用很廣泛惭嚣,比如用戶(hù)注冊(cè)登錄驗(yàn)證碼,營(yíng)銷(xiāo)短信悔政,下單成功短信通知等等晚吞。最開(kāi)始設(shè)計(jì)短信服務(wù)的時(shí)候,我想學(xué)習(xí)業(yè)界是怎么做的谋国。于是把目標(biāo)鎖定在騰訊云的短信服務(wù)上槽地。騰訊云的短信服務(wù)有如下特點(diǎn):
- 統(tǒng)一的SDK,后端入口是http/https服務(wù) , 分配appId/appSecret鑒權(quán)芦瘾;
- 簡(jiǎn)潔的API設(shè)計(jì):?jiǎn)伟l(fā)捌蚊,群發(fā),營(yíng)銷(xiāo)單發(fā)旅急,營(yíng)銷(xiāo)群發(fā)逢勾,模板單發(fā),模板群發(fā)藐吮。
于是溺拱,我參考了這種設(shè)計(jì)思路。
- 模仿騰訊云的SDK設(shè)計(jì)谣辞,提供簡(jiǎn)單易用的短信接口迫摔;
- 設(shè)計(jì)短信服務(wù)API端,接收發(fā)短信請(qǐng)求泥从,發(fā)送短信信息到消息隊(duì)列句占;
- worker服務(wù)消費(fèi)消息,按照負(fù)載均衡的算法躯嫉,調(diào)用不同渠道商的短信接口纱烘;
- Dashboard可以查看短信發(fā)送記錄,配置渠道商信息祈餐。[圖片上傳中...(image-8e9d3f-1668478881924-0)]
短信服務(wù)是我真正意義第一次生產(chǎn)環(huán)境使用RocketMQ擂啥,當(dāng)短信一條條發(fā)出來(lái)的時(shí)候,還是蠻有成就感的帆阳。
MQ控制臺(tái)
使用過(guò)RocketMQ的朋友哺壶,肯定對(duì)上圖的控制臺(tái)很熟悉。當(dāng)時(shí)團(tuán)隊(duì)有多個(gè)RocketMQ集群蜒谤,每組集群都需要單獨(dú)部署一套控制臺(tái)山宾。于是我想著:能不能稍微把控制臺(tái)改造一番,能滿(mǎn)足支持多組集群鳍徽。
于是资锰,擼起袖子干了起來(lái)。大概花了20天的時(shí)間阶祭,我們基于開(kāi)源的版本改造了能支持多組集群的版本台妆。做完之后翎猛,雖然能滿(mǎn)足我最初的想法,但是做的很粗糙接剩。而且搜狐開(kāi)源了他們自己的MQCloud ,我看了他們的設(shè)計(jì)之后萨咳, 覺(jué)得離一個(gè)消息治理平臺(tái)還很遠(yuǎn)懊缺。
后來(lái)我讀了《網(wǎng)易云音樂(lè)的消息隊(duì)列改造之路》,《今日頭條在消息服務(wù)平臺(tái)和容災(zāi)體系建設(shè)方面的實(shí)踐與思考》這兩篇文章培他,越是心癢難耐鹃两,蠻想去做的是一個(gè)真正意義上的消息治理平臺(tái)。一直沒(méi)有什么場(chǎng)景和機(jī)會(huì)舀凛,還是有點(diǎn)可惜俊扳。
最近看了哈羅單車(chē)架構(gòu)專(zhuān)家梁勇的一篇文章《哈啰在分布式消息治理和微服務(wù)治理中的實(shí)踐》,推薦大家一讀猛遍。
一扇窗子馋记,開(kāi)始自研組件
后來(lái),我嘗試進(jìn)一步深入使用RocketMQ懊烤。
- 仿ONS風(fēng)格封裝消息SDK梯醒;
- 運(yùn)維側(cè)平滑擴(kuò)容消息隊(duì)列;
- 生產(chǎn)環(huán)境DefaultMQPullConsumer消費(fèi)模式嘗試
這些做完之后腌紧,我們又自研了注冊(cè)中心茸习、配置中心,任務(wù)調(diào)度系統(tǒng)壁肋。設(shè)計(jì)這些系統(tǒng)的時(shí)候号胚,從RocketMQ源碼里汲取了很多的營(yíng)養(yǎng),雖然現(xiàn)在看來(lái)有很多設(shè)計(jì)不完善的地方浸遗,代碼質(zhì)量也有待提高猫胁,但做完這些系統(tǒng)后,還是大大提升我的自信心乙帮。
RocketMQ給我打開(kāi)了一扇窗子杜漠,讓我能看到更廣闊的Java世界。對(duì)我而言察净,這就是開(kāi)源的盛宴驾茴。
4.2 Kafka: 大數(shù)據(jù)生態(tài)的不可或缺的部分
Kafka是一個(gè)擁有高吞吐、可持久化氢卡、可水平擴(kuò)展锈至,支持流式數(shù)據(jù)處理等多種特性的分布式消息流處理中間件,采用分布式消息發(fā)布與訂閱機(jī)制译秦,在日志收集峡捡、流式數(shù)據(jù)傳輸击碗、在線/離線系統(tǒng)分析、實(shí)時(shí)監(jiān)控等領(lǐng)域有廣泛的應(yīng)用们拙。
日志同步
在大型業(yè)務(wù)系統(tǒng)設(shè)計(jì)中稍途,為了快速定位問(wèn)題,全鏈路追蹤日志砚婆,以及故障及時(shí)預(yù)警監(jiān)控械拍,通常需要將各系統(tǒng)應(yīng)用的日志集中分析處理。
Kafka設(shè)計(jì)初衷就是為了應(yīng)對(duì)大量日志傳輸場(chǎng)景装盯,應(yīng)用通過(guò)可靠異步方式將日志消息同步到消息服務(wù)坷虑,再通過(guò)其他組件對(duì)日志做實(shí)時(shí)或離線分析,也可用于關(guān)鍵日志信息收集進(jìn)行應(yīng)用監(jiān)控埂奈。
日志同步主要有三個(gè)關(guān)鍵部分:日志采集客戶(hù)端迄损,Kafka消息隊(duì)列以及后端的日志處理應(yīng)用。
- 日志采集客戶(hù)端账磺,負(fù)責(zé)用戶(hù)各類(lèi)應(yīng)用服務(wù)的日志數(shù)據(jù)采集芹敌,以消息方式將日志“批量”“異步”發(fā)送Kafka客戶(hù)端。Kafka客戶(hù)端批量提交和壓縮消息绑谣,對(duì)應(yīng)用服務(wù)的性能影響非常小党窜。
- Kafka將日志存儲(chǔ)在消息文件中,提供持久化借宵。
- 日志處理應(yīng)用幌衣,如Logstash,訂閱并消費(fèi)Kafka中的日志消息壤玫,最終供文件搜索服務(wù)檢索日志豁护,或者由Kafka將消息傳遞給Hadoop等其他大數(shù)據(jù)應(yīng)用系統(tǒng)化存儲(chǔ)與分析。
日志同步示意圖:
流計(jì)算處理
在很多領(lǐng)域欲间,如股市走向分析楚里、氣象數(shù)據(jù)測(cè)控、網(wǎng)站用戶(hù)行為分析猎贴,由于數(shù)據(jù)產(chǎn)生快班缎、實(shí)時(shí)性強(qiáng)且量大,您很難統(tǒng)一采集這些數(shù)據(jù)并將其入庫(kù)存儲(chǔ)后再做處理她渴,這便導(dǎo)致傳統(tǒng)的數(shù)據(jù)處理架構(gòu)不能滿(mǎn)足需求达址。Kafka以及Storm、Samza趁耗、Spark等流計(jì)算引擎的出現(xiàn)沉唠,就是為了更好地解決這類(lèi)數(shù)據(jù)在處理過(guò)程中遇到的問(wèn)題,流計(jì)算模型能實(shí)現(xiàn)在數(shù)據(jù)流動(dòng)的過(guò)程中對(duì)數(shù)據(jù)進(jìn)行實(shí)時(shí)地捕捉和處理苛败,并根據(jù)業(yè)務(wù)需求進(jìn)行計(jì)算分析满葛,最終把結(jié)果保存或者分發(fā)給需要的組件径簿。
數(shù)據(jù)中轉(zhuǎn)樞紐
近10多年來(lái),諸如KV存儲(chǔ)(HBase)嘀韧、搜索(ElasticSearch)篇亭、流式處理(Storm、Spark乳蛾、Samza)暗赶、時(shí)序數(shù)據(jù)庫(kù)(OpenTSDB)等專(zhuān)用系統(tǒng)應(yīng)運(yùn)而生。這些系統(tǒng)是為單一的目標(biāo)而產(chǎn)生的肃叶,因其簡(jiǎn)單性使得在商業(yè)硬件上構(gòu)建分布式系統(tǒng)變得更加容易且性?xún)r(jià)比更高。通常十嘿,同一份數(shù)據(jù)集需要被注入到多個(gè)專(zhuān)用系統(tǒng)內(nèi)因惭。例如,當(dāng)應(yīng)用日志用于離線日志分析時(shí)绩衷,搜索單個(gè)日志記錄同樣不可或缺蹦魔,而構(gòu)建各自獨(dú)立的工作流來(lái)采集每種類(lèi)型的數(shù)據(jù)再導(dǎo)入到各自的專(zhuān)用系統(tǒng)顯然不切實(shí)際,利用消息隊(duì)列Kafka版作為數(shù)據(jù)中轉(zhuǎn)樞紐咳燕,同份數(shù)據(jù)可以被導(dǎo)入到不同專(zhuān)用系統(tǒng)中勿决。
下圖是美團(tuán) MySQL 數(shù)據(jù)實(shí)時(shí)同步到 Hive 的架構(gòu)圖,也是一個(gè)非常經(jīng)典的案例招盲。
4.3 如何技術(shù)選型
2018年去哪兒QMQ開(kāi)源了低缩,2019年騰訊TubeMQ開(kāi)源了,2020年P(guān)ulsar如火如荼曹货。
消息隊(duì)列的生態(tài)是如此的繁榮咆繁,那我們?nèi)绾芜x型呢?
我想我們不必局限于消息隊(duì)列顶籽,可以再擴(kuò)大一下玩般。簡(jiǎn)單談一談我的看法。
Databases are specializing – the “one size fits all” approach no longer applies ----- MongoDB設(shè)計(jì)哲學(xué)
第一點(diǎn):先有場(chǎng)景礼饱,然后再有適配這種場(chǎng)景的技術(shù)坏为。什么樣的場(chǎng)景選擇什么樣的技術(shù)。
第二點(diǎn):現(xiàn)實(shí)往往很復(fù)雜镊绪,當(dāng)我們真正做技術(shù)選型匀伏,并需要落地的時(shí)候,技術(shù)儲(chǔ)備和成本是兩個(gè)我們需要重點(diǎn)考量的因素镰吆。
技術(shù)儲(chǔ)備
- 技術(shù)團(tuán)隊(duì)有無(wú)使用這門(mén)技術(shù)的經(jīng)驗(yàn)帘撰,是否踩過(guò)生產(chǎn)環(huán)境的坑,以及針對(duì)這些坑有沒(méi)有完備的解決方案万皿;
- 架構(gòu)團(tuán)隊(duì)是否有成熟的SDK摧找,工具鏈核行,甚至是技術(shù)產(chǎn)品。
成本
- 研發(fā)蹬耘,測(cè)試芝雪,運(yùn)維投入人力成本;
- 服務(wù)器資源成本综苔;
- 招聘成本等惩系。
最后一點(diǎn)是人的因素,特別是管理者的因素如筛。每一次大的技術(shù)選型考驗(yàn)技術(shù)管理者的視野堡牡,格局,以及管理智慧杨刨。