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