運維老司機帶你出坑:複雜故障的排查及解決(案例實錄)

請及時關注“高效運維(微信ID:greatops)”公衆號,並置頂公衆號,以免錯過各種乾貨滿滿的原創文章。

作者介紹

640?wx_fmt=png&wxfrom=5&wx_lazy=1

fast於江

2004年北漂,主要在263從事個人和企業郵件的運維工作,2011年加入騰訊網繼續從事web運維工作,2013年加入騰訊雲負責售後技術支持團隊工作,2015年加入泰康人壽主要負責運維規範流程的制定和推動工作。

系統出問題,通常我們怎麼辦?

做運維工作的同學,在日常的工作中總免不了跟各種各樣的問題打交道,於是在身經百戰之後,總結出了一套在日常工作中解決問題的流程:

  1. 發現問題,通過開發、編輯、客戶、老闆、監控系統等發現系統有問題了;

  2. 接下來就想要看問題是否可以重現,這點非常重要,因爲有些問題很難重現,例如某些空指針異常或者內存泄露等問題,是需要累計運行到一定階段之後才能發現;

  3. 接着不管是否重現與否,都要儘快查看是否有錯誤日誌,通常在日誌中會包含非常關鍵的信息提示;

  4. 接下來會進行判斷,如果是運維能夠自行解決的話,例如由於運行環境的版本有bug,或者某個軟件的依賴庫版本不對,或者某個配置不對,這種問題運維通常通過升級或者更新庫,修改配置就可以解決;

  5. 如果發現是系統本身有Bug,則就需要開發介入進行解決了。

一般過程如下所示:

問題排查各階段,看起來就是一個圈

在發現系統有異常現象的時候,我只簡單的進行處理,不去解決。

我會先對問題進行簡要的分析,儘量保留現場,以便分清楚哪些是干擾因素,哪些纔是導致問題真正的原因,然後根據分析結果判斷要採取什麼行動,不過有時候老司機也有掉坑裏的時候,這時候只能想辦法重新回到正途中。

這時候往往現象比較棘手,分析就會陷入僵局,只好求助網上大神,然後各種分析工具齊上陣,貌似進展神速,但其實往往最終的效果是一般的,還是沒有辦法定位到根本原因。

這時候就需要重新梳理思路,去想辦法定位到問題的關鍵,想想看有沒有辦法能讓問題重現,經過一番嘗試發現居然重現了問題,並且現象一致,那真是萬幸啊,然後經過各種分析源碼+一點點運氣,心中就有底了,然後給代碼打補丁,通過驗證,終於一顆心可以落地了。

這個圈如下所示:

1、如何發現問題根因?

我將整個問題排查到解決的過程分成了4個階段,將前4個步驟,定義爲第1 階段,下面我從兩個案例,來講講在這個階段做的一些事情,先講講案例:

案例1:汽車論壇發現系統異常現象

  1. 汽車論壇PHP升級+LVS改造後,通過auto.qq.com訪問論壇頁面出現“服務器暫時無法響應,請稍後再試”

  2. 汽車開發收到論壇的URL掃描報警監控。

查詢httpd的error.log日誌,有如下的錯誤記錄:

案例2: 房產後臺發現系統異常現象

  1. 房產後臺2臺機器升級完PHP5.3.10後,測試均正常,上線運行一段時間編輯反應無法登入系統,重啓PHP-FPM後,恢復正常。

  2. 經過1日又出現類似現象,查詢nginx的error.log有明顯報錯。

640?wx_fmt=png&wxfrom=5&wx_lazy=1

這些情況怎麼破?且聽我徐徐道來。

1.1 問題排查第1階段

640?wx_fmt=jpeg&wxfrom=5&wx_lazy=1

1.1.1、簡單處理不解決

針對汽車論壇,房產後臺進行重啓後服務均恢復正常,但運行一段時間後又出現類似的問題。

再次重啓的時候,查看了ulimit –a選項,發現默認情況只有1024打開文件數,調整到ulimit –SHn 65535之後再次重啓相關服務。

