魅族 C++ 微服务框架技术内幕揭秘
2019獨(dú)角獸企業(yè)重金招聘Python工程師標(biāo)準(zhǔn)>>>
分享嘉賓簡(jiǎn)介:魅族科技平臺(tái)事業(yè)部于洋子,專注于高并發(fā)高性能服務(wù)端架構(gòu)設(shè)計(jì)與開發(fā),參與過flyme通訊、推送平臺(tái)、實(shí)時(shí)大數(shù)據(jù)統(tǒng)計(jì)等項(xiàng)目。
kiev,是魅族科技推送平臺(tái)目前使用的C++后臺(tái)開發(fā)框架。2012年,魅族的推送業(yè)務(wù)剛剛有一點(diǎn)從傳統(tǒng)架構(gòu)向微服務(wù)架構(gòu)轉(zhuǎn)型的意識(shí)萌芽,為了在拆分系統(tǒng)的同時(shí)提高開發(fā)效率,決定做一個(gè)C++開發(fā)框架,這就是最早期Kiev的由來。在不斷的演變中,框架也經(jīng)過了多次調(diào)整升級(jí),在此一一進(jìn)行講述和揭秘。框架是為架構(gòu)在做服務(wù),所以整篇內(nèi)容會(huì)在架構(gòu)演進(jìn)和框架演進(jìn)兩條線之間交錯(cuò)展開。
第一版:沒有開發(fā)框架
首個(gè)版本的架構(gòu)非常簡(jiǎn)單粗暴,首先開一個(gè)WEB接口,接入PUSH,再開一個(gè)TCP長(zhǎng)連接的接口,讓手機(jī)連上。這么做的目的就是為了能快速上線。不過快是快了,問題也很嚴(yán)重。這個(gè)版本沒有開發(fā)框架,完全從socket寫起,不僅難寫,而且不能水平擴(kuò)展,承載能力也非常有限。
第二版:框架首次出現(xiàn)
隨著魅族用戶量級(jí)的快速提升,很快迭代了第二個(gè)版本。第二版首次出現(xiàn)了開發(fā)框架,命名為“Kiev”。這個(gè)版本對(duì)手機(jī)連接的部分進(jìn)行了拆分,拆出接入層和路由層,業(yè)務(wù)層支持水平擴(kuò)展,這樣重構(gòu)以后抗住了百萬級(jí)的用戶量。不過同樣存在不少問題,因?yàn)檫€是在用普通的HASH算法在做均衡負(fù)載,擴(kuò)容非常不平滑,容易影響用戶體驗(yàn)。而且隨著用戶量的增長(zhǎng),日志量變的非常多,甚至都要把磁盤刷爆。此外,由于使用的文本協(xié)議很臃腫,當(dāng)某一天中午12點(diǎn)推送高峰期的時(shí)候,整個(gè)公司的機(jī)房帶寬都被吃完,其他業(yè)務(wù)受到了不同程度的干擾。
這個(gè)版本的框架如下,左上角是Kiev協(xié)議,左下角是使用到的一些開源的第三方庫?,包括谷歌開源的Protobuf、用于加解密的Openssl、 用于支持HTTP的Curl、優(yōu)化內(nèi)存分配的Tcmalloc等。右上角是Kiev框架的功能組件,包括提供HTTP接口的FastCGI、一些常用的算法和數(shù)據(jù)結(jié)構(gòu)、日志模塊、編碼常用的定時(shí)器以及一個(gè)自研的單鏈接能達(dá)到10W+QPS的Redis Client。
第三版:增加限速、業(yè)務(wù)流程優(yōu)化、日志切割和壓縮
考慮到前面說到的帶寬撐爆問題,第三版增加了限速模塊。此外還做了一個(gè)業(yè)務(wù)流程上的優(yōu)化,使用redis存儲(chǔ)離線消息,用戶上線時(shí)再推送出去。負(fù)載均衡上,改用一致性HASH算法,這樣做的好處是每次擴(kuò)容受到影響的只有遷移的那一部分用戶,另一部分用戶則不會(huì)受任何影響,擴(kuò)容變得平滑了很多。針對(duì)日志刷爆磁盤的問題,做了一個(gè)每天定時(shí)切割和壓縮日志的腳本。
看看這個(gè)版本在框架上做的一些修改,圖中深色部分為新增的東西:
第四版:全面重構(gòu)
為了徹底解決第二版的一些問題,花了半年多的時(shí)間對(duì)框架進(jìn)行全面重構(gòu)。重構(gòu)主要針對(duì)以下幾點(diǎn):
一是將限速、接入層、路由層、邏輯層等都做成了無狀態(tài)服務(wù),這樣的話在整個(gè)擴(kuò)容的過程中可以做到完全平滑;
二是對(duì)協(xié)議進(jìn)行優(yōu)化,將原本臃腫的文本協(xié)議改為二進(jìn)制協(xié)議,協(xié)議頭從700字節(jié)降到6個(gè)字節(jié),大幅度降低了流量;
三是流程上的優(yōu)化,這個(gè)還是趨于流量的考量。大家都知道移動(dòng)互聯(lián)網(wǎng)有個(gè)很顯著的特點(diǎn),就是手機(jī)網(wǎng)絡(luò)特別不穩(wěn)定,可能這一秒在線,下一秒走進(jìn)電梯就失去信號(hào),這個(gè)時(shí)候如果直接進(jìn)行消息推送的話,既浪費(fèi)機(jī)房帶寬,又沒效果,而且還可能會(huì)出現(xiàn)重復(fù)推送的問題。所以針對(duì)這種情況,魅族的做法是每次先推一個(gè)很小的只有幾個(gè)字節(jié)的消息過去,如果手機(jī)端的網(wǎng)絡(luò)穩(wěn)定,它會(huì)回復(fù)一個(gè)同樣很小的消息,這時(shí)候再真正進(jìn)行消息推送,這樣可以有效利用帶寬資源。而且給每一條消息打上唯一的序號(hào),當(dāng)手機(jī)端每次收到消息時(shí),會(huì)將序號(hào)儲(chǔ)存起來,下次拉取消息的時(shí)候再帶上來,比如某用戶已收到1、2、3的消息,拉取的時(shí)候把3帶上來,服務(wù)端就知道1、2、3都已經(jīng)推過了,直接推送4之后的消息即可,避免消息重復(fù)。
這個(gè)版本的框架改進(jìn)比較小,在上個(gè)版本的基礎(chǔ)上引入MongoDBClient,對(duì)序號(hào)進(jìn)行索引。
業(yè)務(wù)越做越大,發(fā)現(xiàn)新問題1
隨著業(yè)務(wù)越做越大,業(yè)務(wù)流程也變得越來越復(fù)雜。舉個(gè)栗子,魅族有一個(gè)業(yè)務(wù)流程中,請(qǐng)求過來時(shí),會(huì)先和Redis來回交互幾次,然后才訪問MongoDB,最后還要和Redis交互幾次才能返回結(jié)果。
這種時(shí)候如果按早期的異步模式去寫代碼,會(huì)很難看。可以看到整個(gè)業(yè)務(wù)流程被切割的支離破碎,寫代碼的和看代碼的人都會(huì)覺得這種方式很不舒服,也容易出錯(cuò)。
針對(duì)這種復(fù)雜的問題,魅族引入了“協(xié)程”,用仿造Golang的方式自己做了一套協(xié)程框架Libgo。重構(gòu)后的代碼變成如下圖左側(cè)的方式,整個(gè)業(yè)務(wù)流程是順序編寫的,不僅沒有損失運(yùn)行的效率,同時(shí)還提高了開發(fā)的效率。
Libgo的簡(jiǎn)介和開源地址如下:
- 提供CSP模型的協(xié)程功能
- Hook阻塞的系統(tǒng)調(diào)用,IO等待時(shí)自動(dòng)切換協(xié)程
- 無縫集成使用同步網(wǎng)絡(luò)模型的第三方庫 (mysqlclient/CURL)
- 完善的功能體系:Channel / 協(xié)程鎖 / 定時(shí)器 / 線程池等等
開源地址:https://github.com/yyzybb537/libgo
業(yè)務(wù)越做越大,發(fā)現(xiàn)新問題2
在這個(gè)時(shí)期,在運(yùn)營(yíng)過程中有遇到一個(gè)問題,每天早上9點(diǎn)鐘,手機(jī)端會(huì)向服務(wù)端發(fā)一個(gè)小小的訂閱請(qǐng)求,這個(gè)請(qǐng)求一旦超時(shí)會(huì)再來一遍,不斷重試。當(dāng)某天用戶量增長(zhǎng)到1300萬左右的時(shí)候,服務(wù)器雪崩了!
雪崩的原因是因?yàn)檫^載產(chǎn)生的,通過分析發(fā)現(xiàn)過載是在流程中的兩個(gè)服務(wù)器間產(chǎn)生的。服務(wù)器A出現(xiàn)了大量的請(qǐng)求超時(shí)的log,服務(wù)器B出現(xiàn)接收隊(duì)列已滿的log,此時(shí)會(huì)將新請(qǐng)求進(jìn)行丟棄。此時(shí)發(fā)現(xiàn),在服務(wù)器B的接收隊(duì)列中積壓了大量請(qǐng)求,而這些請(qǐng)求又都是已經(jīng)超時(shí)的請(qǐng)求,手機(jī)端已經(jīng)在重試第二次,所以當(dāng)服務(wù)器拿起之前這些請(qǐng)求來處理,也是在做無用功,正因?yàn)榉?wù)器一直在做無用功,手機(jī)端就會(huì)一直重試,因此在外部看來整個(gè)服務(wù)是處于不可用狀態(tài),也就形成了雪崩效應(yīng)。
當(dāng)時(shí)的緊急處理方式是先對(duì)接收隊(duì)列的容量進(jìn)行縮小,提供有損服務(wù)。所謂的有損服務(wù)就是當(dāng)服務(wù)器收到1000個(gè)請(qǐng)求但只能處理200個(gè)請(qǐng)求時(shí),就會(huì)直接丟棄剩下的800個(gè)請(qǐng)求,而不是讓他們排隊(duì)等待,這樣就能避免大量超時(shí)請(qǐng)求的問題。
那緊急處理后,要怎么樣根治這個(gè)問題呢?首先對(duì)這個(gè)過載問題產(chǎn)生的過程進(jìn)行分析,發(fā)現(xiàn)是在接收隊(duì)列堵塞,所以對(duì)接收點(diǎn)進(jìn)行改造,從原來的單隊(duì)列變?yōu)槎嚓?duì)列,按優(yōu)先級(jí)進(jìn)行劃分。核心級(jí)業(yè)務(wù)會(huì)賦予最高級(jí)的優(yōu)先處理隊(duì)列,當(dāng)高優(yōu)先級(jí)的請(qǐng)求處理完后才會(huì)處理低優(yōu)先級(jí)的請(qǐng)求。這樣做的就能保證核心業(yè)務(wù)不會(huì)因?yàn)檫^載問題而受到影響。
還有一點(diǎn)是使用固定數(shù)量的工作協(xié)程處理請(qǐng)求,這樣做的好處是可以控制整個(gè)系統(tǒng)的并發(fā)量,防止請(qǐng)求積壓過多,拖慢系統(tǒng)響應(yīng)速度。
業(yè)務(wù)越做越大,發(fā)現(xiàn)新問題3
在最早的時(shí)候,這一塊是沒有灰度發(fā)布機(jī)制的,所有發(fā)布都是直接發(fā)全網(wǎng),一直到機(jī)器量漲到上百臺(tái)時(shí)依然是用這種方式,如果沒問題當(dāng)然皆大歡喜,有問題則所有一起死。這種方式肯定是無法長(zhǎng)遠(yuǎn)進(jìn)行,需要灰度和分組。但由于服務(wù)是基于TCP長(zhǎng)連接的,在業(yè)內(nèi)目前沒有成熟的解決方案,所以只能自己摸索。
當(dāng)時(shí)的第一個(gè)想法是進(jìn)行分組,分為組1和組2,所有的請(qǐng)求過來前都加上中間層。這樣做的好處是可以分流用戶,當(dāng)某一組出現(xiàn)故障時(shí),不會(huì)影響到全部,也可以導(dǎo)到另外一組去,而且在發(fā)布的時(shí)候也可以只發(fā)其中一組。
那中間層這一塊要怎么做呢?在參考了很多業(yè)界的成熟方案,但大多是基于HTTP協(xié)議的,很少有基于TCP長(zhǎng)連接的方案,最終決定做一個(gè)反向代理。它的靈感是來源于Nginx反向代理,Nginx反向代理大家知道是針對(duì)HTTP協(xié)議,而這個(gè)是要針對(duì)框架的Kiev協(xié)議,恰好魅族在使用ProtoBuf在做協(xié)議解析,具有動(dòng)態(tài)解析的功能,因此基于這樣一個(gè)功能做了Kiev反向代理的組件。這個(gè)組件在啟動(dòng)時(shí)會(huì)向后端查詢提供哪些服務(wù)、每個(gè)服務(wù)有哪些接口、每個(gè)接口要什么樣的請(qǐng)求、回復(fù)什么樣的數(shù)據(jù)等等。將這些請(qǐng)求存儲(chǔ)在反向代理組件中,組成一張路由表。接收到前端的請(qǐng)求時(shí),對(duì)請(qǐng)求的數(shù)據(jù)進(jìn)行動(dòng)態(tài)解析,在路由表中找到可以處理的后端服務(wù)并轉(zhuǎn)發(fā)過去。
第五版:針對(duì)問題,解決問題
有了上述這些規(guī)則后,第五版也就是目前使用的版本部署如下圖。對(duì)邏輯層進(jìn)行了分組,分流用戶。在實(shí)際使用過程中精準(zhǔn)調(diào)控用戶分流規(guī)則,慢慢進(jìn)行遷移,一旦發(fā)現(xiàn)有問題,立即往回倒。此外,還精簡(jiǎn)了存儲(chǔ)層,把性價(jià)比不高的MongoDB砍掉,降低了70%的存儲(chǔ)成本。
很多項(xiàng)目特別是互聯(lián)網(wǎng)項(xiàng)目,在剛剛上線的時(shí)候都有個(gè)美好的開始,美好之處在于最初所有服務(wù)的協(xié)議版本號(hào)都是一樣的。就比如說A服務(wù)、B服務(wù)、C服務(wù)剛開始的時(shí)候全都是1.0,完全不用去考慮兼容性問題。當(dāng)有一天,你需要升級(jí)了,要把這三個(gè)服務(wù)都變成2.0的時(shí)候,如果想平滑的去升級(jí)就只能一個(gè)一個(gè)來。而在這個(gè)升級(jí)的過程中,會(huì)出現(xiàn)低版本調(diào)用高版本,也會(huì)出現(xiàn)高版本調(diào)用低版本的情況,特別蛋疼,這就要求選擇的通訊協(xié)議支持雙向兼容,這也是魅族使用Protobuf的原因。
最終,完整的框架生態(tài)如下。虛線框內(nèi)為后續(xù)將加入的服務(wù)。
魅族消息推送服務(wù)的現(xiàn)狀
該服務(wù)在過去的4年多來一直只是默默的為魅族的100多個(gè)項(xiàng)目提供,前段時(shí)間,正式向社區(qū)所有的開發(fā)者開放了這種推送能力,接入的交流群:QQ488591713。目前有3000萬的長(zhǎng)連接用戶,為100多個(gè)項(xiàng)目提供服務(wù)。集群中有20多個(gè)微服務(wù)和數(shù)百個(gè)服務(wù)進(jìn)程,有100多臺(tái)服務(wù)器,每天的推送量在2億左右。
(文章內(nèi)容由開源中國整理自2016年9月10日的【OSC源創(chuàng)會(huì)】珠海站,轉(zhuǎn)載請(qǐng)注明出處。)
轉(zhuǎn)載于:https://my.oschina.net/osccreate/blog/752986
總結(jié)
以上是生活随笔為你收集整理的魅族 C++ 微服务框架技术内幕揭秘的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 使用BootStrapValidator
- 下一篇: 用 Hasor 谈一谈MVC设计模式