MySQL運維繫列 之 CPU瓶頸故障案例分析

本文轉載自:https://yq.aliyun.com/articles/618480

現在容我給大家分析一起新鮮出爐熱乎乎滾燙燙的故障案例

在分析案例前,據我們故障報告得到的數據分析顯示,最近2年出現的故障

  • 80%來自硬件,20%來自壓力瓶頸(因爲瓶頸分析和解除瓶頸做的及時)
  • 壓力瓶頸中:80%來自disk(IO壓力、磁盤空間)、20%來自CPU

今天主要聊聊cpu相關的問題

一、背景

  • 環境
1. 系統:Centos 6
2. 內核:2.6.32-504.23.4.el6.x86_64
3. MySQL: 5.7.22
  • 案發經過

凌晨做了一次數據庫升級,讀寫從old master 切到了 new master,一切順利

然後,到早高峯的時候,接到告警,緊張刺激的一天開始了

告警內容如下:

  1. slow query 上漲至 10000/s , 平時 5/s
  2. thread running 上漲至 169/s , 平時 10/s
  3. too many connection 頻繁
  4. cpu 使用率上漲至 99% , 平時 20-30%
  5. 服務器響應變慢

cpu_1

thread_run_2

sys_cpu_3

二、故障降級

根據以上報警信息,第一時間查看slow query

  1. 沒有發現特殊的慢查詢(掃描行數多、執行特別慢等),其餘基本都是在100ms ~ 500ms
  2. java每次讀寫的時候,有帶有大量的 select @@session.tx_read_only。
  3. 正常情況下,這樣的查詢是不會慢的,但是爲了先解決鏈接數多的問題,我們讓業務調整了鏈接參數:useLocalSessionState=true

可是劇情並沒有像我們想的那樣發展,雖然有所好轉(TMC從持續告警降低到間歇性告警),也就是問題並沒有徹底解決

接下來,我們能做的就是用DCT(數據庫配置管理系統)將master上的查詢分流一部分(60%)到slave上來

好消息是: Master 壓力降低,slow 降低,各種指標恢復正常
壞消息是: Slave 壓力瞬間就起來了,然後表現的症狀跟之前的Master一模一樣

再接下來,我們就繼續分流,再分60%的量到另外一臺slave
最終,Master和slave 系統各指標穩定了下來

好了,現在我們可以有時間來回顧和分析下剛剛發生的一連串事件

  • 案件名稱:cpu暴走事件

cpu的sys 使用率非常高

  1. 線索1: 數據庫升級
  2. 線索2: 高峯期流量大
  3. 線索3: 數據沒有預熱
  • 臨時控制:流量遷移
  • 後續方案:
  1. 繼續追蹤,找到根本原因

    1.1 保留現場,尋找蛛絲馬跡
    1.2 模擬案發,重現問題
    
  2. 回退版本

    數據庫降級方案:並不太好做,可能存在的問題更多,不到萬不得已,我們不願意這樣嘗試。
    

三、尋找真相

  • 當cpu飆高的時候,究竟發生了什麼

使用perf top 去看看哪些函數調用非常多,結果又遇到了另外一個坑,機器直接hang住,不久就掛了

直接上圖

4_perf

對呀,正所謂禍不單行就是這麼個道理哈

所以,接下來,我們的解題思路變成了這樣

  1. 解決perf 導致linux掛掉的情況
  2. 解決cpu高的瓶頸

    2.1 線下場景還原: 保留了案發當時的slow sql , 使用mysqlslap模擬併發sql,將壓力打在測試庫上
    2.2 線上場景還原: 使用tcpcopy,在線引流將真實壓力打在測試庫上
    

四、測試

4.1 測試用例一

先解決perf的問題,因爲有了它,纔可能知道坑在哪裏

  • 模擬問題,跑perf的時候讓linux掛掉的場景復現
1. 沒有mysql進程,perf沒問題
2. 有mysql進程,只有查詢,perf沒問題
3. 有mysql進程,模擬寫入,perf沒問題
4. 有mysql進程,複製同步,有查詢,但是master模擬單一sql寫入,perf沒問題
5. 有mysql進程,複製同步,有查詢,複製的sql來自生產環境,perf每次執行,必定crash


