QQ18年
1999年2月10日,騰訊QQ橫空出世。光陰荏苒缀皱,那個(gè)在你屏幕右下角頻頻閃動(dòng)的企鵝已經(jīng)度過了18個(gè)年頭斗这。隨著QQ一同成長的你,還記得它最初的摸樣嗎啤斗?
1999年:騰訊QQ的前身OICQ誕生表箭,該版本具備中文網(wǎng)絡(luò)尋呼機(jī)、公共聊天室以及傳輸文件功能钮莲。
1999年QQ界面
2000年免钻,OICQ正式更名為QQ,發(fā)布視頻聊天功能崔拥、QQ群和QQ秀等功能极舔。
2003年版本,QQ發(fā)布聊天場(chǎng)景链瓦、捕捉屏幕拆魏、給好友播放錄影及QQ炫鈴等功能。
2004年慈俯,QQ新增個(gè)人網(wǎng)絡(luò)硬盤渤刃、遠(yuǎn)程協(xié)助和QQ小秘書功能。
···
幾經(jīng)更迭贴膘,QQ版本也產(chǎn)生許多變化卖子,很多操作方式都變了,也讓QQ更有現(xiàn)代感了刑峡。如今的QQ越來越精美洋闽,越來越簡(jiǎn)潔,如你所見氛琢。
據(jù)不完全統(tǒng)計(jì)喊递,騰訊QQ月活用戶達(dá)到8.7億左右,而這個(gè)數(shù)字還在不斷增加阳似。。铐伴。
如此龐大的用戶群的任何行為撮奏,都會(huì)產(chǎn)生巨大的影響。
2017年春節(jié)当宴,QQ推出AR紅包加入紅包大戰(zhàn)畜吊,經(jīng)調(diào)查手機(jī)QQ的紅包全網(wǎng)滲透率達(dá)到52.9%。
在此期間户矢,后臺(tái)想必又一次承受了海量的壓力玲献,年后第一波推送,來看看騰訊內(nèi)部對(duì)QQ后臺(tái)的接口處理的相關(guān)技術(shù)干貨,或許可以給到你答案捌年。
一
背景
QQ后臺(tái)提供了一套內(nèi)部訪問的統(tǒng)一服務(wù)接口瓢娜,對(duì)騰訊各業(yè)務(wù)部門提供統(tǒng)一的資料關(guān)系鏈訪問服務(wù)递览,后面我們把這套接口簡(jiǎn)稱為DB门坷。
現(xiàn)在說說分set的背景:2013年的某一天塑陵,某個(gè)業(yè)務(wù)的小朋友在申請(qǐng)正式環(huán)境的DB接入權(quán)限后席覆,使用正式環(huán)境來驗(yàn)證剛寫完的測(cè)試程序颓影,循環(huán)向DB接口機(jī)發(fā)送請(qǐng)求包吊履,但因?yàn)檫@個(gè)包格式非法廓推,觸發(fā)了DB解包的一個(gè)bug颓帝,導(dǎo)致收到這些請(qǐng)求包的服務(wù)器群體core
dump励堡,無一幸免谷丸。。应结。刨疼。整個(gè)DB系統(tǒng)的服務(wù)頓時(shí)進(jìn)入癱瘓狀態(tài)。
因此有了故障隔離的需求摊趾,2014年初币狠,我們著手DB的故障隔離增強(qiáng)改造。實(shí)現(xiàn)方法就是分set服務(wù)–把不同業(yè)務(wù)部門的請(qǐng)求定向到不同的服務(wù)進(jìn)程組上砾层,如果某個(gè)業(yè)務(wù)的請(qǐng)求有問題漩绵,最多只影響一個(gè)部門,不會(huì)影響整個(gè)服務(wù)系統(tǒng)肛炮。
二
總體方案
為了更清楚描述分set的方案止吐,我們通過兩個(gè)圖進(jìn)行分set前后的對(duì)比。
分set之前:
分set之后:
從圖中可以看出侨糟,實(shí)現(xiàn)方式其實(shí)非常簡(jiǎn)單碍扔,就是多啟動(dòng)一個(gè)proxy進(jìn)程根據(jù)IP到set的映射關(guān)系分發(fā)請(qǐng)求包到對(duì)應(yīng)set的進(jìn)程上。
三
分set嘗試
很多事情往往看起來非常簡(jiǎn)單秕重,實(shí)現(xiàn)起來卻十分復(fù)雜不同,DB分set就是一個(gè)典型的例子。怎么說呢溶耘?先看看我們剛開始實(shí)現(xiàn)的分set方案二拐。
實(shí)現(xiàn)方案一:通過socket轉(zhuǎn)包給分set進(jìn)程,分set進(jìn)程直接回包給前端凳兵。
這個(gè)方案剛發(fā)布幾臺(tái)后就發(fā)現(xiàn)問題:
1百新,有前端業(yè)務(wù)投訴回包端口不對(duì)導(dǎo)致訪問失敗。后來了解這些業(yè)務(wù)會(huì)對(duì)回包端口進(jìn)行校驗(yàn)庐扫,如果端口不一致就會(huì)把包丟棄饭望。
2仗哨,CPU比原來上漲了25%(同樣的請(qǐng)求量,原來是40%铅辞,使用這個(gè)方案后CPU變成50%)
回包端口改變的問題因?yàn)橛绊憳I(yè)務(wù)(業(yè)務(wù)就是我們的上帝厌漂,得罪不起^^),必須馬上解決巷挥,于是有了方案二桩卵。
實(shí)現(xiàn)方案二:通過socket轉(zhuǎn)包給分set進(jìn)程,分set進(jìn)程回包給proxy倍宾,由proxy回包雏节。
改動(dòng)很快完成,一切順利高职,馬上鋪開批量部署钩乍。。怔锌。寥粹。
晚上10點(diǎn)準(zhǔn)時(shí)迎來第一次高峰,DB出現(xiàn)大量的丟包和CPU告警埃元,運(yùn)維緊急遷移流量涝涤。
第二天全部回滾為未分set的版本。
重新做性能驗(yàn)證的時(shí)候岛杀,發(fā)現(xiàn)CPU比原來漲了50%阔拳,按這個(gè)比例,原來600多臺(tái)機(jī)器类嗤,現(xiàn)在需要增加300多臺(tái)機(jī)器才能撐起同樣請(qǐng)求的容量糊肠。(這是寫本文時(shí)候的機(jī)器數(shù),目前機(jī)器數(shù)已經(jīng)翻倍了~)
后來分析原因的時(shí)候遗锣,發(fā)現(xiàn)網(wǎng)卡收發(fā)包量都漲了一倍货裹,而CPU基本上都消耗在內(nèi)核socket隊(duì)列的處理上,其中競(jìng)爭(zhēng)socket資源的spin_lock占用了超過30%的CPU — 這也正是我們決定一定要做無鎖隊(duì)列的原因精偿。
四
最終實(shí)現(xiàn)方案
做互聯(lián)網(wǎng)服務(wù)弧圆,最大的一個(gè)特點(diǎn)就是,任何一項(xiàng)需求笔咽,做與不做墓阀,都必須在投入、產(chǎn)出拓轻、時(shí)間、質(zhì)量之間做一個(gè)取舍经伙。
前面的嘗試選擇了最簡(jiǎn)單的實(shí)現(xiàn)方式扶叉,目的就是為了能夠盡快上線勿锅,減少群體core掉的風(fēng)險(xiǎn),但卻引入了容量不足的風(fēng)險(xiǎn)枣氧。
既然這個(gè)方案行不通溢十,那就得退而求其次(退說的是延期,次說的是犧牲一些人力和運(yùn)維投入)达吞,方案是很多的张弛,但是需要以人力作為代價(jià)。
舉個(gè)簡(jiǎn)單的實(shí)現(xiàn)方法:安裝一個(gè)內(nèi)核模塊酪劫,掛個(gè)netfilter鉤子吞鸭,直接在網(wǎng)絡(luò)層進(jìn)行分set,再把回包改一下發(fā)送端口覆糟。
這在內(nèi)核實(shí)現(xiàn)是非常非常簡(jiǎn)單的事情刻剥,但卻帶來很大的風(fēng)險(xiǎn):
1,不是所有同事都懂內(nèi)核代碼
2滩字,運(yùn)營環(huán)境的機(jī)器不支持動(dòng)態(tài)加載內(nèi)核模塊造虏,只能重新編譯內(nèi)核
3,從運(yùn)維的角度:動(dòng)內(nèi)核?==?殺雞取卵?—?內(nèi)核有問題麦箍,都不知道找誰了
好吧漓藕,我無法說服開發(fā)運(yùn)營團(tuán)隊(duì),就只能放棄這種想法了–即便很不情愿挟裂。
享钞。。话瞧。跑題了嫩与,言歸正傳,這是我們重新設(shè)計(jì)的方案:
方案描述:
1交排,使用一寫多讀的共享內(nèi)存隊(duì)列來分發(fā)數(shù)據(jù)包划滋,每個(gè)set創(chuàng)建一個(gè)shm_queue,同個(gè)set下面的多個(gè)服務(wù)進(jìn)程通過掃描shm_queue進(jìn)行搶包埃篓。
2处坪,Proxy在分發(fā)的時(shí)候同時(shí)把收包端口、客戶端地址架专、收包時(shí)間戳(用于防滾雪球控制同窘,后面介紹)一起放到shm_queue中。
3部脚,服務(wù)處理進(jìn)程回包的時(shí)候直接使用Raw Socket回包想邦,把回包的端口寫成proxy收包的端口。
看到這里委刘,各位同學(xué)可能會(huì)覺得這個(gè)實(shí)現(xiàn)非常簡(jiǎn)單丧没。鹰椒。。不可否認(rèn)呕童,確實(shí)也是挺簡(jiǎn)單的~~
不過漆际,在實(shí)施的時(shí)候,有一些細(xì)節(jié)是我們不得不考慮的夺饲,包括:
1)這個(gè)共享內(nèi)存隊(duì)列是一寫多讀的(目前是一個(gè)proxy進(jìn)程對(duì)應(yīng)一組set化共享內(nèi)存隊(duì)列奸汇,proxy的個(gè)數(shù)可以配置為多個(gè),但目前只配一個(gè)往声,占單CPU不到10%的開銷)擂找,所以共享內(nèi)存隊(duì)列的實(shí)現(xiàn)必須有效解決讀寫、讀讀沖突的問題烁挟,同時(shí)必須保證高性能婴洼。
2)服務(wù)server需要偵聽后端的回包,同時(shí)還要掃描shm_queue中是否有數(shù)據(jù)撼嗓,這兩個(gè)操作無法在一個(gè)select或者epoll_wait中完成柬采,因此無法及時(shí)響應(yīng)前端請(qǐng)求,怎么辦且警?
3)原來的防滾雪球控制機(jī)制是直接取網(wǎng)卡收包的時(shí)間戳和用戶層收包時(shí)系統(tǒng)時(shí)間的差值粉捻,如果大于一定閥值(比如100ms),就丟棄“呶撸現(xiàn)在server不再直接收包了肩刃,這個(gè)策略也要跟著變化。
基于signal通知機(jī)制的無鎖共享內(nèi)存隊(duì)列
A. 對(duì)于第一個(gè)問題杏头,解決方法就是無鎖共享內(nèi)存隊(duì)列盈包,使用CAS來解決訪問沖突。
這里順便介紹一下CAS(Compare And Swap)醇王,就是一個(gè)匯編指令cmpxchg呢燥,用于原子性執(zhí)行CAS(mem, oldvalue, newvalue):如果mem內(nèi)存地址指向的值等于oldvalue,就把newvalue寫入mem寓娩,否則返回失敗叛氨。
那么,讀的時(shí)候棘伴,只要保證修改ReadIndex的操作是一個(gè)CAS原子操作寞埠,誰成功修改了ReadIndex,誰就獲得對(duì)修改前ReadIndex指向元素的訪問權(quán)焊夸,從而避開多個(gè)進(jìn)程同時(shí)訪問的情況仁连。
B. 對(duì)于第二個(gè)問題,我們的做法就是使用注冊(cè)和signal通知機(jī)制:
工作方式如下:
1)Proxy負(fù)責(zé)初始化信號(hào)共享內(nèi)存
2)Server進(jìn)程啟動(dòng)的時(shí)候調(diào)用注冊(cè)接口注冊(cè)自己的進(jìn)程ID阱穗,并返回進(jìn)程ID在進(jìn)程ID列表中的下標(biāo)(sigindex)
3)在Server進(jìn)入睡眠之前調(diào)用打開通知接口把sigindex對(duì)應(yīng)的bitmap置位怖糊,然后進(jìn)入睡眠函數(shù)(pselect)
4)Proxy寫完數(shù)據(jù)發(fā)現(xiàn)共享內(nèi)存隊(duì)列中的塊數(shù)達(dá)到一定個(gè)數(shù)(比如40帅容,可以配置)的時(shí)候,掃描進(jìn)程bitmap伍伤,根據(jù)對(duì)應(yīng)bit為1的位取出一定個(gè)數(shù)(比如8,可以配置為Server進(jìn)程的個(gè)數(shù))的進(jìn)程ID
5)Proxy遍歷這些進(jìn)程ID遣钳,執(zhí)行kill發(fā)送信號(hào)扰魂,同時(shí)把bitmap對(duì)應(yīng)的位置0(防止進(jìn)程死了,不斷被通知)
6)Server進(jìn)程收到信號(hào)或者超時(shí)后從睡眠函數(shù)中醒來蕴茴,把sigindex對(duì)應(yīng)的bit置0劝评,關(guān)閉通知
除了signal通知,其實(shí)還有很多通知機(jī)制倦淀,包括pipe蒋畜、socket,還有較新的內(nèi)核引入的eventfd撞叽、signalfd等等姻成,我們之所以選擇比較傳統(tǒng)的signal通知,主要因?yàn)楹?jiǎn)單愿棋、高效科展,兼容各種內(nèi)核版本,另外一個(gè)原因糠雨,是因?yàn)閟ignal的對(duì)象是進(jìn)程才睹,我們可以選擇性發(fā)送signal,避免驚群效應(yīng)的發(fā)生甘邀。
防滾雪球控制機(jī)制
前面已經(jīng)說過琅攘,原來的防滾雪球控制機(jī)制是基于網(wǎng)卡收包時(shí)間戳的。但現(xiàn)在server拿不到網(wǎng)卡收包的時(shí)間戳了松邪,只能另尋新路坞琴,新的做法是:
Proxy收包的時(shí)候把收包時(shí)間戳保存起來,跟請(qǐng)求包一起放到隊(duì)列里面测摔,server收包的時(shí)候置济,把這個(gè)時(shí)間戳跟當(dāng)前時(shí)間進(jìn)行對(duì)比。
這樣能更有效的做到防滾雪球控制锋八,因?yàn)槲覀儼堰@個(gè)包在前面的環(huán)節(jié)里面經(jīng)歷的時(shí)間都考慮進(jìn)來了浙于,用圖形描述可能更清楚一點(diǎn)。
五
性能驗(yàn)證
使用shm_queue和raw socket后挟纱,DB接口機(jī)處理性能基本跟原來未分set的性能持平羞酗,新加的proxy進(jìn)程占用的CPU一直維持在單CPU?10%以內(nèi),但攤分到多個(gè)CPU上就變成非常少了(對(duì)于8核的服務(wù)器紊服,只是增加了1.25%的平均CPU開銷檀轨,完全可以忽略不計(jì))胸竞。
最后,分set的這個(gè)版本已經(jīng)正式上線運(yùn)行一段時(shí)間了参萄,目前狀態(tài)穩(wěn)定卫枝。