導(dǎo)語(yǔ)
ibco是微信後臺(tái)大規(guī)模使用的c/c++協(xié)程庫(kù),2013年至今穩(wěn)定運(yùn)作在微信後臺(tái)的數(shù)萬(wàn)臺(tái)機(jī)器上。 libco在2013年的時(shí)候作為騰訊六大開(kāi)源專案首次開(kāi)源,我們最近做了一次較大的更新,同步更新在https://github.com/tencent/libco?上。 libco支援後臺(tái)敏捷的同步風(fēng)格程式設(shè)計(jì)模式,同時(shí)提供系統(tǒng)的高並發(fā)能力。
無(wú)需侵入業(yè)務(wù)邏輯,把多進(jìn)程、多執(zhí)行緒服務(wù)改造成協(xié)程服務(wù),並發(fā)能力得到百倍提升;
支援CGI框架,輕鬆建立web服務(wù)(Newget); 、mysqlclient、ssl等常用第三庫(kù)(New);
可選的共享堆疊模式,單機(jī)輕鬆接入千萬(wàn)連接(New);
完善簡(jiǎn)潔的協(xié)程編程接口
– 類pthread – 類透過(guò)co_create、co_resume等簡(jiǎn)單清晰介面即可完成協(xié)程的建立與復(fù)原; – 類別__thread的協(xié)程私有變數(shù)、協(xié)程間通訊的協(xié)程訊號(hào)量co_signal (New); – 非語(yǔ)言層級(jí)的lambda實(shí)現(xiàn),結(jié)合協(xié)程原地編寫(xiě)並執(zhí)行後臺(tái)非同步任務(wù)(New); – 基於epoll/kqueue實(shí)現(xiàn)的小而輕的網(wǎng)路框架,基於時(shí)間輪盤(pán)實(shí)現(xiàn)的高效能定時(shí)器;
libco產(chǎn)生的背景
『早期微信後臺(tái)因?yàn)闃I(yè)務(wù)需求複雜多變、產(chǎn)品需求快速迭代等需求,大部分模組都採(cǎi)用了半同步半非同步模型。存取層為非同步模型,業(yè)務(wù)邏輯層則是同步的多進(jìn)程或多執(zhí)行緒模型,業(yè)務(wù)邏輯的並發(fā)能力只有幾十到幾百。隨著微信業(yè)務(wù)的成長(zhǎng),系統(tǒng)規(guī)模變得越來(lái)越龐大,每個(gè)模組很容易受到後端服務(wù)/網(wǎng)路抖動(dòng)的影響。
非同步化改造的選擇
為了提升微信後臺(tái)的並發(fā)能力,一般的做法是把現(xiàn)網(wǎng)的所有服務(wù)改成非同步模型。這種做法工程量龐大,從框架到業(yè)務(wù)邏輯程式碼均需要做一次徹底的改造,耗時(shí)耗力且風(fēng)險(xiǎn)巨大。於是我們開(kāi)始考慮使用協(xié)程。
但使用協(xié)程會(huì)面臨以下挑戰(zhàn):
業(yè)界協(xié)程在c/c++環(huán)境下沒(méi)有大規(guī)模應(yīng)用的經(jīng)驗(yàn);
產(chǎn)業(yè)調(diào)度;
『 mysqlclient等;
如何處理已有全域變數(shù)、執(zhí)行緒私有變數(shù)的使用;
最終我們透過(guò)libco解決了上述的所有問(wèn)題,實(shí)現(xiàn)了對(duì)業(yè)務(wù)邏輯非侵入性的非同步化改造。我們使用libco對(duì)微信後臺(tái)上百個(gè)模組進(jìn)行了協(xié)程非同步化改造,改造過(guò)程中業(yè)務(wù)邏輯程式碼基本上沒(méi)有修改。至今,微信後臺(tái)絕大部分服務(wù)都已是多進(jìn)程或多執(zhí)行緒協(xié)程模型,並發(fā)能力相比之前有了質(zhì)的提升,而libco也成為了微信後臺(tái)框架的基石。
libco框架
libco在框架分為三層,分別是介面層、系統(tǒng)函數(shù)Hook層以及事件驅(qū)動(dòng)層。
同步風(fēng)格API的處理
對(duì)於同步風(fēng)格的API,主要是同步的網(wǎng)路調(diào)用,libco的首要任務(wù)是消除這些等待對(duì)資源的佔(zhàn)用,提高系統(tǒng)的並發(fā)效能。一個(gè)常規(guī)的網(wǎng)路後臺(tái)服務(wù),我們可能會(huì)經(jīng)歷connect、write、read等步驟,完成一次完整的網(wǎng)路互動(dòng)。當(dāng)同步的呼叫這些API的時(shí)候,整個(gè)執(zhí)行緒會(huì)因?yàn)榈却W(wǎng)路互動(dòng)而掛起。
雖然同步程式設(shè)計(jì)風(fēng)格的並發(fā)效能並不好,但是它具有程式碼邏輯清晰、易於編寫(xiě)的優(yōu)點(diǎn),並可支援業(yè)務(wù)快速迭代敏捷開(kāi)發(fā)。為了繼續(xù)保持同步程式設(shè)計(jì)的優(yōu)點(diǎn),且不需修改線上已有的業(yè)務(wù)邏輯程式碼,libco創(chuàng)新地接管了網(wǎng)路呼叫介面(Hook),把協(xié)程的讓出與復(fù)原作為非同步網(wǎng)路IO中的一次事件註冊(cè)與回調(diào)。當(dāng)業(yè)務(wù)處理遇到同步網(wǎng)路請(qǐng)求的時(shí)候,libco層會(huì)把本次網(wǎng)路請(qǐng)求註冊(cè)為非同步事件,本協(xié)程讓出CPU佔(zhàn)用,CPU交給其它協(xié)程執(zhí)行。 libco會(huì)在網(wǎng)路事件發(fā)生或逾時(shí)的時(shí)候,自動(dòng)的復(fù)原協(xié)程執(zhí)行。
大部分同步風(fēng)格的API我們都透過(guò)Hook的方法來(lái)接管了,libco會(huì)在恰當(dāng)?shù)臅r(shí)機(jī)調(diào)度協(xié)程恢復(fù)執(zhí)行。
千萬(wàn)級(jí)協(xié)程支援
libco預(yù)設(shè)是每一個(gè)協(xié)程獨(dú)享一個(gè)運(yùn)行棧,在協(xié)程創(chuàng)建的時(shí)候,從堆內(nèi)存分配一個(gè)固定大小的內(nèi)存作為該協(xié)程的運(yùn)行棧。如果我們用一個(gè)協(xié)程處理前端的一個(gè)接入連接,那麼對(duì)於一個(gè)海量接入服務(wù)來(lái)說(shuō),我們的服務(wù)的並發(fā)上限就很容易受限於記憶體。為此,libco也提供了stackless的協(xié)程共享堆疊模式,可以設(shè)定若干個(gè)協(xié)程共享同一個(gè)運(yùn)行堆疊。同一個(gè)共享堆疊下的協(xié)程間切換的時(shí)候,需要把目前的運(yùn)行棧內(nèi)容拷貝到協(xié)程的私有記憶體中。為了減少這種記憶體拷貝次數(shù),共享堆疊的記憶體拷貝只發(fā)生在不同協(xié)程間的切換。當(dāng)共享?xiàng)5膩?zhàn)用者一直沒(méi)有改變的時(shí)候,則不需要拷貝運(yùn)行棧。
libco協(xié)程的共享協(xié)程棧模式使得單機(jī)很容易接入千萬(wàn)連接,只需創(chuàng)建足夠多的協(xié)程即可。我們透過(guò)libco共享堆疊模式創(chuàng)建1千萬(wàn)的協(xié)程(E5-2670 v3 @ 2.30GHz * 2, 128G內(nèi)存),每10萬(wàn)個(gè)協(xié)程共享的使用128k內(nèi)存,整個(gè)穩(wěn)定echo服務(wù)的時(shí)候總內(nèi)存消耗大概為66G。
協(xié)程私有變數(shù)
多進(jìn)程程序改造為多執(zhí)行緒程式時(shí)候,我們可以用__thread來(lái)對(duì)全域變數(shù)進(jìn)行快速修改,而在協(xié)程環(huán)境下,我們創(chuàng)造了協(xié)程變量協(xié)程的改造工作量。
因?yàn)閰f(xié)程實(shí)質(zhì)上是線程內(nèi)串行執(zhí)行的,所以當(dāng)我們定義了一個(gè)線程私有變數(shù)的時(shí)候,可能會(huì)有重入的問(wèn)題。例如我們定義了一個(gè)__thread的線程私有變量,原本就是希望每一個(gè)執(zhí)行邏輯獨(dú)享這個(gè)變數(shù)的。但當(dāng)我們的執(zhí)行環(huán)境遷移到協(xié)程了之後,同一個(gè)執(zhí)行緒私有變量,可能會(huì)有多個(gè)協(xié)程會(huì)操作它,這就導(dǎo)致了變數(shù)衝入的問(wèn)題。為此,我們?cè)谧鰈ibco非同步化改造的時(shí)候,把大部分的執(zhí)行緒私有變數(shù)改成了協(xié)程級(jí)私有變數(shù)。協(xié)程私有變數(shù)具有這樣的特性:當(dāng)程式碼運(yùn)行在多執(zhí)行緒非協(xié)程環(huán)境下時(shí),該變數(shù)是執(zhí)行緒私有的;當(dāng)程式碼運(yùn)行在協(xié)程環(huán)境的時(shí)候,此變數(shù)是協(xié)程私有的。底層的協(xié)程私有變數(shù)會(huì)自動(dòng)完成執(zhí)行環(huán)境的判斷並正確傳回所需的值。
協(xié)程私有變數(shù)對(duì)於現(xiàn)有環(huán)境同步到非同步化改造起了舉足輕重的作用,同時(shí)我們定義了一個(gè)非常簡(jiǎn)單方便的方法定義協(xié)程私有變量,簡(jiǎn)單到只需一行聲明程式碼即可。
gethostbyname的Hook方法
對(duì)於現(xiàn)網(wǎng)服務(wù),有可能需要透過(guò)系統(tǒng)的gethostbyname API介面去查詢DNS取得真實(shí)位址。我們?cè)趨f(xié)程化改造的時(shí)候,發(fā)現(xiàn)我們hook的socket族函數(shù)對(duì)gethostbyname不適用,當(dāng)一個(gè)協(xié)程呼叫了gethostbyname時(shí)會(huì)同步等待結(jié)果,這就導(dǎo)致了同執(zhí)行緒內(nèi)的其它協(xié)程被延遲執(zhí)行。我們對(duì)glibc的gethostbyname源碼進(jìn)行了研究,發(fā)現(xiàn)hook不生效主要是由於glibc內(nèi)部是定義了__poll方法來(lái)等待事件,而不是通用的poll方法;同時(shí)glibc還定義了一個(gè)線程私有變量,不同協(xié)程的切換可能會(huì)重入導(dǎo)致資料不準(zhǔn)確。最終gethostbyname協(xié)程非同步化是透過(guò)Hook __poll方法以及定義協(xié)程私有變數(shù)來(lái)解決的。
gethostbyname是glibc提供的同步查詢dns接口,業(yè)界還有很多優(yōu)秀的gethostbyname的異步化解決方案,但是這些實(shí)現(xiàn)都需要引入一個(gè)第三方庫(kù)並且要求底層提供異步回調(diào)通知機(jī)制。 libco透過(guò)hook方法,在不修改glibc源碼的前提下實(shí)現(xiàn)了的gethostbyname的非同步化。
在多執(zhí)行緒環(huán)境下,我們會(huì)有執(zhí)行緒間同步的需求,例如一個(gè)執(zhí)行緒的執(zhí)行需要等待另一個(gè)執(zhí)行緒的訊號(hào),對(duì)於這個(gè)需求,我們通常是使用pthread_signal 來(lái)解決的。在libco中,我們定義了協(xié)程信號(hào)量co_signal用於處理協(xié)程間的並發(fā)需求,一個(gè)協(xié)程可以透過(guò)co_cond_signal與co_cond_broadcast來(lái)決定通知一個(gè)等待的協(xié)程或喚醒所有等待協(xié)程。
總結(jié)
libco是一個(gè)高效的c/c++協(xié)程庫(kù),提供了完善的協(xié)程編程接口、常用的Socket族函數(shù)Hook等,使得業(yè)務(wù)可用同步編程模型快速迭代開(kāi)發(fā)。隨著幾年來(lái)的穩(wěn)定運(yùn)行,libco作為微信後臺(tái)框架的基石發(fā)揮了舉足輕重的作用。

熱AI工具

Undress AI Tool
免費(fèi)脫衣圖片

Undresser.AI Undress
人工智慧驅(qū)動(dòng)的應(yīng)用程序,用於創(chuàng)建逼真的裸體照片

AI Clothes Remover
用於從照片中去除衣服的線上人工智慧工具。

Clothoff.io
AI脫衣器

Video Face Swap
使用我們完全免費(fèi)的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱門(mén)文章

熱工具

記事本++7.3.1
好用且免費(fèi)的程式碼編輯器

SublimeText3漢化版
中文版,非常好用

禪工作室 13.0.1
強(qiáng)大的PHP整合開(kāi)發(fā)環(huán)境

Dreamweaver CS6
視覺(jué)化網(wǎng)頁(yè)開(kāi)發(fā)工具

SublimeText3 Mac版
神級(jí)程式碼編輯軟體(SublimeText3)