結論:perf + MySQL replicate  = crash  , 此場景每次必定復現
  • perf爲什麼會掛

google了一圈後,發現一丟丟可操作的點,大致的意思是說: perf在centos 2.6.32的內核中有bug

鏈接如下:

1. https://access.redhat.com/solutions/1310693

2. https://access.redhat.com/solutions/1992703

內核版本跟我們的版本一樣

於是,我們也沒想要去看是什麼因素導致perf在linux 2.6.32 中觸發了bug,我們所幸直接將系統升級到 centos 7來看看問題是否可以重現

再次經過上述的測試發現:perf + MySQL replicate 沒問題了,現在基本斷定是perf + kernel 2.6.32 遇到相關bug了
不過,既然centos 7上的perf可以用,那麼我們所幸在centos 7上覆現 cpu高的問題

4.2 線下場景還原: 保留了案發當時的slow sql , 使用mysqlslap模擬併發sql,將壓力打在測試庫上

  • 單一 slow sql 測試 + 多條 slow sql 混合測試

SQL語句:

1. SELECT loupan_page_use,last_update FROM mp_loupan_tags WHERE id=233598838
2. SELECT loupan_page_use,last_update FROM mp_loupan_tags WHERE loupan_page_use = '1' AND mp_type = '1' AND loupan_id in ('431819','417907','431337','413800','431650','215981','435463','429181','240518','411871') ORDER BY display_seq ASC,last_update DESC LIMIT 500 OFFSET 0
3. SELECT loupan_id,last_update_time FROM grade WHERE loupan_id = '257809' AND status = '2' ORDER BY last_update_time DESC
4. SELECT loupan_page_use,last_update FROM mp_loupan_tags WHERE loupan_page_use = '1' AND mp_type = '1' AND loupan_id in ('263126','426580','263275','263298','411670') ORDER BY display_seq ASC,last_update DESC LIMIT 500 OFFSET 0
5. SELECT loupan_page_use,last_update FROM mp_loupan_tags WHERE loupan_page_use = '1' AND mp_type = '1' AND loupan_id in ('215981','411875','418474','416128','298380','411083','418262','412631','237083','415427','411644','437897','415833','434643','400752','432261','416437','438013','436612','400947','431337','418277','413800','435463','250211','240518','417907','439605','435269','430827','430420','418785','417828','416877','416624','416376','416241') ORDER BY display_seq ASC,last_update DESC LIMIT 500 OFFSET 0
6. SELECT house_type_id,last_update FROM loupan_images WHERE ((loupan_id=261547) AND (house_type_id="161236") AND (category=10)) ORDER BY rank DESC,last_update DESC,category ASC,id DESC LIMIT 100 OFFSET 0
7. SELECT loupan_id,last_update FROM loupan_images WHERE category = '7' AND loupan_id = '260933' AND ((flag in (0,1) and 1)) ORDER BY rank DESC,last_update DESC,id DESC LIMIT 200 OFFSET 0
8. SELECT loupan_id,last_update FROM loupan_images WHERE ((loupan_id=425002) AND (category=8)) ORDER BY rank DESC,last_update DESC,category ASC,id DESC LIMIT 500 OFFSET 0
9. SELECT loupan_page_use,last_update FROM mp_loupan_tags WHERE loupan_page_use = '1' AND mp_type = '1' AND loupan_id in ('431994','251016','425992','383237','252069','435523','414367','438602','418001','411606','242004','435999','414054','437995','415811','414771','439970','251996','411782','416632','417611','247610','298398','251870','436767','417374','241933','241801','250763','432331') ORDER BY display_seq ASC,last_update DESC LIMIT 500 OFFSET 0
10. SELECT loupan_page_use,last_update FROM mp_loupan_tags WHERE loupan_page_use = '1' AND mp_type = '1' AND loupan_id in ('414321','401273','414409','256987','239486','399951','411857','431038','418800','409145','419135','418756','438468') ORDER BY display_seq ASC,last_update DESC LIMIT 500 OFFSET 0
11. SELECT loupan_page_use,last_update FROM mp_loupan_tags WHERE loupan_page_use = '1' AND mp_type = '1' AND loupan_id in ('413112','413151','410298','410283','439916','245708','241716','241812','242072','241877','241890','241797','245710','242082','418996','413118','257403','413216','401112','412289') ORDER BY display_seq ASC,last_update DESC LIMIT 500 OFFSET 0
12. SELECT loupan_page_use,last_update FROM mp_loupan_tags WHERE loupan_page_use = '1' AND mp_type = '1' AND loupan_id in ('439605','429377','252408','400752','248666','401202','401143','249507','250236','415528','247559','244521','394528','435679','264022','383196','246045','241059','414997','250767','251984','411968','247811','414493','409271','240435','401207','417573','411913','400886','251003','240528','249072','253033','249420','240837','249433','428320','389816','416080','249171','414844','410203','240028','416743','416048','417677','416666','249162','264101') ORDER BY display_seq ASC,last_update DESC LIMIT 500 OFFSET 0
13. SELECT loupan_page_use,last_update FROM mp_loupan_tags WHERE loupan_page_use = '1' AND mp_type = '1' AND loupan_id in ('415666','416121','417661','431364','419055','257898','417384','424463','417831','429187','416745','417962','257940','258046','414746','416471','435536','431355','257873','414545','417836','257845','438835','417947','437315','433201','432220','432046','432027','427731','423631','423298','420072') ORDER BY display_seq ASC,last_update DESC LIMIT 500 OFFSET 0
14. SELECT loupan_page_use,last_update FROM mp_loupan_tags WHERE loupan_page_use = '1' AND mp_type = '1' AND loupan_id in ('437148','258865','417140','416114','432010','258825','258829','426077','258805','258813','405587','405958','258774','426808') ORDER BY display_seq ASC,last_update DESC LIMIT 500 OFFSET 0
15. SELECT loupan_page_use,last_update FROM mp_loupan_tags WHERE loupan_page_use = '1' AND mp_type = '1' AND loupan_id in ('438956','436725','409405','428214','438718','439063','439199','439619','432314','415807','437725','418984','439932','432333','439773','429497','439609','415674','428729','434976','237609','438566','416101','426292','426251','427909','432703','435501','418548','438644') ORDER BY display_seq ASC,last_update DESC LIMIT 500 OFFSET 0
16. SELECT loupan_page_use,last_update FROM mp_loupan_tags WHERE loupan_page_use = '1' AND mp_type = '1' AND loupan_id in ('416298','252892','253165','436887','428371','251816','229854','432686','418173','411578','416559','253142','298034','240938','240864','409266','431641','426850','432043','435222','428298','229366','430458','237272','298020','410783','431129','249778','240946','251629') ORDER BY display_seq ASC,last_update DESC LIMIT 500 OFFSET 0
17. SELECT loupan_page_use,last_update FROM mp_loupan_tags WHERE loupan_page_use = '1' AND mp_type = '1' AND loupan_id in ('237961','246760','245148','400531','411079','426430','252039','238153','239570','237975','414382','415405','430961','430002','250845','416659','237984','438604','416113','437075','418631','431147','245171','416380','246418','411336','400697','418718','239952','238007','238017','417838','238031','252181','250326','240911','414506','246970','248340','241266','241018','251248','241011','238054','240459','251286','239843','251255','438525','237986') ORDER BY display_seq ASC,last_update DESC LIMIT 500 OFFSET 0
18. SELECT loupan_id,last_update FROM loupan_images WHERE ((loupan_id=414637) AND (house_type_id="238264") AND (category=3)) ORDER BY rank DESC,last_update DESC,category ASC,id DESC LIMIT 100 OFFSET 0
19. SELECT loupan_id,last_update FROM loupan_images WHERE ((loupan_id=417843) AND (category=4)) ORDER BY rank DESC,last_update DESC,category ASC,id DESC LIMIT 500 OFFSET 0
20. SELECT loupan_id,last_update FROM mp_loupan_tags WHERE loupan_page_use = '1' AND mp_type = '1' AND loupan_id in ('417138','416488','417434','417118','414536','431886','410367','399936','414636','435391') ORDER BY display_seq ASC,last_update DESC LIMIT 500 OFFSET 0
21. SELECT category,last_update FROM loupan_images WHERE ((loupan_id="402011") AND (category=3)) ORDER BY rank DESC,last_update DESC,category ASC,id DESC LIMIT 5 OFFSET 0

