乡下人产国偷v产偷v自拍,国产午夜片在线观看,婷婷成人亚洲综合国产麻豆,久久综合给合久久狠狠狠9

  • <output id="e9wm2"></output>
    <s id="e9wm2"><nobr id="e9wm2"><ins id="e9wm2"></ins></nobr></s>

    • 分享

      微信異步化改造實踐:8億月活、單機千萬連接背后的后臺解決方案

       liang1234_ 2019-01-31

      如今,微信擁有月活躍用戶8億。不可否認,當今的微信后臺擁有著強大的并發(fā)能力。不過, 正如羅馬并非一日建成;微信的技術(shù)也曾經(jīng)略顯稚嫩。


      微信誕生于2011年1月,當年用戶規(guī)模為0.1億左右;2013年11月,微信月活躍用戶數(shù)達到3.55億,一躍成為亞洲地區(qū)擁有最大用戶群體的移動終端即時通訊軟件。面對如此體量的提升,微信后臺也曾遭遇棘手的窘境;令人贊嘆的是,技術(shù)人及時地做出了漂亮的應(yīng)對。

      這背后有著怎樣的技術(shù)故事?此時此刻,你在微信手機端發(fā)出的請求,是怎樣被后臺消化和處理的?

      這次,我們聚焦微信后臺解決方案之協(xié)程庫libco(libco介紹和源碼下載)。該項目在保留后臺敏捷的同步風格同時,提高了系統(tǒng)的并發(fā)能力,節(jié)省了大量的服務(wù)器成本;自2013年起穩(wěn)定運行于微信的數(shù)萬臺機器之上。

      本文是《開源libco庫:單機千萬連接、支撐微信8億用戶的后臺框架基石》一文的進一步技術(shù)挖掘和整理。

      當初的微信后端到底遇到了問題


      早期微信后臺因為業(yè)務(wù)需求復(fù)雜多變、產(chǎn)品要求快速迭代等需求,大部分模塊都采用了半同步半異步模型。接入層為異步模型,業(yè)務(wù)邏輯層則是同步的多進程或多線程模型,業(yè)務(wù)邏輯的并發(fā)能力只有幾十到幾百。

      隨著微信業(yè)務(wù)的增長,直到2013年中,微信后臺機器規(guī)模已達到1萬多臺,涉及數(shù)百個后臺模塊,RPC調(diào)用每分鐘數(shù)十億。在如此龐大復(fù)雜的系統(tǒng)規(guī)模下,每個模塊很容易受到后端服務(wù)或者網(wǎng)絡(luò)抖動的影響。因此我們急需對微信后臺進行異步化的改造。

      微信后臺異步化改造方案的考量


      當時微信技術(shù)團隊有兩種選擇:

      • A 線程異步化:把所有服務(wù)改造成異步模型,等同于從框架到業(yè)務(wù)邏輯代碼的徹底改造。
      • B 協(xié)程異步化:對業(yè)務(wù)邏輯非侵入的異步化改造,即只修該少量框架代碼。

      兩者相比,工作量和風險系數(shù)的差異顯而易見。雖然A方案服務(wù)器端多線程異步處理是常見做法,對提高并發(fā)能力這個原始目標非常奏效;但是對于微信后臺如此復(fù)雜的系統(tǒng),這過于耗時耗力且風險巨大。

      無論是異步模型還是同步模型,都需要保存異步狀態(tài)。所以兩者在技術(shù)細節(jié)的相同點是,兩個方案,都是需要維護當前請求的狀態(tài)。在A異步模型中方案,當請求需要被異步執(zhí)行時,需要主動把請求相關(guān)數(shù)據(jù)保存起來,再等待狀態(tài)機的下一次調(diào)度執(zhí)行;而在B協(xié)程模型方案中,異步狀態(tài)的保存與恢復(fù)是自動的,協(xié)程恢復(fù)執(zhí)行的時候就是上一次退出時的上下文。

      因此,B協(xié)程方案不需要顯式地維護異步狀態(tài):一方面在編程上可以更簡單和直接;另一方面協(xié)程中只需要保存少量的寄存器。因此在復(fù)雜系統(tǒng)上,協(xié)程服務(wù)的性能可能比純異步模型更優(yōu)。

      綜合以上考慮,最終微信選擇了B方案,通過協(xié)程的方式對微信后臺上百個模塊進行了異步化改造。

      如何接管歷史遺留的同步風格API


      方案敲定之后,接下來做的就是實現(xiàn)異步化的同時盡可能地少做代碼修改。

      通常而言,一個常規(guī)的網(wǎng)絡(luò)后臺服務(wù)需要connect、write、read等系列步驟,如果使用同步風格的API對網(wǎng)絡(luò)進行調(diào)用,整個服務(wù)線程會因為等待網(wǎng)絡(luò)交互而掛起,這就會造成等待并占用資源。原來的這種情況很明顯地影響到了系統(tǒng)的并發(fā)性能,但是當初這樣的選擇是因為對應(yīng)的同步編程風格具有其獨特的優(yōu)勢:代碼邏輯清晰、易于編寫并且支持業(yè)務(wù)快速迭代敏捷開發(fā)。

      微信團隊的改造方案需要消除同步風格API的缺點,但是同時還希望保持同步編程的優(yōu)點。

      最后在不修改線上已有的業(yè)務(wù)邏輯代碼的情況下,微信團隊的libco框架創(chuàng)新地接管了網(wǎng)絡(luò)調(diào)用接口(Hook)。把協(xié)程的讓出與恢復(fù)作為異步網(wǎng)絡(luò)IO中的一次事件注冊與回調(diào)。當業(yè)務(wù)處理遇到同步網(wǎng)絡(luò)請求的時候,libco層會把本次網(wǎng)絡(luò)請求注冊為異步事件,當前的協(xié)程讓出CPU占用,CPU交給其它協(xié)程執(zhí)行。在網(wǎng)絡(luò)事件發(fā)生或者超時的時候,libco會自動的恢復(fù)協(xié)程執(zhí)行。

      libco的架構(gòu)


      libco架構(gòu)從設(shè)計的時候就已經(jīng)確立下來了,最近的在GitHub上一次較大更新主要是功能上的更新。注:libco為開源項目,源碼同步更新,詳見:http://www./thread-623-1-1.html

      libco框架有三層,分別是協(xié)程接口層、系統(tǒng)函數(shù)Hook層以及事件驅(qū)動層。

      微信異步化改造實踐:8億月活、單機千萬連接背后的后臺解決方案_1.png 

      協(xié)程接口層實現(xiàn)了協(xié)程的基本源語。co_create、co_resume等簡單接口負責協(xié)程創(chuàng)建于恢復(fù)。co_cond_signal類接口可以在協(xié)程間創(chuàng)建一個協(xié)程信號量,可用于協(xié)程間的同步通信。

      系統(tǒng)函數(shù)Hook層負責主要負責系統(tǒng)中同步API到異步執(zhí)行的轉(zhuǎn)換。對于常用的同步網(wǎng)絡(luò)接口,Hook層會把本次網(wǎng)絡(luò)請求注冊為異步事件,然后等待事件驅(qū)動層的喚醒執(zhí)行。

      事件驅(qū)動層實現(xiàn)了一個簡單高效的異步網(wǎng)路框架,里面包含了異步網(wǎng)絡(luò)框架所需要的事件與超時回調(diào)。對于來源于同步系統(tǒng)函數(shù)Hook層的請求,事件注冊與回調(diào)實質(zhì)上是協(xié)程的讓出與恢復(fù)執(zhí)行。 

      相比線程,libco選擇協(xié)程意味著什么?


      比起線程,對于很多人而言,協(xié)程的應(yīng)用并不是那么輕車熟路。

      線程和協(xié)程的相同點是什么?我們可以簡單認為協(xié)程是一種用戶態(tài)線程,它與線程一樣擁有獨立的寄存器上下文以及運行棧,對程序員最直觀的效果就是,代碼可以在協(xié)程里面正常的運作,就像在線程里面一樣。但是線程和協(xié)程還是有區(qū)別的,我們需要重點關(guān)注是運行棧管理模式與協(xié)程調(diào)度策略。關(guān)于這兩點的具體執(zhí)行,在本文后續(xù)部分會談及。

      那兩者的不同點呢?協(xié)程的創(chuàng)建與調(diào)度相比線程要輕量得多,而且協(xié)程間的通信與同步是可以無鎖的,任一時刻都可以保證只有本協(xié)程在操作線程內(nèi)的資源。

      微信的方案是使用協(xié)程,但這意味著面臨以下挑戰(zhàn):
      • 業(yè)界協(xié)程在C/C++環(huán)境下沒有大規(guī)模應(yīng)用的經(jīng)驗;
      • 如何處理同步風格的API調(diào)用,如Socket、mysqlclient等;
      • 如何控制協(xié)程調(diào)度;
      • 如何處理已有全局變量、線程私有變量的使用。

      下面我們來探討如何攻克這4個挑戰(zhàn)。 

      挑戰(zhàn)一 :前所未有的大規(guī)模應(yīng)用C/C++協(xié)程


      實際上,協(xié)程這個概念的確很早就提出來了,但是確是因為最近幾年在某些語言中(如lua、go等)被廣泛的應(yīng)用而逐漸的被大家所熟知。但是真正用于C/C++語言的、并且是大規(guī)模生產(chǎn)的著實不多。

      而這個libco框架中,除了協(xié)程切換時寄存器保存與恢復(fù)使用了匯編代碼,其它代碼實現(xiàn)都是用C/C++語言編寫的。 

      那么,為什么我們選擇了C/C++語言?

      當前微信后臺絕大部分服務(wù)都基于C++,原因是微信最早的后臺開發(fā)團隊從郵箱延續(xù)而來,郵箱團隊一直使用C++作為后臺主流開發(fā)語言,而且C++能滿足微信后臺對性能和穩(wěn)定性的要求。

      微信的C++后臺服務(wù)框架增加了協(xié)程支持之后,高并發(fā)和快速開發(fā)的矛盾解決了。開發(fā)者絕大部分情況下只需要關(guān)注并發(fā)數(shù)的配置,不需要關(guān)注協(xié)程本身。其他語言微信也會在一些工具里面嘗試,但是對于整個微信后臺而言,C++仍是微信團隊未來長期的主流語言。

      挑戰(zhàn)二:保留同步風格的API


      這里的做法我們在上文中提到了處理同步風格的API的思路方法:大部分同步風格的API我們都通過Hook的方法來接管了,libco會在恰當?shù)臅r機調(diào)度協(xié)程恢復(fù)執(zhí)行。

      怎樣防止協(xié)程庫調(diào)度器被阻塞?

      libco的系統(tǒng)函數(shù)Hook層主要處理同步API到異步執(zhí)行的轉(zhuǎn)換,我們當前的hook層只處理了主要的同步網(wǎng)絡(luò)接口,對于這些接口,同步調(diào)用會被異步執(zhí)行,不會導(dǎo)致系統(tǒng)的線程阻塞。當然,我們還有少量未Hook的同步接口,這些接口的調(diào)用可能會導(dǎo)致協(xié)程調(diào)度器阻塞等待。

      與線程類似,當我們操作跨線程數(shù)據(jù)的時候,需要使用線程安全級別的函數(shù)。而在協(xié)程環(huán)境下,也是有協(xié)程安全的代碼約束。在微信后臺,我們約束了不能使用導(dǎo)致協(xié)程阻塞的函數(shù),比如pthread_mutex、sleep類函數(shù)(可以用 poll(NULL, 0, timeout) 代替)等。而對于已有系統(tǒng)的改造,就需要審核已有代碼是否符合協(xié)程安全規(guī)范。 

      挑戰(zhàn)三:調(diào)度千萬級協(xié)程


      調(diào)度策略方面,我們可以看下Linux的進程調(diào)度,從早期的O(1)到目前CFS完全公平調(diào)度,經(jīng)過了很復(fù)雜的演進過程,而協(xié)程調(diào)度事實上也是可以參考進程調(diào)度方法的,比如說你可以定義一種調(diào)度策略,使得協(xié)程在不同的線程間切換,但是這樣做會帶來昂貴的切換代價。在進程/線程上面,后臺服務(wù)通常已經(jīng)做了足夠多的工作,使得多核資源得到充分使用,所以協(xié)程的定位應(yīng)該是在這個基礎(chǔ)上發(fā)揮最大的性能。

      libco的協(xié)程調(diào)度策略很簡潔,單個協(xié)程限定在固定的線程內(nèi)部,僅在網(wǎng)絡(luò)IO阻塞等待時候切出,在網(wǎng)絡(luò)IO事件觸發(fā)時候切回,也就是說在這個層面上面可以認為協(xié)程就是有限狀態(tài)機,在事件驅(qū)動的線程里面工作,相信后臺開發(fā)的同學(xué)會一下子就明白了。

      那么,怎么實現(xiàn)千萬級別呢?

      libco默認是每一個協(xié)程獨享一個運行棧,在協(xié)程創(chuàng)建的時候,從堆內(nèi)存分配一個固定大小的內(nèi)存作為該協(xié)程的運行棧。如果我們用一個協(xié)程處理前端的一個接入連接,那對于一個海量接入服務(wù)來說,我們的服務(wù)的并發(fā)上限就很容易受限于內(nèi)存。

      所以量級的問題就轉(zhuǎn)換成了怎樣高效使用內(nèi)存的問題。

      為了解決這個問題,libco采用的是共享棧模式。(傳統(tǒng)運行棧管理有stackfull和stackless兩種模式)簡單來講,是若干個協(xié)程共享同一個運行棧。

      同一個共享棧下的協(xié)程間切換的時候,需要把當前的運行棧內(nèi)容拷貝到協(xié)程的私有內(nèi)存中。為了減少這種內(nèi)存拷貝次數(shù),共享棧的內(nèi)存拷貝只發(fā)生在不同協(xié)程間的切換。當共享棧的占用者一直沒有改變的時候,則不需要拷貝運行棧。

      微信異步化改造實踐:8億月活、單機千萬連接背后的后臺解決方案_2.png 

      再具體一點講講共享棧的原理:libco默認模式(stackfull) 滿足大部分的業(yè)務(wù)場景,每個協(xié)程獨占128k??臻g,只需1G內(nèi)存就可以支持萬級協(xié)程。 而共享棧是libco新增的一個特性,可以支持單機千萬協(xié)程,應(yīng)對海量連接特殊場景。實現(xiàn)原理上,共享棧模式在傳統(tǒng)的stackfull和stackless兩種模式之間做了個微創(chuàng)新,用戶可以自定義分配若干個共享棧內(nèi)存,協(xié)程創(chuàng)建時指定使用哪一個共享棧。

      不同協(xié)程之間的切換、 如何主動退出一個正在執(zhí)行的協(xié)程?我們把共享同一塊棧內(nèi)存的多個協(xié)程稱為協(xié)程組,協(xié)程組內(nèi)不同協(xié)程之間切換需要把棧內(nèi)存拷貝到協(xié)程的私有空間,而協(xié)程組內(nèi)同一個協(xié)程的讓出與恢復(fù)執(zhí)行則不需要拷貝棧內(nèi)存,可以認為共享棧的棧內(nèi)存是“寫時拷貝”的。

      共享棧下的協(xié)程切換與退出,與普通協(xié)程模式的API一致,co_yield與co_resume,libco底層會實現(xiàn)共享棧的模式下的按需拷貝棧內(nèi)存。 

      挑戰(zhàn)四 :全局變量 vs 私有變量


      在stackfull模式下面,局部變量的地址是一直不變的;而stackless模式下面,只要協(xié)程被切出,那么局部變量的地址就失效了,這是開發(fā)者需要注意的地方。

      libco默認的棧模式是每一個協(xié)程獨享運行棧的,在這個模式下,開發(fā)者需要注意棧內(nèi)存的使用,盡量避免 char buf[128 * 1024] 這種超大棧變量的申請,當棧使用大小超過本協(xié)程棧大小的時候,就可能導(dǎo)致棧溢出的core。

      而在共享棧模式下,雖然在協(xié)程創(chuàng)建的時候可以映射到一個比較大的棧內(nèi)存上面,但是當本協(xié)程需要讓出給其它協(xié)程執(zhí)行的時候,已使用棧的拷貝保存開銷也是有的,因此最好也是盡量減少大的局部變量使用。更多的,共享棧模式下,因為是多個協(xié)程共享了同一個??臻g,因此,用戶需要注意協(xié)程內(nèi)的局部棧變量地址不可以跨協(xié)程傳遞。

      協(xié)程私有變量的使用場景與線程私有變量類似,協(xié)程私有變量是全局可見的,不同的協(xié)程會對同一個協(xié)程變量保存自己的副本。開發(fā)者可以通過我們的API宏聲明協(xié)程私有變量,在使用上無特別需要注意的地方。

      多進程程序改造為多線程程序時候,我們可以用__thread來對全局變量進行快速修改,而在協(xié)程環(huán)境下,我們創(chuàng)造了協(xié)程變量ROUTINE_VAR,極大簡化了協(xié)程的改造工作量。

      關(guān)于協(xié)程私有變量,因為協(xié)程實質(zhì)上是線程內(nèi)串行執(zhí)行的,所以當我們定義了一個線程私有變量的時候,可能會有重入的問題。比如我們定義了一個__thread的線程私有變量,原本是希望每一個執(zhí)行邏輯獨享這個變量的。但當我們的執(zhí)行環(huán)境遷移到協(xié)程了之后,同一個線程私有變量,可能會有多個協(xié)程會操作它,這就導(dǎo)致了變量沖入的問題。

      為此,我們在做libco異步化改造的時候,把大部分的線程私有變量改成了協(xié)程級私有變量。協(xié)程私有變量具有這樣的特性:當代碼運行在多線程非協(xié)程環(huán)境下時,該變量是線程私有的;當代碼運行在協(xié)程環(huán)境的時候,此變量是協(xié)程私有的。底層的協(xié)程私有變量會自動完成運行環(huán)境的判斷并正確返回所需的值。

      協(xié)程私有變量對于現(xiàn)有環(huán)境同步到異步化改造起了舉足輕重的作用,同時我們定義了一個非常簡單方便的方法定義協(xié)程私有變量,簡單到只需一行聲明代碼即可。 

      簡言之,一句話總結(jié)libco庫的原理,在協(xié)程里面用同步風格編寫代碼,實際運作是事件驅(qū)動的有限狀態(tài)機,由上層的進程/線程負責多核資源的使用。

      最終效果:大功告成


      微信團隊曾把一個狀態(tài)機驅(qū)動的純異步代理服務(wù)改成了基于libco協(xié)程的服務(wù),在性能上比之前提升了10%到20%,并且,在基于協(xié)程的同步模型下,很簡單的就實現(xiàn)了批量請求的功能。

      正如當時所愿,使用libco對微信后臺上百個模塊進行了協(xié)程異步化改造,在整個的改造過程中,業(yè)務(wù)邏輯代碼基本沒有改變,修改只是在框架層代碼。微信所做的是把原先在線程內(nèi)執(zhí)行的業(yè)務(wù)邏輯轉(zhuǎn)到了協(xié)程上執(zhí)行。改造的工作主要是復(fù)核系統(tǒng)中線程私有變量、全局變量、線程鎖的使用,確保在協(xié)程切換的時候不會數(shù)據(jù)錯亂或者重入。  

      至今,微信后臺絕大部分服務(wù)都已是多進程或多線程協(xié)程模型,并發(fā)能力相比之前有了質(zhì)的提升,而在這過程中應(yīng)運而生的libco也成為了微信后臺框架的基石。 

        本站是提供個人知識管理的網(wǎng)絡(luò)存儲空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點。請注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購買等信息,謹防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請點擊一鍵舉報。
        轉(zhuǎn)藏 分享 獻花(0

        0條評論

        發(fā)表

        請遵守用戶 評論公約

        類似文章 更多