運行一段時間後又出現類似無法打開頁面的問題。通過下面命令查詢到當前系統已經打開的文件句柄數,可用的句柄數,最大句柄數。

cat  /proc/sys/fs/file-nr

系統當前狀態打開文件也達到10多萬了。雖然沒有這到最大的可用數了,但有可能是會出現無法打開頁面的問題。這種問題的重現概率非常高。

640?wx_fmt=png&wxfrom=5&wx_lazy=1

640?wx_fmt=png&wxfrom=5&wx_lazy=1

sockets: used:已使用的所有協議套接字總量

TCP:inuse:正在使用(正在偵聽)的TCP套接字數量。其值≤ netstat –lnt | grep ^tcp | wc –l

TCP:orphan:無主(不屬於任何進程)的TCP連接數(無用、待銷燬的TCP socket數)

TCP:tw:等待關閉的TCP連接數。其值等於netstat –ant | grep TIME_WAIT | wc –l

TCP:alloc(allocated):已分配(已建立、已申請到sk_buff)的TCP套接字數量。其值等於netstat –ant | grep ^tcp | wc –l

TCP:mem:套接字緩衝區使用量(單位不詳)

1.1.2、干擾因素問題簡要分析

根據以前工作經驗判斷,打開文件過多問題,一般是打開文件沒有close靠成的。代碼問題可能是居多,但這些都只是猜測,還沒有拿得出手的任何證據。近期同時操作了PHP升級和LVS改造,所以這3方面入手進行思考:

  1. 代碼沒有更新的背景上,還是懷疑PHP升級造成的可能性要大一些,但還覺是不是特別認可這個懷疑,但無法從PHP.net獲得更多信息。

  2. 另一方面,LVS是非常成熟的技術,只涉及數據包的轉發,只是爲了驗證RS是否存活,會週期性探測80端口是否有響應,會增加一定的訪問量,但也只是一次簡單GET訪問,不會造成WEB無響應的問題。

    同時監控LVS並未有異常的連接數的增加。

  3. 還有就是php加載了過多的公司自已獨有的so文件,使整個事件事情的關鍵點過多,而且需要跨部門協調開發人員,增加了問題分析的複雜度。

1.1.3、解決問題方向判斷錯誤

由於干擾因素過多,並且接手業務時間不長,所以增加了方向判斷失誤可能性。主要因素爲出問題前做過PHP升級和LVS改造,還是在一定程序上增加了迷惑性。

一度只是簡單通過strace分析,並沒有認真的研究strace的具體調用細節。判斷是連接數據庫超時等原因造成的socket釋放異常。

並且想通過“時間+used socket+參數優化”三個方面綜合進行逆向查詢入手查找。但由於涉及到系統各方面操作因素太多,而且對系統理解有限,也導致處理前期有了判斷問題的方向性錯誤。

1.2 問題排查第2階段

經過前面的判斷,發現第一階段的方向錯誤了,於是進入第二個階段:

1.2.1、重回正途利用現有工具

短時間從PHP升級和LVS改造上面無法尋找到突破口。

決定利用LINUX現在提供的工具,如strace,gdb,netstat,lsof,/proc提供的各種系統分析工具進行排查問題。

計劃準備使用的工具:

  • strace - trace system calls and signals

  • lsof - list open files

  • gdb - The GNU Debugger

  • netstat  - Print network connections, routing tables, interface statistics, masquerade connections, and multicast member-ships

  • Proc - 文件系統是一個僞文件系統,它只存在內存當中,文件可以用於訪問有關內核的狀態、計算機的屬性、正在運行的進程的狀態等信息

1.2.2、現象棘手分析迷茫

640?wx_fmt=png&wxfrom=5&wx_lazy=1

  • 從架構入手:將RS服務器從LVS掉,恢復正常DNS指向, 問題依舊。

  • 從PHP入手:將PHP恢復到原有版本,問題依舊,只是socketd速度增長沒有新版本快(這點令我很奇怪)。

  • 從系統入手:系統負載不高,連接數正常,IO壓力正常,dmesg無報錯。

  • 從web應用入手:httpd日誌正常,發現httpd進程打開大量的socket。