結果cpu壓力果然上漲了,且sys cpu非常高,跟我們的故障場景類似

  1. |usr:18 sys:3 idle:78
  2. |usr:8 sys:87 idle:5
  3. |usr:8 sys:86 idle:6
  4. | ... ...
  5. |usr:7 sys:88 idle:5

然後用perf top -g 追蹤統計下發生了啥

5_system_perf

看到的確是內核態的函數比較多啊,但是這些是幹啥的,並不知道哇,只知道自旋鎖是瓶頸哇

如果這個沒法統計,那我只能用pstack去碰碰運氣,追蹤下當時壓力大的時候的點發生了什麼?


      1 ib_wqueue_timedwait(ib_wqueue_t*,
      1 lock_wait_timeout_thread
      1 timer_notify_thread_func
      6 os_event::timed_wait(timespec
      6 os_event::wait_time_low(unsigned
      6 pthread_cond_timedwait@@GLIBC_2.3.2
     55 Field_timestampf::get_date_internal(st_mysql_time*)
     55 Time_zone_system::gmt_sec_to_TIME(st_mysql_time*,

似乎發現了什麼,沒錯,就是時區轉換函數,從google得之,在有timestamp的場景下,如果MySQL的time_zone=system,會調用linux的時區函數,從而內核態的cpu使用率高

timestamp的場景包括很多,什麼情況下會導致sys cpu飆高呢,我們不妨測試一把:

得到的最後結論是:

  • sys cpu高,只跟結果集包含timestamp有關係,跟查詢條件是否有timestamp無關
  • 使用time_zone='+8:00' 來替換 time_zone='system' ,可以大大降低sys cpu的使用率

最後,我們再針對一開始的21種語句使用 time_zone='+8:00' 來模擬一遍

  1. |usr:18 sys:3 idle:78|usr:19 sys:3 idle:77
  2. |usr:8 sys:87 idle:5|usr:92 sys:1 idle:6
  3. |usr:8 sys:86 idle:6|usr:93 sys:2 idle:4
  4. | ... ...
  5. |usr:7 sys:88 idle:5|usr:90 sys:2 idle:7

很明顯,問題得到解決

4.3 線下場景還原:使用TCPCOPY的方式在線引入真實的流量測試

  • 簡單介紹下tcpcopy

6_tcpcopy_arch

7_tcpcopy

在搭建和測試tcpcopy的時候也遇到一些坑,其中在路由上卡殼了很久,數據包一直被reset, 最終排查下來 1. 由於騰訊機房的交換機上設置了arp自動應答 2. test serve和assistant server必須在同一網段

反正過程蠻曲折的,這不是我們今天的重點,先忽略。

詳細的tcpcopy文章,請參考同事整理的 : tcpcopy介紹

  • 繼續問題追蹤

好了,根據4.1的測試,我們認爲問題是時區轉換導致的,但是爲了嚴謹,我們在對生產環境的真實引流做一次測試,如果引流的時候出問題,然後設置時區可以解決問題,才能正在確定問題的所在

好消息是: 當qps達到早高峯的量時,cpu sys壓力高的場景復現了
壞消息是: 設置了time_zone='+8:00' 之後,並沒有得到好轉,還是自旋鎖的問題

pstack的跟蹤並沒有Time_zone_system::gmt_sec_to_TIME(st_mysql_time* , 所以跟時區轉換沒有多大關係

     33 alloc_root
     38 dispatch_command
     38 mysql_parse
     50 my_raw_malloc
     55 _L_lock_12685
     56 malloc
     66 fil_aio_wait
     66 __io_getevents_0_4
     66 io_handler_thread
     66 LinuxAIOHandler::collect
     66 LinuxAIOHandler::poll
     66 os_aio_linux_handler
     71 __lll_lock_wait_private
     80 acl_authenticate
     80 check_connection
     80 do_auth_once
     80 login_connection
     80 native_password_authenticate
     80 server_mpvio_read_packet
    128 <Per_thread_connection_handler::LOCK_thread_cache>,
    128 that=0x1e7db20
    130 native_cond_wait
    142 pthread_cond_wait@@GLIBC_2.3.2
    212 at
    264 Protocol_classic::get_command
    302 do_command
    328 vio_io_wait
    328 vio_read
    328 vio_socket_io_wait
    329 poll
    330 Protocol_classic::read_packet
    331 my_net_read
    331 net_read_packet_header
    331 net_read_raw_loop
    527 handle_connection
    532 pfs_spawn_thread
    618 clone
    618 start_thread

繼續從分析結果中尋找貓膩,以上很多函數都見過,那麼我們就去尋找下平常不起眼的一些的函數名一個個排查。

經過排查後,腦海中對alloc,malloc,my_raw_mallo 有點敏感,意識中就突然想到了tcmalloc,而tcmalloc我的印象中就是解決內存溢出和高併發場景的

然後,死馬當活馬醫,抱着試一試的心跳來安裝測試下

4.4 tcmalloc 安裝和部署

以上是同事整理的 tcmalloc原理&安裝&部署&運維經驗 相關文檔,我這邊簡單摘要下

1. 下載gperftools:

git clone https://github.com/gperftools/gperftools.git


2. 編譯安裝:

sh autogen.sh
yum install libunwind libunwind-devel
./configure --prefix=/usr/local/gperftools
make all
make install
ln -s /usr/local/gperftools/lib/libtcmalloc.so /usr/lib/
ln -s /usr/local/gperftools/lib/libtcmalloc.so /usr/lib64/


3. 編輯/etc/my.cnf, 在mysqld_safe節中加入:

malloc-lib = tcmalloc


4. 啓動時執行命令:

mysqld_safe --user=mysql &


5. 查看tcmalloc是否已經被加載到mysqld進程中:

lsof -n | grep tcmalloc

如果輸出結果不爲空,則說明已經被加載,否則是未加載的狀態。

4.4 malloc vs tcmalloc

malloc

8_malloc_2

9_malloc

10_malloc

tcmolloc

11_tcmalloc_2

12_tcmalloc

以上測試經過了10次以上,通過以上測試看:

  1. 使用glibc自帶的malloc,在同樣的sql模型下,當MySQL QPS 達到16000的時候,CPU使用率已經高達92%,而sys cpu明顯比較高
  2. 使用google的tcmalloc,在同樣的sql模型下,當MySQL QPS達到21000的時候,CPU使用率才39%,而sys cpu明顯比較低

至此,告一段落,接下來我們還有一系列要做的事情

總結

  1. SQL模型 決定了 QPS的極限 , 我們需要一套相對靠譜的模型來探測機器的極限, 給後期擴容給出相對靠譜的理論依據
  2. 簡單的SQL模型意味着更高的QPS,SQL越簡單越好,複雜的業務邏輯可以放在程序端處理
  3. time_zone = '+8:00' 來代替 time_zone='system' , 用以獲得更好的性能
  4. tcmalloc 來替換 malloc , 用以獲得更好的併發,tcmalloc 在小內存分配管理上更加優秀
  5. 透明大頁需要關閉 : 在之前的N次測試中,perf偶爾會看到compact_zone的出現,而這個可能跟透明大頁相關,此問題我沒能復現出來,但仍引起我們的關心
  6. 如何快速、高效的做讀遷移
  7. perf不要隨便亂用,儘量升級Linux的內核到高版本,來降低系統bug的可能,當然也許會引進新的bug --需測試
  8. 如何用tcpcopy將master的讀壓力引流到test server , 畢竟master的操作需要非常謹慎
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章