介紹
異步模型和協(xié)程模型都是原fastcgi的優(yōu)化模型吼旧。
-
cocgi
是使用騰訊開源庫libco實(shí)現(xiàn)的協(xié)程模式的fastcgi. -
mucgi
是使用muduo開源庫實(shí)現(xiàn)的異步模式的fastcgi.
兩者針對的場景略有不同虫腋」兔恚可以根據(jù)業(yè)務(wù)情況選擇使用:
- 對于網(wǎng)絡(luò)抖動的應(yīng)付能力吩谦,
mucgi
優(yōu)于cocgi
優(yōu)于libfcgi
。 - 對于后端業(yè)務(wù)復(fù)雜度的應(yīng)付能力,
cocgi
優(yōu)于mucgi
優(yōu)于libfcgi
帜篇。
在一個系統(tǒng)中兩者可以結(jié)合起來使用:
用mucgi
接入如秒殺活動徐绑,抽獎等請求數(shù)波動大且響應(yīng)速度快的后端。
用cocgi
接入存在復(fù)雜業(yè)務(wù)邏輯忌穿,請求響應(yīng)速度快慢不均的后端抒寂。
三種模式優(yōu)缺點(diǎn):
部署都是 nginx -> fastcgi -> 同步后端(測試用的是ice)
1. fastcgi(同步)
這個框架的fastcgi是用官網(wǎng) 的libfcgi庫編譯C++程序,
然后用cgi-fcgi或者spawn-fcgi指定ip端口調(diào)起這個程序處理fastcgi請求掠剑。
網(wǎng)上搜到的nginx + fastcgi +c教程基本都是這個模式屈芜。
缺點(diǎn):
監(jiān)聽模式是 listen -> fork 共享監(jiān)聽端口給所有的進(jìn)程。每次只有一個進(jìn)程可以接受新連接。這個模式的問題后面會討論到井佑。
同步進(jìn)程個數(shù)属铁,開多了浪費(fèi)資源,開少了躬翁,nginx會報一堆connect refuse.因?yàn)槿绻衒astcgi的進(jìn)程都在忙焦蘑。而且fastcgi的backlog(這個是存放三次握手成功但沒access鏈接)滿,那么nginx新建立的鏈接會直接返回失敗盒发。
這里要重點(diǎn)指出例嘱,在fastcgi同步模型中,nginx -> fastcgi必須使用短鏈接宁舰。不要在nginx里面配fastcgi_keep_conn on.原因是上面說的拼卵,nginx不是所有請求都用已經(jīng)存在的長鏈,他會自動去創(chuàng)建新鏈接蛮艰。這個時候一個進(jìn)程一個鏈接腋腮,已經(jīng)沒有辦法接受新的鏈接,所以會出現(xiàn)一堆請求失敗壤蚜。
不要去改fastcgi的listen backlog. 在backlog里面的已經(jīng)三次握手鏈接低葫,但沒有accept的鏈接。這些鏈接會等待仍律,不會立刻返回失敗嘿悬。模型里面設(shè)置是5,曾經(jīng)試過把他改成128,也就是說有128個請求等在那里水泉,導(dǎo)致超時的鏈接更多了善涨。超時比拒絕鏈接更傷性能。
總結(jié):
- libfcgi不能應(yīng)對后端存在大量同步模塊草则,且業(yè)務(wù)復(fù)雜的情況(業(yè)務(wù)處理速度快慢不均)钢拧。
- libfcgi不能應(yīng)對網(wǎng)絡(luò)抖動的情況(請求數(shù)突然暴漲)。
驚群問題
Linux內(nèi)核版本2.6.18以前炕横,listen -> fork 這種做法會有驚群效應(yīng)源内。在 2.6.18 以后,這個問題得到修復(fù)份殿,僅有一個進(jìn)程被喚醒并 accept 成功膜钓。
多IO+多路IO復(fù)用的模型也存在驚群在問題(如epoll)。nginx就花了大力氣處理這個問題卿嘲。nginx配置accept_mutex on的時候颂斜,用加鎖來保證每次只有一個進(jìn)程的listen_fd會進(jìn)入epoll。
但在極高的tps下拾枣,如用weighttp短鏈壓測nginx echo沃疮。每次都只有一個鏈接能夠被接收的模式盒让,這個接受新連接的速度將是個瓶頸,本機(jī)測試一秒最多2.2-2.5w次司蔬,cpu有一兩個核跑滿邑茄,其它大量idle.
nginx解決這個速度瓶頸問題提供了兩個配置項,一個是關(guān)掉accept_mutex 雖然會有驚群俊啼,但對比開著的性能要提高太多肺缕。至少是能跑滿cpu了。一個是打開multi_accept on,表示一次可以處理多個accept吨些,能很好的提高accept的速度搓谆,但會引起worker負(fù)載不均衡炒辉。
fastcgi要解決accept性能瓶頸目前沒有很好的方案豪墅。使用 SO_REUSEPORT(since Linux 3.9),可以稍微提升下性能黔寇。
改進(jìn)點(diǎn):
高內(nèi)核版本使用SO_REUSEPORT提升accept性能偶器。需要改libfcgi源碼.
os_unix.c
修改代碼如下:
int OS_CreateLocalIpcFd(const char *bindPath, int backlog)
{
...
328 if(listenSock >= 0) {
329 int flag = 1;
330 if(setsockopt(listenSock, SOL_SOCKET, SO_REUSEADDR,
331 (char *) &flag, sizeof(flag)) < 0) {
332 fprintf(stderr, "Can't set SO_REUSEADDR.\n");
333 exit(1001);
334 }
// 增加下面幾行
335 {
336 int flag = 1;
337 setsockopt(listenSock, SOL_SOCKET, SO_REUSEPORT, (char *) &flag, sizeof(flag));
338 }
339 }
...
}
測試性能大概提升10%-15% 不算很高。不過方便添加fastcgi進(jìn)程缝裤。
2. mucgi(異步)
異步fastcgi(mucgi)使用了muduo網(wǎng)絡(luò)庫作為通訊框架屏轰。
引入Cgicc庫多個文件用于解析http請求。
僅需要修改backend.cpp和backend.h就可以把請求傳到后端服務(wù)使用.
需要boost庫憋飞。 使用scons安裝霎苗,或者直接運(yùn)行make.sh。(make.sh是導(dǎo)出的scons的編譯日志,實(shí)在不想安裝scons,直接運(yùn)行make.sh也可以編譯程序)
優(yōu)點(diǎn):
- nginx可以配置異步長鏈接.提供了性能榛做。
- 能很好的應(yīng)對請求數(shù)瞬間爆炸的情況唁盏。比如10秒沒有請求,突然下一秒來了1萬個检眯。一般發(fā)生在公網(wǎng)網(wǎng)絡(luò)抖動的時候厘擂。如果是同步模型,這里就會出現(xiàn)幾千條connect refuse锰瘸。
muduo的日志用起來不是很順手刽严,業(yè)務(wù)環(huán)境我是替換成了log4cxx。為了不增加本模塊的復(fù)雜度避凝,就沒把那份代碼放上來舞萄。
缺點(diǎn):
- 由于fastcgi是把請求轉(zhuǎn)到后端,后端處理是用同步的管削,這個時候fastcgi進(jìn)程其實(shí)是等在這里了鹏氧。也就是說,一個CGI進(jìn)程或線程能處理的請求數(shù)是要看后端響應(yīng)速度的佩谣。所以當(dāng)后端出現(xiàn)延遲很高的調(diào)用時把还,會造成mucgi堵車。就像是多條道的高速公路,如果路上有的車開得很慢吊履,就容易造成堵車安皱。
改進(jìn):
muduo網(wǎng)絡(luò)庫是支持 在構(gòu)造函數(shù)傳入muduo::net::TcpServer::kReusePort即可
如下:TcpServer server(&loop, addr, "FastCGI", muduo::net::TcpServer::kReusePort)
可以在進(jìn)程很忙的時候,不需重啟就能多加幾個fastcgi進(jìn)程進(jìn)來處理請求艇炎。
doc文檔有同步和異步cgi的性能測試對比酌伊。
3. cocgi(協(xié)程)
協(xié)程fastcgi(cocgi)使用了騰訊開源框架libco。
使用muduo的Buffer類作為tcp的receive buffer缀踪。
加入Cgicc庫多個文件用于解析http請求居砖。
僅需要修改backend.cpp和backend.h就可以把請求傳到后端服務(wù)使用.
使用scons安裝,或者直接運(yùn)行make.sh驴娃。(make.sh是導(dǎo)出的scons的編譯日志,實(shí)在不想安裝scons,直接運(yùn)行make.sh也可以編譯程序)
優(yōu)點(diǎn):
- 這個可以算是第一個(同步)模型的改進(jìn)版本奏候。每個進(jìn)程可以創(chuàng)建多個協(xié)程,也就是說每個進(jìn)程通過協(xié)程能模擬出多個進(jìn)程的效果唇敞。具體可以看: https://github.com/Tencent/libco
- 能很好的應(yīng)對后端請求耗時不均衡的情況蔗草。特別是有的請求耗時較長的時候,cocgi表現(xiàn)要比mucgi好疆柔。因?yàn)閰f(xié)程的數(shù)量多咒精,所以阻塞幾個對整個模型的影響幾乎沒有。不像原生的fastcgi和mucgi的模型旷档,開10個進(jìn)程有幾個慢請求就要影響整個系統(tǒng)模叙。
缺點(diǎn):
- 協(xié)程模型也是同步模型,所以nginx->cocgi不能配置長鏈接鞋屈,測試時候發(fā)現(xiàn)范咨,就算有2000協(xié)程,如果前端請求很多谐区,nginx發(fā)起的連接會等待回收湖蜕。然而如果長鏈接沒釋放,新連接有兩種選擇宋列,一種是等待昭抒,可能引發(fā)超時(代碼里面是這種)。一種是直接close炼杖,可以返回請求失敗灭返。兩種都會影響用戶體驗(yàn)。
- 每個進(jìn)程的協(xié)程數(shù)不宜開太高坤邪。開多也會浪費(fèi)資源熙含。測試時候,每個進(jìn)程一般開30-50個協(xié)程艇纺。
改進(jìn)點(diǎn):
nginx->cocgi可以使用長鏈接怎静,如果要配好超時和自動回收的機(jī)制邮弹。還要和請求量和協(xié)程做個均衡。
異步實(shí)現(xiàn)源碼和協(xié)程實(shí)現(xiàn)源碼:
https://github.com/toniz/fastcgi-async-or-coroutine