通過lsof命令將httpd進程的打開文件都列出來:

640?wx_fmt=png&wxfrom=5&wx_lazy=1
640?wx_fmt=png&wxfrom=5&wx_lazy=1

1.2.3、系統狀態亂查一氣

640?wx_fmt=png&wxfrom=5&wx_lazy=1
640?wx_fmt=png&wxfrom=5&wx_lazy=1

一度查到這條strace記錄 時候,都開始懷疑數據庫連接上面的問題。

剛開始對strace的輸出內容一時也沒有理清頭緒,但隨着查詢文檔的增多,也隨步加深了對strace輸出信息的理解。

1.2.4、求助網上大神

所謂大神,即偉大的Google.com,使用各種關鍵字進行搜索相關文章。

相對靠譜的文章,關於can’t identify protocol問題定位問題定位步驟:

  1. 用root帳戶 遍歷 /proc/進程ID/fd目錄,如果該目錄下文件數比較大(如果大於10,一般就屬於socket泄漏),根據該進程ID,可以確認該進程ID所對應的名稱。

  2. 重啓程序恢復服務,以便後續查找問題。

  3. strace 該程序並記錄strace信息。strace –p 進程ID >>/tmp/stracelog.log 2>&1

  4. 查看 /proc/程ID/fd 下的文件數目是否有增加,如果發現有增加,記錄上一個socket編號,停止strace

  5. 確認問題代碼的位置。

    打開/tmp/stracelog.log,從尾部向上查找close(socket編號)所在行,可以確認在該次close後再次創建的socket沒有關閉,根據socket連接的server ip可以確認問題代碼的位置。

Lsof  FAQ

640?wx_fmt=png&wxfrom=5&wx_lazy=1

1.3 問題排查第3階段

接下來進入非常關鍵第3 階段
640?wx_fmt=jpeg&wxfrom=5&wx_lazy=1

1.3.1、分析工具齊上場

以nginx+php-cgi場景開始重複測試工具,抓取有用信息:

640?wx_fmt=png&wxfrom=5&wx_lazy=1

上面抓取的信息,在一定程度上影響了我的判斷,因爲19u,20u是因爲連接完數據庫後,狀態才生成的can’t identify protocol,所以我的關注點轉向了數據庫連接上面。

再另一個終端上面查strace ,根據FD進行從下往上查詢,查詢FD19,20連接完數據庫後,已經進行 正常close()操作了。

但查詢到下面4個系統調用是最後使用19,20句柄,就沒有下文了。而且沒有正常close();

下面沒有19和20的任何輸出了,當然也沒有包括close()的操作。

下面是一個標準的open,close操作記錄,便於對比參考。

http://kasicass.blog.163.com/blog/static/3956192010101994124701/

根據這篇文章的介紹,can‘t identify protocol是lsof的源碼,我也在dsock.c查到這個定義。

在 openbsd 下:

在 debian 下:

很奇怪哦,正確創建的 socket fd 居然顯示 “can‘t identify protocol”。

PS:

根據TCP的狀態遷移圖。應用程序主動打開後,沒有進行任何SYN及後續的ESTABLISED,CLOSE_WAIT。直接被應用程序關閉或超時,纔會狀態直接變成生CLOSED

640?wx_fmt=png&wxfrom=5&wx_lazy=1

1.3.2、進展神速,效果一般

通過多次測試,監控系統調用,已經可以定位FD問題是由於socket和ioctl兩個系統調產生的,並且處理正確處理造成socket沒有釋放。

640?wx_fmt=png&wxfrom=5&wx_lazy=1

一直在處糾結了很長時間,很長時間… … …

1.3.3、重理思路定位關鍵

排查的重點工作轉向這2條系統調和是如何產生的,由於這個系統調和是比較獨立的,所以並不知道是哪個文件,以何種方式進行調用,排查陷入困境。

分析系統調用:

函數解釋:

socket() 爲通信創造一個端點並返回一個文件描述符。 socket() 由三個參數:

  • domain, 確定協議族。例如:

