1. 程式人生 > >深度解析Tengine的調試與資源監控方法論

深度解析Tengine的調試與資源監控方法論

分布式

摘要: Tengine是由淘寶網發起的Web服務器項目。它在Nginx的基礎上,針對大訪問量網站的需求,提供更強大的流量負載均衡能力、全站HTTPS服務、安全防×××、鏈路追蹤等眾多高級特性。團隊的核心成員來自於淘寶、搜狗等互聯網企業,從2011年12月開始,Tengine成為一個開源項目,團隊在積極地開發和維護著它,最終目標是打造一個高效、穩定、安全、易用的Web平臺。

阿裏雲CDN現在服務超過24萬家客戶,Tengine作為接入層提供高性能Web Server服務,是CDN系統最核心的組件之一。無論是來自阿裏集團內部還是外部客戶的流量服務,幾乎都是由Tengine承載。可以毫不誇張地說,Tengine的服務質量直接影響著國內外無數大中小型Web站點的服務質量和業務存活,所以,維護Tengine的穩定性一直是我們團隊的最高優先級目標之一。經過了多年淘寶、天貓等大型網站雙十一活動的洗禮,Tengine的性能和穩定性已經得到了很好的驗證。

有一句俗語:“上帝說要有光,於是便有了光。“阿裏雲高級開發工程師墨飏說,“Tengine在做工具化的時候,也基本沿襲了這樣的思路。在做開發之前,我們會系統性地思考:我們需要面對什麽樣的場景,會碰到什麽樣的問題,需要怎樣的調試技巧和工具,是否可以解決更多此類問題,於是,我們的工具便會在這樣的思路下逐漸成型和完善。同時,在服務客戶的過程中,我們也會遇到各種新場景新問題,為了定位和解決問題,我們也會針對性地提出解決方案,沈澱出更多調試技巧和工具。作為一線開發團隊,我們一路走來積累了非常多調試技巧、工具化的經驗。”

本文由阿裏雲CDN團隊的研發同學笑臣和墨飏帶來,從Tengine的內存調試、核心結構、upstream、coredump四個部分展開,為大家整理和分享一些實踐經驗。

內存調試——精準定位問題

Tengine作為C語言開發的應用,在內存的使用中會碰到一些問題,第一部分將重點介紹內存調試方面的相關內容。

從下圖可以清晰的看出,Tengine內存分布可以從三個維度來理解:底層實現、抽象層、應用層。

技術分享圖片

一、底層實現

Tengine底層實現依賴操作系統的內存分配機制,常見的內存分配器包括jemalloc(FreeBSD)、ptmalloc(glic)、tcmalloc(Google),luajit則使用內置的dlmalloc庫。Tengine在每個連接accept後會malloc一塊內存,作為整個連接生命周期內的內存池, 當HTTP請求到達的時候,又會malloc一塊當前請求階段的內存池, 因此對malloc的分配速度有一定的依賴關系。jemalloc的性能是ptmalloc的兩倍以上,我們在使用Tengine的時候默認采用jemalloc。jemalloc在追蹤實際內存分配時可以使用“malloc_stats_print”來查看內部細節,幫助定位內存泄露等問題。

技術分享圖片

二、nginx pool調試

在底層內存分配工具無法定位問題時,我們需要從抽象層分析出了什麽問題。
Tengine作為nginx 的fork,在使用nginx pool方面與官方nginx基本沒什麽區別,它的內存池管理機制在HTTP請求的任一階段都可能被調用來分配內存,我們可以從內存分配的真實函數調用來統計內存分配的占用量、歷史數量、當前數量、large alloc等。mod_debug_pool已經在Tengine社區開源,有興趣可以自行查閱文檔,它的原理是通過hook ngx_create_pool函數來記錄__func__/__LINE__,在需要排查問題時可以展示歷史數據,從抽象層定位內存泄露等問題。
技術分享圖片

三、lua內存統計

lua/luajit是另一個非常成熟的開源項目,在nginx生態系統中,lua-nginx-module允許lua/luajit以虛擬機形式內嵌到nginx提供強大的腳本能力,我們在阿裏雲CDN海量業務開發中大量使用到lua/luajit。在調試luajit內存占用時,可以通過collectgarbage來展示總內存占用量,通過掃描gc object,可以統計global_State中所有gc對象,OpenResty社區也提供了GDB工具來輸出gc對象的數量和內存占用。
技術分享圖片

四、共享內存調試

Tengine是多進程服務模型,其進程間的通信主要依賴操作系統共享內存機制,隨著業務的發展共享內存的使用頻率越來越高,如何去定位、調試共享內存,是一個比較嚴峻的問題,且業內缺少相關工具。我們在調試共享內存問題時,沈澱和開源了mod_slab_stat工具,在Tengine社區可以查閱詳細的文檔,該工具統計和展示了每個zone的內存分配細節,包括page、slab和alloc num等,突出業務通信內容塊分布情況等。該工具適用於ngx shm,基於ngx slab的stub stats模塊是個例外。
技術分享圖片

