咚咚是什么红选?咚咚之于京東相當(dāng)于旺旺之于淘寶腰池,它們都是服務(wù)于買家和賣家的溝通名眉。 自從京東開始為第三方賣家提供入駐平臺服務(wù)后,咚咚也就隨之誕生了污尉。 我們首先看看它誕生之初是什么樣的膀哲。
1.0 誕生(2010 - 2011)
為了業(yè)務(wù)的快速上線,1.0 版本的技術(shù)架構(gòu)實(shí)現(xiàn)是非常直接且簡單粗暴的被碗。 如何簡單粗暴法等太?請看架構(gòu)圖,如下蛮放。
1.0 的功能十分簡單缩抡,實(shí)現(xiàn)了一個(gè) IM 的基本功能,接入包颁、互通消息和狀態(tài)瞻想。 另外還有客服功能,就是顧客接入咨詢時(shí)的客服分配娩嚼,按輪詢方式把顧客分配給在線的客服接待蘑险。 用開源 Mina 框架實(shí)現(xiàn)了 TCP 的長連接接入,用 Tomcat Comet 機(jī)制實(shí)現(xiàn)了 HTTP 的長輪詢服務(wù)岳悟。 而消息投遞的實(shí)現(xiàn)是一端發(fā)送的消息臨時(shí)存放在 Redis 中佃迄,另一端拉取的生產(chǎn)消費(fèi)模型。
這個(gè)模型的做法導(dǎo)致需要以一種高頻率的方式來輪詢 Redis 遍歷屬于自己連接的關(guān)聯(lián)會話消息贵少。 這個(gè)模型很簡單呵俏,簡單包括多個(gè)層面的意思:理解起來簡單;開發(fā)起來簡單滔灶;部署起來也簡單普碎。 只需要一個(gè) Tomcat 應(yīng)用依賴一個(gè)共享的 Redis,簡單的實(shí)現(xiàn)核心業(yè)務(wù)功能录平,并支持業(yè)務(wù)快速上線麻车。
但這個(gè)簡單的模型也有些嚴(yán)重的缺陷,主要是效率和擴(kuò)展問題斗这。 輪詢的頻率間隔大小基本決定了消息的延時(shí)动猬,輪詢越快延時(shí)越低,但輪詢越快消耗也越高表箭。 這個(gè)模型實(shí)際上是一個(gè)高功耗低效能的模型赁咙,因?yàn)椴换钴S的連接在那做高頻率的無意義輪詢。 高頻有多高呢,基本在 100 ms 以內(nèi)序目,你不能讓輪詢太慢,比如超過 2 秒輪一次伯襟,人就會在聊天過程中感受到明顯的會話延遲猿涨。 隨著在線人數(shù)增加,輪詢的耗時(shí)也線性增長姆怪,因此這個(gè)模型導(dǎo)致了擴(kuò)展能力和承載能力都不好叛赚,一定會隨著在線人數(shù)的增長碰到性能瓶頸。
1.0 的時(shí)代背景正是京東技術(shù)平臺從 .NET 向 Java 轉(zhuǎn)型的年代稽揭,我也正是在這期間加入京東并參與了京東主站技術(shù)轉(zhuǎn)型架構(gòu)升級的過程俺附。 之后開始接手了京東咚咚,并持續(xù)完善這個(gè)產(chǎn)品溪掀,進(jìn)行了三次技術(shù)架構(gòu)演進(jìn)事镣。
2.0 成長(2012)
我們剛接手時(shí) 1.0 已在線上運(yùn)行并支持京東 POP(開放平臺)業(yè)務(wù),之后京東打算組建自營在線客服團(tuán)隊(duì)并落地在成都揪胃。 不管是自營還是 POP 客服咨詢業(yè)務(wù)當(dāng)時(shí)都起步不久璃哟,1.0 架構(gòu)中的性能和效率缺陷問題還沒有達(dá)到引爆的業(yè)務(wù)量級。 而自營客服當(dāng)時(shí)還處于起步階段喊递,客服人數(shù)不足随闪,服務(wù)能力不夠,顧客咨詢量遠(yuǎn)遠(yuǎn)超過客服的服務(wù)能力骚勘。 超出服務(wù)能力的顧客咨詢铐伴,當(dāng)時(shí)我們的系統(tǒng)統(tǒng)一返回提示客服繁忙,請稍后咨詢俏讹。 這種狀況導(dǎo)致高峰期大量顧客無論怎么刷新請求当宴,都很可能無法接入客服,體驗(yàn)很差泽疆。 所以 2.0 重點(diǎn)放在了業(yè)務(wù)功能體驗(yàn)的提升上即供,如下圖所示。
針對無法及時(shí)提供服務(wù)的顧客于微,可以排隊(duì)或者留言逗嫡。 針對純文字溝通,提供了文件和圖片等更豐富的表達(dá)方式株依。 另外支持了客服轉(zhuǎn)接和快捷回復(fù)等方式來提升客服的接待效率驱证。 總之,整個(gè) 2.0 就是圍繞提升客服效率和用戶體驗(yàn)恋腕。 而我們擔(dān)心的效率問題在 2.0 高速發(fā)展業(yè)務(wù)的時(shí)期還沒有出現(xiàn)抹锄,但業(yè)務(wù)量正在逐漸積累,我們知道它快要爆了。 到 2012 年末伙单,度過雙十一后開始了 3.0 的一次重大架構(gòu)升級获高。
3.0 爆發(fā)(2013 - 2014)
經(jīng)歷了 2.0 時(shí)代一整年的業(yè)務(wù)高速發(fā)展,實(shí)際上代碼規(guī)模膨脹的很快吻育。 與代碼一塊膨脹的還有團(tuán)隊(duì)念秧,從最初的 4 個(gè)人到近 30 人。 團(tuán)隊(duì)大了后布疼,一個(gè)系統(tǒng)多人開發(fā)摊趾,開發(fā)人員層次不一,規(guī)范難統(tǒng)一游两,系統(tǒng)模塊耦合重砾层,改動(dòng)溝通和依賴多,上線風(fēng)險(xiǎn)難以控制贱案。 一個(gè)單獨(dú) tomcat 應(yīng)用多實(shí)例部署模型終于走到頭了肛炮,這個(gè)版本架構(gòu)升級的主題就是服務(wù)化。
服務(wù)化的第一個(gè)問題如何把一個(gè)大的應(yīng)用系統(tǒng)切分成子服務(wù)系統(tǒng)宝踪。 當(dāng)時(shí)的背景是京東的部署還在半自動(dòng)化年代铸董,自動(dòng)部署系統(tǒng)剛起步,子服務(wù)系統(tǒng)若按業(yè)務(wù)劃分太細(xì)太多肴沫,部署工作量很大且難管理粟害。 所以當(dāng)時(shí)我們不是按業(yè)務(wù)功能分區(qū)服務(wù)的,而是按業(yè)務(wù)重要性級別劃分了 0颤芬、1悲幅、2 三個(gè)級別不同的子業(yè)務(wù)服務(wù)系統(tǒng)。 另外就是獨(dú)立了一組接入服務(wù)站蝠,針對不同渠道和通信方式的接入端汰具,見下圖。
更細(xì)化的應(yīng)用服務(wù)和架構(gòu)分層方式可見下圖菱魔。
這次大的架構(gòu)升級留荔,主要考慮了三個(gè)方面:穩(wěn)定性、效率和容量澜倦。 做了下面這些事情:
- 業(yè)務(wù)分級聚蝶、核心、非核心業(yè)務(wù)隔離
- 多機(jī)房部署藻治,流量分流碘勉、容災(zāi)冗余、峰值應(yīng)對冗余
- 讀庫多源桩卵,失敗自動(dòng)轉(zhuǎn)移
- 寫庫主備验靡,短暫有損服務(wù)容忍下的快速切換
- 外部接口倍宾,失敗轉(zhuǎn)移或快速斷路
- Redis 主備,失敗轉(zhuǎn)移
- 大表遷移胜嗓,MongoDB 取代 MySQL 存儲消息記錄
- 改進(jìn)消息投遞模型
前 6 條基本屬于考慮系統(tǒng)穩(wěn)定性高职、可用性方面的改進(jìn)升級。 這一塊屬于陸續(xù)迭代完成的辞州,承載很多失敗轉(zhuǎn)移的配置和控制功能在上面圖中是由管控中心提供的怔锌。 第 7 條主要是隨著業(yè)務(wù)量的上升,單日消息量越來越大后孙技,使用了 MongoDB 來單獨(dú)存儲量最大的聊天記錄。 第 8 條是針對 1.0 版本消息輪詢效率低的改進(jìn)排作,改進(jìn)后的投遞方式如下圖所示:
不再是輪詢了牵啦,而是讓終端每次建立連接后注冊接入點(diǎn)位置,消息投遞前定位連接所在接入點(diǎn)位置再推送過去妄痪。 這樣投遞效率就是恒定的了哈雏,而且很容易擴(kuò)展,在線人數(shù)越多則連接數(shù)越多衫生,只需要擴(kuò)展接入點(diǎn)即可裳瘪。 其實(shí),這個(gè)模型依然還有些小問題罪针,主要出在離線消息的處理上彭羹,可以先思考下,我們最后再講泪酱。
3.0 經(jīng)過了兩年的迭代式升級派殷,單純從業(yè)務(wù)量上來說還可以繼續(xù)支撐很長時(shí)間的增長。 但實(shí)際上到 2014 年底我們面對的不再是業(yè)務(wù)量的問題墓阀,而是業(yè)務(wù)模式的變化毡惜。 這直接導(dǎo)致了一個(gè)全新時(shí)代的到來。
4.0 涅槃(2015 至今 )
2014 年京東的組織架構(gòu)發(fā)生了很大變化斯撮,從一個(gè)公司變成了一個(gè)集團(tuán)经伙,下設(shè)多個(gè)子公司。 原來的商城成為了其中一個(gè)子公司勿锅,新成立的子公司包括京東金融帕膜、京東智能、京東到家溢十、拍拍泳叠、海外事業(yè)部等。 各自業(yè)務(wù)范圍不同茶宵,業(yè)務(wù)模式也不同危纫,但不管什么業(yè)務(wù)總是需要客服服務(wù)。 如何復(fù)用原來為商城量身訂做的咚咚客服系統(tǒng)并支持其他子公司業(yè)務(wù)快速接入成為我們新的課題。
最早要求接入的是拍拍網(wǎng)种蝶,它是從騰訊收購的契耿,所以是完全不同的賬戶和訂單交易體系。 由于時(shí)間緊迫螃征,我們把為商城訂做的部分剝離搪桂,基于 3.0 架構(gòu)對接拍拍又單獨(dú)訂做了一套,并獨(dú)立部署盯滚,像下面這樣踢械。
雖然在業(yè)務(wù)要求的時(shí)間點(diǎn)前完成了上線,但這樣做也帶來了明顯的問題:
- 復(fù)制工程魄藕,定制業(yè)務(wù)開發(fā)内列,多套源碼維護(hù)成本高
- 獨(dú)立部署,至少雙機(jī)房主備外加一個(gè)灰度集群背率,資源浪費(fèi)大
以前我們都是面向業(yè)務(wù)去架構(gòu)系統(tǒng)话瞧,如今新的業(yè)務(wù)變化形勢下我們開始考慮面向平臺去架構(gòu),在統(tǒng)一平臺上跑多套業(yè)務(wù)寝姿,統(tǒng)一源碼交排,統(tǒng)一部署,統(tǒng)一維護(hù)饵筑。 把業(yè)務(wù)服務(wù)繼續(xù)拆分埃篓,剝離出最基礎(chǔ)的 IM 服務(wù),IM 通用服務(wù)根资,客服通用服務(wù)都许,而針對不同的業(yè)務(wù)特殊需求做最小化的定制服務(wù)開發(fā)。 部署方式則以平臺形式部署嫂冻,不同的業(yè)務(wù)方的服務(wù)跑在同一個(gè)平臺上胶征,但數(shù)據(jù)互相隔離。 服務(wù)繼續(xù)被拆分的更微两胺拢化睛低,形成了一組服務(wù)矩陣(見下圖)。
而部署方式服傍,只需要在雙機(jī)房建立兩套對等集群钱雷,并另外建一個(gè)較小的灰度發(fā)布集群即可,所有不同業(yè)務(wù)都運(yùn)行在統(tǒng)一平臺集群上吹零,如下圖罩抗。
更細(xì)粒度的服務(wù)意味著每個(gè)服務(wù)的開發(fā)更簡單,代碼量更小灿椅,依賴更少套蒂,隔離穩(wěn)定性更高钞支。 但更細(xì)粒度的服務(wù)也意味著更繁瑣的運(yùn)維監(jiān)控管理,直到今年公司內(nèi)部彈性私有云操刀、緩存云烁挟、消息隊(duì)列、部署骨坑、監(jiān)控撼嗓、日志等基礎(chǔ)系統(tǒng)日趨完善, 使得實(shí)施這類細(xì)粒度劃分的微服務(wù)架構(gòu)成為可能欢唾,運(yùn)維成本可控且警。 而從當(dāng)初 1.0 的 1 種應(yīng)用進(jìn)程,到 3.0 的 6礁遣、7 種應(yīng)用進(jìn)程斑芜,再到 4.0 的 50+ 更細(xì)粒度的不同種應(yīng)用進(jìn)程。 每種進(jìn)程再根據(jù)承載業(yè)務(wù)流量不同分配不同的實(shí)例數(shù)亡脸,真正的實(shí)例進(jìn)程數(shù)會過千押搪。 為了更好的監(jiān)控和管理這些進(jìn)程树酪,為此專門定制了一套面向服務(wù)的運(yùn)維管理系統(tǒng)浅碾,見下圖。
統(tǒng)一服務(wù)運(yùn)維提供了實(shí)用的內(nèi)部工具和庫來幫助開發(fā)更健壯的微服務(wù)续语。 包括中心配置管理垂谢,流量埋點(diǎn)監(jiān)控,數(shù)據(jù)庫和緩存訪問疮茄,運(yùn)行時(shí)隔離滥朱,如下圖所示是一個(gè)運(yùn)行隔離的圖示:
細(xì)粒度的微服務(wù)做到了進(jìn)程間隔離,嚴(yán)格的開發(fā)規(guī)范和工具庫幫助實(shí)現(xiàn)了異步消息和異步 HTTP 來避免多個(gè)跨進(jìn)程的同步長調(diào)用鏈力试。 進(jìn)程內(nèi)部通過切面方式引入了服務(wù)增強(qiáng)容器 Armor 來隔離線程徙邻, 并支持進(jìn)程內(nèi)的單獨(dú)業(yè)務(wù)降級和同步轉(zhuǎn)異步化執(zhí)行。而所有這些工具和庫服務(wù)都是為了兩個(gè)目標(biāo):
- 讓服務(wù)進(jìn)程運(yùn)行時(shí)狀態(tài)可見
- 讓服務(wù)進(jìn)程運(yùn)行時(shí)狀態(tài)可被管理和改變
最后我們回到前文留下的一個(gè)懸念畸裳,就是關(guān)于消息投遞模型的缺陷缰犁。 一開始我們在接入層檢測到終端連接斷開后,消息無法投遞怖糊,再將消息緩存下來帅容,等終端重連接上來再拉取離線消息。 這個(gè)模型在移動(dòng)時(shí)代表現(xiàn)的很不好伍伤,因?yàn)橐苿?dòng)網(wǎng)絡(luò)的不穩(wěn)定性并徘,導(dǎo)致經(jīng)常斷鏈后重連。 而準(zhǔn)確的檢測網(wǎng)絡(luò)連接斷開是依賴一個(gè)網(wǎng)絡(luò)超時(shí)的扰魂,導(dǎo)致檢測可能不準(zhǔn)確麦乞,引發(fā)消息假投遞成功蕴茴。 新的模型如下圖所示,它不再依賴準(zhǔn)確的網(wǎng)絡(luò)連接檢測路幸,投遞前待確認(rèn)消息 id 被緩存荐开,而消息體被持久存儲。 等到終端接收確認(rèn)返回后简肴,該消息才算投妥晃听,未確認(rèn)的消息 id 再重新登陸后或重連接后作為離線消息推送。 這個(gè)模型不會產(chǎn)生消息假投妥導(dǎo)致的丟失砰识,但可能導(dǎo)致消息重復(fù)能扒,只需由客戶終端按消息 id 去重即可。
京東咚咚誕生之初正是京東技術(shù)轉(zhuǎn)型到 Java 之時(shí)辫狼,經(jīng)歷這些年的發(fā)展初斑,取得了很大的進(jìn)步。 從草根走向?qū)I(yè)膨处,從弱小走向規(guī)模见秤,從分散走向統(tǒng)一,從雜亂走向規(guī)范真椿。 本文主要重心放在了幾年來咚咚架構(gòu)演進(jìn)的過程鹃答,技術(shù)架構(gòu)單獨(dú)拿出來看我認(rèn)為沒有絕對的好與不好, 技術(shù)架構(gòu)總是要放在彼時(shí)的背景下來看突硝,要考慮業(yè)務(wù)的時(shí)效價(jià)值测摔、團(tuán)隊(duì)的規(guī)模和能力、環(huán)境基礎(chǔ)設(shè)施等等方面解恰。 架構(gòu)演進(jìn)的生命周期適時(shí)匹配好業(yè)務(wù)的生命周期锋八,才可能發(fā)揮最好的效果。