流量低峰也烦人-lighttpd耗时长问题追查
結論
如果你用lighttpd1.5(以下lighttpd均指1.5)做靜態文件服務器,或者你雖然用lighttpd處理php請求,但是用到$PHYSICAL作為mod_proxy_core的條件, 且某個時候你的單機流量很低(幾個/s), 或許你也有類似的問題,但是影響程度或許不會引起你的注意!
1.Lighttpd的mod_proxy_core不建議用$PHYSICAL作為條件;
2.Lighttpd的stat cache機制沒有節省任何開銷;
3.Lighttpd子線程和主線程通過管道+epoll的通信機制,存在event丟失問題;
?
現象
用戶反饋凌晨的時候訪問百度某頁面,某些模塊的數據出不來;
其它依賴于我們的前端接口的產品線反饋訪問時間有時候超過1s;
我們自己的QA環境偶爾也會出現請求超過1s的問題;
因此我們打開lighttpd的日志的%D配置,打印ms級別的處理時間,發現晚上1點到凌晨8點有很多處理時間超過1s的請求,500ms以上的也有很多,并且流量越低, 比例越大;
1點-8點是流量低峰時期,流量越低,性能越差?
這個現象每到高峰時期就正常了,因為是流量低峰才會出現這樣的慢請求,占總比例非常之少,對整體的性能和穩定性影響極小,所以性能和穩定性監控報表中沒有發現這個問題。
追查過程
由于這個頁面對性能要求相對比較嚴格,雖然性能和穩定性衡量數據已經非常好, 但是這個問題一直是一個陰影,不解決終歸不爽,所以開始了下面的追查過程:
由于處理路徑是lighttpd->php-cgi->框架+邏輯, 首先的懷疑是框架+邏輯問題,但是通過查看php的處理時間,流量低峰高峰都非常正常,極少超過100ms,所以排除是框架+邏輯問題;那究竟是php-cgi的問題還是lighttpd本身的問題呢?為了排除php-cgi的問題,我們嘗試了從線下復現這個問題,看訪問靜態文件是否也有類似的問題。但是悲劇的是線下就是復現不了這個問題。那再對比和線上環境的不同,會不會是先要經過一段時間的大流量,然后再小流量才會出現這個問題呢?于是用ab 30qps壓了2個小時后停止,然后再手動訪問試了一下,果然如此!通過訪問靜態文件,發現靜態文件也是如此,處理時間超過1s, 因此基本排除php-cgi本身的原因,問題應該是出在lighttpd本身;
通過這個線下實驗,我們還發現了如下規律:
1.前期用ab壓的時間不定,有時候壓2個小時后然后低流量訪問還不會出現這個問題,有一定的隨機性;
2.手動低流量訪問的時候,并不是每次都慢,對同一個url, 緊接著的兩次訪問(訪問第一次后馬上訪問第二次),第一次會慢,但第二次會很快,然后再過個1-2s鐘再訪問第三次,又會很慢;
3.手動請求的時候,如果慢,總是慢1s, 但是線上有慢1s的,也有不是慢1s的,最多1s;
4.重啟lighttpd后,所有請求會恢復正常,需要重新壓;
于是產生兩個最大的疑問, lighttpd在公司使用這么廣,處理靜態資源和php請求的都有用到, 別的產品線為什么不報? 為什么是1s? 對于第一個疑問,覺得可能是因為這種情況影響的平均性能非常少,可能其他產品線不會這么敏感,或者是流量低峰的單機請求量也很高,沒有頻繁的觸發這個問題,這個時候還對比了其它產品線的lighttpd.conf, 這個時候是沒有發現有什么問題的。于是就從第二個問題開始著手追查:為什么是1s?
帶著問題,開始讀lighttpd的源代碼了。。。
該頁面lighttpd event-handler用的是linux-sysepoll;
首先發現lighttpd代碼中有各個地方和1s有關的代碼:
源文件server.c
第一個1000ms是lighttpd的epoll的超時時間,也就是如果沒有任何句柄有事件發生,epoll最多等待1000ms后即會返回,如果有事件發生,epoll會馬上返回有事件發生的所有句柄,然后lighttpd會處理joblist中已經準備好的connection,重新進入狀態機;第二個1s是lighttpd有個一個trigger機制,每隔1s會觸發一次SIGALRM, 然后lighttpd處理超時的請求,清理stat_cache等;因此,通過修改這兩個1s, 發現當改成fdevent_poll(srv->ev, 500);? 后,慢請求都變成500了, 所以慢請求是因為的那個connection已經放在joblist, 但是沒有成功觸發epoll返回, epoll只有等待超時后返回,該connection才會被處理,這也就解釋了為什么流量高峰的時候沒有這個問題,因為高峰的時候epoll返回得相當頻繁,也可以解釋為什么線上的慢請求慢100,200ms的都有,但是最多不超過1s了,線下手工訪問的時候總是慢1s, 這也是因為每秒的請求量的原因, 這其實也是類似epoll這種異步事件處理模型所帶來的通病,用延遲換吞吐量;
問題進一步,那為什么重啟lighttpd后,就算流量低也沒問題呢,所以進一步看代碼,通過把lighttpd所有debug日志打開,發現這個問題和lighttpd-的stat cache機制有關, 為了避免反復的調用stat來獲取文件信息,lighttpd用了一個全局的hash表保存了每個物理路徑的所對應文件的stat結果,這個機制和server. stat-cache-engine這個配置有關,我們用的默認配置“simple”, cache結果會緩存1s, 如果沒有命中或者失效,lighttpd會把這個stat任務放在一個隊列里面, 然后告訴狀態機HANDLER_WAIT_FOR_EVENT, 暫時退出狀態機,由另外幾個線程來異步處理這個stat任務,處理完這個任務后,會重新把這個任務關聯的connection加到joblist_queue中,然后通過管道通知主線程,讓epoll返回, 相關代碼如下:
源文件joblist.c
上面的代碼可以看出, lighttpd是通過判斷一個全局的變量srv->did_wakeup,如果是0,就把它改成1,然后往這個管道發生一個空格字符串,觸發主線程的epoll返回,如果這個變量不是0,就不會通知主進程。
下面的代碼是主線程epoll返回后,和這個管道句柄對于的處理函數,可以看出主線程又把srv->did_wakeup初始化成0了, 這樣下次還會wakeup主線程;
源文件server.c
這就引發一個思考,如果因為某種原因srv->did_wakeup被修改成1了,但是主線程由于某種原因沒有收到這個write事件,導致srv->did_wakeup沒有被改成0,那不是后面都不會通過管道通知主線程了,為了證明這個假設,我加了下trace代碼,發現確實是這樣的,設置srv->did_wakeup =1 做了2456次,但是設置srv->did_wakeup = 0只做了2455次,只差一次,并且后續都沒有做這個操作了,另外還發現子線程每次write管道都是成功的,但是最后一次主線程沒有收到這個事件,至于為什么沒有收到,就沒有繼續查了。
但是,還是有個疑問,我訪問的php請求,lighttpd應該把請求路徑發給php-fpm,自己應該不關心物理路徑的啊,就不用搞什么stat cache吧,這個時候想起了當時為了解決某擴展能夠正確獲取到PATH_INFO的問題,把mod_proxy_core的條件配置從$HTTP["url"] =~ “\.php$”改成了$PHYSICAL["existing-path"] =~ “\.php$”。馬上修改配置,再測試,問題果然沒有了, 通過查看lighttpd代碼,發現如果配置成$PHYSICAL這種形式,會導致lighttpd去stat這個物理文件,這個操作在mod_proxy_core之前執行,如果用$HTTP[“url”]就不會引發這個問題,到此,一切都清楚了,我看到的其它老的產品線都是配的$HTTP[“url”], 只有少數的幾個產品線不是用的$HTTP[“url”],也只是單機流量非常低的情況才會出現這個問題,很難會讓人覺察到!
另外,在追查問題的過程中還發現lighttpd stat_cache機制的兩個問題,第一個問題就是處理stat任務的子線程,在stat之后,并沒有更新這個stat cache的狀態為FINISHED, 下次來查的時候還是沒有命中cache, 等于是白干了。如下代碼所示:
源文件stat_cache.c
第二個問題是就是命中了stat cache, 其實還是需要調用stat判斷改cache有沒有過期, 所以覺得stat cache本身這個機制也是白搞了,比較沒有節省stat的開銷,還多搞了,如下代碼所示:
源文件stat_cache.c
遺留問題
子線程和主線程通過管道+epoll的機制來通信,為什么會有一定的概率失敗呢?write管道其實是成功的,由于精力有限,這個問題沒有繼續追查;
管道其實是成功的,由于精力有限,這個問題沒有繼續追查;
對lighttpd配置的建議
如果利用mod_proxy_core做php處理,還是用$HTTP[“url”]做條件吧,例如:
$HTTP["url"] =~ “\.php” {
proxy-core.balancer = “static”
proxy-core.protocol = “fastcgi”
proxy-core.allow-x-sendfile = “enable”
proxy-core.backends = ( “unix:/home/super/php/var/php-cgi.sock” )
proxy-core.rewrite-request = (
“_pathinfo” => ( “(/[^\?]*)/index\.php(/[^\?]*)” => “$2″ ),
“_scriptname” => ( “(.*\.php)” => “$1″ )
)
注意,為了讓php-cgi取到正確的PATH_IFNO, 請注意添加“_pathinfo” rewrite規則!
對lighttpd代碼優化的建議
1.在每隔1s的trigger操作中,新增一個操作:將srv->did_wakeup重置為0,防止這個變量變成1以后永遠便不會0的情況發生;
2.stat_cache_thread處理完stat_job之后,要更新源stat_cache_entry的狀態為FINISHED, 否則就白搞了;
3.命中stat cache后,不用再通過stat判斷該cache是不是最新的,因為最多緩存1s鐘;
?
?
【本文首發于:搜索研發部官方博客】http://stblog.baidu-tech.com/?p=1444 【關注百度技術沙龍】?
本文轉自百度技術51CTO博客,原文鏈接:http://blog.51cto.com/baidutech/779536,如需轉載請自行聯系原作者
總結
以上是生活随笔為你收集整理的流量低峰也烦人-lighttpd耗时长问题追查的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 生成器 迭代器学习
- 下一篇: OpenNebula 入门安装配置