以上,從底層實現、抽象層、應用層三個維度,可以定位到絕大部分Tengine系統和業務的內存問題,包括一些內存的調試技巧。

核心結構——調試與解決故障

Tengine作為高性能服務器被廣泛應用,它的核心框架和核心數據結構決定了其承載服務的質量。針對所遇到的CDN用戶上報故障的工單,比如請求異常、訪問慢等,如何從Tengine核心結構去調試、定位和解決問題,是本部分要介紹的內容。

Tengine主要的核心結構包括連接、請求和計時器等。連接承載著請求,以連接池的形式提供前端、後端網絡服務,其異步事件驅動的特性是Tengine高性能網絡服務的基石,而其事件模型又和計時器緊緊關聯,這些核心結構組成了Tengine的核心框架。

要調試和定位Tengine網絡問題,需要從連接、請求、計時器等多角度思考,從讀寫請求狀態、解析請求階段、應答輸出階段、建連等多維度采集,從而得到更詳細的全局監控數據。

技術分享圖片

Tengine在nginx stub status工具基礎上,開發了tsar工具(已開源),來展示更細粒度的歷史資源監控,包括應用層QPS、HTTPS和網絡層的accept、讀寫等待等。基於reqstatus的域名級監控,可以針對任意粒度域名級來統計請求數、連接數和5xx、4xx等狀態碼數量,還包括連接的復用情況等。

技術分享圖片

有了tsar和reqstatus工具,我們可以從全局來分析Tengine系統的服務和性能狀態,但是,這夠了麽?CDN系統中,我們會遇到一些問題,比如:為啥tengine/nginx訪問慢?是因為客戶端慢、後端慢、還是nginx本身hang住?如何去衡量“慢“?我們需要對請求時間進一步細分。

Tengine新增了responsefirstbytetime、responsefirstbytetime、upstream_response_time、writewaittime、writewaittime、upstream_read_wait_time等變量來記錄從請求進入到響應結束整個請求流程中的包括不限於應答首字節、後端應答首字節、socket讀寫等待時間總和等關鍵指標。

技術分享圖片

Tengine在衡量和監控請求卡頓時,將events cycle內event平均處理時間和events cycle內timer平均處理時間綜合來定位單cycle平均耗時長的異常,常見的問題如同步IO(磁盤負載高時寫log卡頓)、CPU密集型執行流(lua實現的CPU密集型邏輯)。

有些時候,我們無法從全局資源監控角度定位問題,這時候可以從ngx_cycles->connections[]來查詢當前執行的請求或連接結構信息,通過gdb腳本來掃描worker內當前連接信息,查看和調試是否有請求堆積或連接泄露等問題。mod_debug_conn(待開源)工具是上述調試技巧的沈澱,它還可以幫助我們查看請求/連接的內存占用、分析連接和請求上的各類信息。

技術分享圖片

同時,Tengine的計時器(timer)在異步業務場景中有重要作用,通過掃描計時器紅黑樹,分析每個event timer,我們可以調試和定位異步操作中的問題。有時候我們在平滑升級tengine服務時,worker一直處於shutting down狀態無法退出,其實便是因為一直存在timer導致的。也可以通過hook ngx.timer.at函數來統計lua-nginx-module中的timer caller次數,解決timer超限的報錯等。

回源監控

CDN是內容分發網絡的簡稱,其分發的內容來自用戶源站,負責回源的upstream模塊是Tengine最重要組成部分之一,使Tengine跨越單機的限制,完成網絡數據的接收、處理和轉發。這部分主要介紹upsteam的一些調試技巧和回源資源監控的內容,以及相應的實例分享。

在上面的章節中,我們已經分析過如何依靠Tengine提供的關鍵指標來衡量和監控請求卡頓,那麽,我們同樣可以提出問題:如何去分析請求代理或回源失敗?是因為客戶端主動斷連、後端異常還是請求超時?如何去分析真實的原因,從而實時監控回源失敗,這也是經常困擾CDN用戶和開發人員的難題。

從Tengine的請求處理流程分析,客戶端在完成TCP三次握手後,比較常見的錯誤是:1、客戶端異常斷開連接;2、客戶端主動斷開連接;3、等待讀寫客戶端超時。在Tengine讀取請求內容後,解析客戶端請求失敗如請求行/請求頭等協議出錯,在upstream模塊回源時,也可能發生連接後端失敗、後端異常斷開連接、後端主動斷開連接、等待讀寫後端超時、解析後端應答頭失敗等錯誤,我們可以從Tengine的錯誤日誌查看對應的報錯信息,在此基礎上,我們從ngx_http_upstream_connect、ngx_http_upstream_send_request、ngx_http_upstream_process_header、ngx_http_upstream_process_body_in_memory等回源關鍵函數切入,提供error flag來監控真實的客戶端/回源失敗原因,將錯誤統計輸出至訪問日誌、reqstatus工具等,關聯和分析域名/請求級別的回源失敗問題,保障CDN服務的穩定性。