PF_INET 是IPv4 或者

PF_INET6 是 IPv6

PF_UNIX 是本地(用一個文件)

  • type, 是下面中的一個:

SOCK_STREAM (可靠的面向連接的服務或者 Stream Sockets)

SOCK_DGRAM (數據包服務或者 Datagram Sockets)

SOCK_SEQPACKET (可靠的有序的分組服務),或者

SOCK_RAW (網絡層的原始協議)。

  • protocol 確定實際使用的運輸層。最常見的是 IPPROTO_TCP, IPPROTO_SCTP, IPPROTO_UDP, IPPROTO_DCCP。這些協議是在

    中定義的。如果 domain 和 type已經確定,“0” 可以用來選擇一個默認的協議。

ioctl 主要參數SIOCGIFADDR 獲取接口地址。

1.3.4、問題重現現象一致

經過上面的函數分析,得知是獲取eth1的IP系統調用。

由於本人不是開發出身,所以了爲避免出錯,我需要通過另一種方法驗證我的分析:

640?wx_fmt=png&wxfrom=5&wx_lazy=1

2、如何正確解決問題

在定位到問題之後,剩下的其實相對來說就容易的多了

640?wx_fmt=jpeg&wxfrom=5&wx_lazy=1

2.1、分析源碼運氣稍好

  1. 先要查出PHP是如何調用,查詢eth1網關,調和這個IP做什麼。

    傳統方法grep –R eth1./*結果很給力,多個.php文件都有調一個geteth1_ip_str**函數(實屬運氣,如果函數沒寫eth1類似的名稱,還可能查不到哪. ^^)

  2. 通過php源代碼查到此函數,是公司t_common.so裏面定義實現。手頭正好有這個源碼,查到get_eth1_ip_str這個函數,很簡單就是返回一個eth1的IP地址。

640?wx_fmt=png&wxfrom=5&wx_lazy=1

問題總結的時候想到腳本,可以列出來所有加載擴展庫的支持函數列表:

640?wx_fmt=png&wxfrom=5&wx_lazy=1

輸出內容:

640?wx_fmt=png&wxfrom=5&wx_lazy=1

2.2、心中有底略顯激動

通過簡單分析,以及諮詢同事,覺是應該是申請了sock,沒有進行close造成的。但真的是這樣嗎?我們還要驗證一下。

640?wx_fmt=png&wxfrom=5&wx_lazy=1

sleep(10000); sleep函數是要進程進行阻塞(sleep可以實現一種比較特殊的阻塞,這點跟IO阻塞不太一致),這樣有時間可以提取這個進程運行狀態。

心中有底略顯激動

640?wx_fmt=png&wxfrom=5&wx_lazy=1

2.3、代碼補丁驗證通過

再次執行測試程序

/usr/local/php/bin/php test.php xxx.xxx.169.114

同時監控lsof 和/proc/pid/fd下面都沒有出現socket不釋放(can’t identify protocol)的問題。

640?wx_fmt=png&wxfrom=5&wx_lazy=1

更新擴展so後,線上測試均正常。沒有再出現因爲調用這個函數不釋放socket句柄的問題。

至此整個問題都就都解決,世界又恢復了平靜(大笑)

3、經驗總結

對於本次問題處理的經驗,歸納提煉成如下4句話:

  1. 收集信息,隨時記錄。

  2. 冷靜判斷,積極分析。

  3. 大膽假設,大膽嘗試。

  4. 積極總結,以備後用。

當然,總結我這幾年處理問題的思路及經驗,可以提煉成以下這三點:

  1. 要有明確的數據流和業務流的概念,例如:通常對於Web數據流處理起來較簡單,而對於Mail數據流則較複雜;

  2. 要能準確切入關鍵流節點,要敢於迅速的切入這些關鍵流,必要的時候進行快速模擬,以得到一手數據。

  3. 要掌握程序運行的狀態,可以從兩方面着手,第一是掌握輸出日誌內容;第二是進行strace跟蹤程序運行狀態等

END.

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章