導(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)源項(xiàng)目首次開(kāi)源,我們最近做了一次較大的更新,同步更新在https://github.com/tencent/libco?上。libco支持后臺(tái)敏捷的同步風(fēng)格編程模式,同時(shí)提供系統(tǒng)的高并發(fā)能力。
libco支持的特性
無(wú)需侵入業(yè)務(wù)邏輯,把多進(jìn)程、多線程服務(wù)改造成協(xié)程服務(wù),并發(fā)能力得到百倍提升;
支持CGI框架,輕松構(gòu)建web服務(wù)(New);
支持gethostbyname、mysqlclient、ssl等常用第三庫(kù)(New);
可選的共享?xiàng)DJ剑瑔螜C(jī)輕松接入千萬(wàn)連接(New);
完善簡(jiǎn)潔的協(xié)程編程接口
– 類(lèi)pthread接口設(shè)計(jì),通過(guò)co_create、co_resume等簡(jiǎn)單清晰接口即可完成協(xié)程的創(chuàng)建與恢復(fù); – 類(lèi)__thread的協(xié)程私有變量、協(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)絡(luò)框架,基于時(shí)間輪盤(pán)實(shí)現(xiàn)的高性能定時(shí)器;
libco產(chǎn)生的背景
早期微信后臺(tái)因?yàn)闃I(yè)務(wù)需求復(fù)雜多變、產(chǎn)品要求快速迭代等需求,大部分模塊都采用了半同步半異步模型。接入層為異步模型,業(yè)務(wù)邏輯層則是同步的多進(jìn)程或多線程模型,業(yè)務(wù)邏輯的并發(fā)能力只有幾十到幾百。隨著微信業(yè)務(wù)的增長(zhǎng),系統(tǒng)規(guī)模變得越來(lái)越龐大,每個(gè)模塊很容易受到后端服務(wù)/網(wǎng)絡(luò)抖動(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);
如何控制協(xié)程調(diào)度;
如何處理同步風(fēng)格的API調(diào)用,如Socket、mysqlclient等;
如何處理已有全局變量、線程私有變量的使用;
最終我們通過(guò)libco解決了上述的所有問(wèn)題,實(shí)現(xiàn)了對(duì)業(yè)務(wù)邏輯非侵入的異步化改造。我們使用libco對(duì)微信后臺(tái)上百個(gè)模塊進(jìn)行了協(xié)程異步化改造,改造過(guò)程中業(yè)務(wù)邏輯代碼基本無(wú)修改。至今,微信后臺(tái)絕大部分服務(wù)都已是多進(jìn)程或多線程協(xié)程模型,并發(fā)能力相比之前有了質(zhì)的提升,而libco也成為了微信后臺(tái)框架的基石。
libco框架
libco在框架分為三層,分別是接口層、系統(tǒng)函數(shù)Hook層以及事件驅(qū)動(dòng)層。
同步風(fēng)格API的處理
對(duì)于同步風(fēng)格的API,主要是同步的網(wǎng)絡(luò)調(diào)用,libco的首要任務(wù)是消除這些等待對(duì)資源的占用,提高系統(tǒng)的并發(fā)性能。一個(gè)常規(guī)的網(wǎng)絡(luò)后臺(tái)服務(wù),我們可能會(huì)經(jīng)歷connect、write、read等步驟,完成一次完整的網(wǎng)絡(luò)交互。當(dāng)同步的調(diào)用這些API的時(shí)候,整個(gè)線程會(huì)因?yàn)榈却W(wǎng)絡(luò)交互而掛起。
雖然同步編程風(fēng)格的并發(fā)性能并不好,但是它具有代碼邏輯清晰、易于編寫(xiě)的優(yōu)點(diǎn),并可支持業(yè)務(wù)快速迭代敏捷開(kāi)發(fā)。為了繼續(xù)保持同步編程的優(yōu)點(diǎn),并且不需修改線上已有的業(yè)務(wù)邏輯代碼,libco創(chuàng)新地接管了網(wǎng)絡(luò)調(diào)用接口(Hook),把協(xié)程的讓出與恢復(fù)作為異步網(wǎng)絡(luò)IO中的一次事件注冊(cè)與回調(diào)。當(dāng)業(yè)務(wù)處理遇到同步網(wǎng)絡(luò)請(qǐng)求的時(shí)候,libco層會(huì)把本次網(wǎng)絡(luò)請(qǐng)求注冊(cè)為異步事件,本協(xié)程讓出CPU占用,CPU交給其它協(xié)程執(zhí)行。libco會(huì)在網(wǎng)絡(luò)事件發(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默認(rèn)是每一個(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ā)上限就很容易受限于內(nèi)存。為此,libco也提供了stackless的協(xié)程共享?xiàng)DJ剑梢栽O(shè)置若干個(gè)協(xié)程共享同一個(gè)運(yùn)行棧。同一個(gè)共享?xiàng)O碌膮f(xié)程間切換的時(shí)候,需要把當(dāng)前的運(yùn)行棧內(nèi)容拷貝到協(xié)程的私有內(nèi)存中。為了減少這種內(nèi)存拷貝次數(shù),共享?xiàng)5膬?nèi)存拷貝只發(fā)生在不同協(xié)程間的切換。當(dāng)共享?xiàng)5恼加谜咭恢睕](méi)有改變的時(shí)候,則不需要拷貝運(yùn)行棧。
libco協(xié)程的共享協(xié)程棧模式使得單機(jī)很容易接入千萬(wàn)連接,只需創(chuàng)建足夠多的協(xié)程即可。我們通過(guò)libco共享?xiàng)DJ絼?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é)程私有變量
多進(jìn)程程序改造為多線程程序時(shí)候,我們可以用__thread來(lái)對(duì)全局變量進(jìn)行快速修改,而在協(xié)程環(huán)境下,我們創(chuàng)造了協(xié)程變量ROUTINE_VAR,極大簡(jiǎn)化了協(xié)程的改造工作量。
因?yàn)閰f(xié)程實(shí)質(zhì)上是線程內(nèi)串行執(zhí)行的,所以當(dāng)我們定義了一個(gè)線程私有變量的時(shí)候,可能會(huì)有重入的問(wèn)題。比如我們定義了一個(gè)__thread的線程私有變量,原本是希望每一個(gè)執(zhí)行邏輯獨(dú)享這個(gè)變量的。但當(dāng)我們的執(zhí)行環(huán)境遷移到協(xié)程了之后,同一個(gè)線程私有變量,可能會(huì)有多個(gè)協(xié)程會(huì)操作它,這就導(dǎo)致了變量沖入的問(wèn)題。為此,我們?cè)谧鰈ibco異步化改造的時(shí)候,把大部分的線程私有變量改成了協(xié)程級(jí)私有變量。協(xié)程私有變量具有這樣的特性:當(dāng)代碼運(yùn)行在多線程非協(xié)程環(huán)境下時(shí),該變量是線程私有的;當(dāng)代碼運(yùn)行在協(xié)程環(huán)境的時(shí)候,此變量是協(xié)程私有的。底層的協(xié)程私有變量會(huì)自動(dòng)完成運(yùn)行環(huán)境的判斷并正確返回所需的值。
協(xié)程私有變量對(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接口去查詢(xún)DNS獲取真實(shí)地址。我們?cè)趨f(xié)程化改造的時(shí)候,發(fā)現(xiàn)我們hook的socket族函數(shù)對(duì)gethostbyname不適用,當(dāng)一個(gè)協(xié)程調(diào)用了gethostbyname時(shí)會(huì)同步等待結(jié)果,這就導(dǎo)致了同線程內(nèi)的其它協(xié)程被延時(shí)執(zhí)行。我們對(duì)glibc的gethostbyname源碼進(jìn)行了研究,發(fā)現(xiàn)hook不生效主要是由于glibc內(nèi)部是定義了__poll方法來(lái)等待事件,而不是通用的poll方法;同時(shí)glibc還定義了一個(gè)線程私有變量,不同協(xié)程的切換可能會(huì)重入導(dǎo)致數(shù)據(jù)不準(zhǔn)確。最終gethostbyname協(xié)程異步化是通過(guò)Hook __poll方法以及定義協(xié)程私有變量解決的。
gethostbyname是glibc提供的同步查詢(xún)dns接口,業(yè)界還有很多優(yōu)秀的gethostbyname的異步化解決方案,但是這些實(shí)現(xiàn)都需要引入一個(gè)第三方庫(kù)并且要求底層提供異步回調(diào)通知機(jī)制。libco通過(guò)hook方法,在不修改glibc源碼的前提下實(shí)現(xiàn)了的gethostbyname的異步化。
協(xié)程信號(hào)量
在多線程環(huán)境下,我們會(huì)有線程間同步的需求,比如一個(gè)線程的執(zhí)行需要等待另一個(gè)線程的信號(hào),對(duì)于這種需求,我們通常是使用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脫衣機(jī)

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)