技術分享圖片

現在,我們有了回源的關鍵指標變量和error flag,可以依靠一些調試技巧來分享一個upstream問題實例。如圖所示,當客戶端請求通過Tengine upstream回源時,我們可能碰到這樣的問題:

技術分享圖片

  • 讀源站,讀滿buffer

  • 發送buffer size數據給客戶端,全發完了

  • 循環上述兩步驟…

  • 繼續讀源站,讀出若幹數據,且源站已讀完

  • 發送部分給客戶端,未發完(客戶端卡或者其他原因)

  • 再次讀upstream,讀出again(顯然,因為之前源站數據讀完了)

  • 繼續發送數據給客戶端,未發完(客戶端卡或者其他原因)

  • upstream回源先超時了,這個時候$error_flag表明等待讀源站超時,但是事實是這樣嗎?

我們提供了完整的復現方法,如圖所示:
技術分享圖片

站在上帝視角,很明顯可以看到其實是客戶端問題,但是回源采集的關鍵指標:errorflag、errorflag、upstream_read_wait_time、$write_wait_time,卻告訴我們是源站出錯導致的問題,數據不會欺騙我們,那麽問題到底在哪裏?

上述調試的一些技巧和回源監控,幫助我們理解upstream模塊的原理:
1、upstream有2個計時器,前端的和後端的;
2、Tengine/Nginx即使寫客戶端寫不進去,只要proxy buf沒滿也會嘗試讀後端,如果後端數據讀完了,會讀出EAGAIN;
3、後端計時器較短先超時了,關閉請求,此時往往認為後端出錯,真實情況是客戶端出錯;

技術分享圖片

這個特殊實例,在某些場景下,甚至在很多的生成環境中,都是比較常見但卻往往被忽視的問題,在幫助提升用戶體驗和服務質量的目標下,即使是nginx核心代碼不易發現的bug,在完善的調試工具和回源監控下,一樣無所遁形。有興趣的同學可以搜索nginx郵件列表來詳細了解問題背景,雖然nginx 官方因為一些原因沒有合入該patch,我們在Tengine中也做了單獨的優化策略。

Coredump

coredump是Tengine這類C開發的應用程序比較常見的問題,隨著業務的迅猛發展,coredump往往會變得越來越大,甚至越來越頻繁,我們從空間、數量、自動分析等角度層層遞進來優化Tengine的coredump。

“cat /proc/PID/coredump_filter”這行指令幫助我們了解Linux操作系統coredump機制所支持的內存形態,包括私有內存、共享內存等。我們在使用Tengine時可以去除共享內存,降低coredump文件大小。同時,也可以限制一定時間內crash寫coredump文件的次數,防止磁盤IO過高或磁盤空間被占滿,Tengine提供了“worker_core_limit”指令(待開源)來限制至分鐘/小時/天級別。

技術分享圖片

現在我們已經從空間和數量角度優化了coredump,方便了調試。那麽是否可以自動化完成分析,提升定位問題的速度?答案是肯定的。

我們通過gdb python腳本來自動分析coredump文件,從中提取得到觸發問題的請求的host、URL、headers等,有了這些信息,就可以不斷復現來快速調試和定位問題。但是有時候,coredump棧並不能告訴我們完整的現場,而在crash時,Tengine丟失了日誌中的請求信息,我們將這些信息記錄在環形內存中,通過coredump文件將環形緩沖數據輸出到文件,環環相扣,完整的現場,真相只有一個。
技術分享圖片
原理:http://nginx.org/en/docs/ngx_core_module.html#error_log

事後諸葛只能查漏補缺,我們需要提前發現問題,實時流量拷貝工具應運而生。http_copy是Tengine的擴展模塊,可以實時放大轉發本機的流量(HTTP請求),可以配置放大流量到指定地址和端口,也可設置放大的倍數和並發量等,更可以通過源站健康檢查來自動關停流量,避免過大流量對Tengine和源站的正常服務造成影響。技術分享圖片

在流量壓測/流量拷貝時,不需要真實返回響應的內容,減少帶寬消耗,drop_traffic工具添加body filter直接丟棄數據,或者截獲c->send_chain/c->send函數直接丟棄數據,在調試時發現問題而不產生問題。

以上就是阿裏雲CDN團隊對於Tengine的內存調試與資源監控方面的一些實踐經驗,希望對於正在使用開源Tengine的同學有一些幫助。

Tengine官網:http://tengine.taobao.org

Tengine Github:https://github.com/alibaba/tengine

阿裏雲CDN:https://www.aliyun.com/product/cdn

最後,阿裏雲CDN團隊正在招聘誌同道合的夥伴,歡迎加入~

技術分享圖片

原文鏈接


深度解析Tengine的調試與資源監控方法論