記一次生產(chǎn)數(shù)據(jù)庫(kù)"意外"重啟的經(jīng)歷
前言
在一個(gè)陽(yáng)光明媚的下午,電腦右下角傳來(lái)一片片郵件提醒,同時(shí)伴隨著微信釘釘?shù)恼饎?dòng),打開(kāi)一看,應(yīng)用各種出錯(cuò),天兔告警,數(shù)據(jù)庫(kù)服務(wù)器內(nèi)存爆紅,MySql 數(shù)據(jù)庫(kù)實(shí)例掛掉了。
排查
先交代一下數(shù)據(jù)庫(kù)版本:
- mysql> status
- --------------
- mysql Ver 14.14 Distrib 5.7.22-22, for Linux (x86_64) using 6.2
- Connection id: 59568
- Current database:
- Current user: root@localhost
- SSL: Not in use
- Current pager: stdout
- Using outfile: ''
- Using delimiter: ;
- Server version: 5.7.22-22-log Percona Server (GPL), Release 22, Revision f62d93c
- Protocol version: 10
崩潰故障排除絕不是一項(xiàng)有趣的任務(wù),特別是如果MySQL沒(méi)有報(bào)告崩潰的原因。例如,當(dāng)MySQL內(nèi)存不足時(shí)。
數(shù)據(jù)庫(kù)郵件告警提醒發(fā)來(lái)的消息:
- Type: mysql
- Tags: 生產(chǎn)主庫(kù)
- Host: 172.16.1.66:3306
- Level: critical
- Item: connect
- Value: down
- Message: mysql server down
登錄 Grafana 監(jiān)控面板,數(shù)據(jù)庫(kù)連接在哪個(gè)時(shí)間段曾有幅度的增長(zhǎng)。
順手檢查一下之前的服務(wù)器郵件監(jiān)控告警記錄,上一個(gè)時(shí)間點(diǎn),內(nèi)存占用率99%,這說(shuō)明了數(shù)據(jù)庫(kù)連接的幅度增長(zhǎng),可能是壓垮服務(wù)器的最后一根稻草。
其實(shí)導(dǎo)致OOM的直接原因并不復(fù)雜,就是因?yàn)榉?wù)器內(nèi)存不足,內(nèi)核需要回收內(nèi)存,回收內(nèi)存就是kill掉服務(wù)器上使用內(nèi)存最多的程序,而MySQL服務(wù)可能就是使用內(nèi)存最多,所以就OOM了。
- Type: os
- Tags: 66數(shù)據(jù)庫(kù)
- Host: 172.16.1.66:
- Level: critical
- Item: memory
- Value: 99%
- Message: too more memory usage
查看系統(tǒng)日志
我們帶著這個(gè)疑問(wèn)來(lái)排查一下日志:
- # 查看日志
- tail -500f /var/log/messages
- # 以下是 oom-killer
- Nov 27 14:55:48 itstyledb1 kernel: mysqld invoked oom-killer: gfp_mask=0x201da, order=0, oom_score_adj=0
- Nov 27 14:55:48 itstyledb1 kernel: mysqld cpuset=/ mems_allowed=0-1
- Nov 27 14:55:48 itstyledb1 kernel: CPU: 2 PID: 895 Comm: mysqld Kdump: loaded Not tainted 3.10.0-862.3.2.el7.x86_64 #1
- Nov 27 14:55:48 itstyledb1 kernel: Hardware name: Huawei RH1288 V3/BC11HGSC0, BIOS 3.22 05/16/2016
- Nov 27 14:55:48 itstyledb1 kernel: Call Trace:
小伙伴們繼續(xù)往下看:
- 0 pages HighMem/MovableOnly
- Nov 27 14:55:48 itstyledb1 kernel: 291281 pages reserved
- Nov 27 14:55:48 itstyledb1 kernel: [ pid ] uid tgid total_vm rss nr_ptes swapents oom_score_adj name
- Nov 27 14:55:48 itstyledb1 kernel: [ 468] 0 468 28271 4326 62 55 0 systemd-journal
- Nov 27 14:55:48 itstyledb1 kernel: [ 490] 0 490 11492 2 24 553 -1000 systemd-udevd
- Nov 27 14:55:48 itstyledb1 kernel: [ 787] 0 787 13877 18 27 96 -1000 auditd
- Nov 27 14:55:48 itstyledb1 kernel: [ 810] 81 810 14552 81 34 89 -900 dbus-daemon
- Nov 27 14:55:48 itstyledb1 kernel: [ 815] 0 815 55956 1 60 466 0 abrtd
- Nov 27 14:55:48 itstyledb1 kernel: [ 816] 0 816 55327 9 64 346 0 abrt-watch-log
- Nov 27 14:55:48 itstyledb1 kernel: [ 818] 0 818 121607 220 90 495 0 NetworkManager
- Nov 27 14:55:48 itstyledb1 kernel: [ 822] 0 822 5415 49 16 33 0 irqbalance
- Nov 27 14:55:48 itstyledb1 kernel: [ 823] 997 823 134634 97 60 1306 0 polkitd
- Nov 27 14:55:48 itstyledb1 kernel: [ 825] 0 825 6594 42 20 41 0 systemd-logind
- Nov 27 14:55:48 itstyledb1 kernel: [ 830] 0 830 31578 28 21 139 0 crond
- Nov 27 14:55:48 itstyledb1 kernel: [ 839] 0 839 27522 2 10 31 0 agetty
- Nov 27 14:55:48 itstyledb1 kernel: [ 1142] 0 1142 143454 114 97 2672 0 tuned
- Nov 27 14:55:48 itstyledb1 kernel: [ 1144] 0 1144 28203 11 59 246 -1000 sshd
- Nov 27 14:55:48 itstyledb1 kernel: [ 1145] 0 1145 97438 694 103 328 0 rsyslogd
- Nov 27 14:55:48 itstyledb1 kernel: [ 1369] 0 1369 22526 20 44 256 0 master
- Nov 27 14:55:48 itstyledb1 kernel: [ 1371] 89 1371 22596 32 46 251 0 qmgr
- Nov 27 14:55:48 itstyledb1 kernel: [ 5140] 0 5140 5102 1617 15 239 0 mysqld_exporter
- Nov 27 14:55:48 itstyledb1 kernel: [ 9430] 0 9430 55966 378 62 790 0 snmpd
- Nov 27 14:55:48 itstyledb1 kernel: [30320] 27 30320 22951376 13928375 43437 8163662 0 mysqld
- Nov 27 14:55:48 itstyledb1 kernel: [ 688] 89 688 22552 271 46 0 0 pickup
- Nov 27 14:55:48 itstyledb1 kernel: Out of memory: Kill process 30320 (mysqld) score 984 or sacrifice child
- Nov 27 14:55:48 itstyledb1 kernel: Killed process 30320 (mysqld) total-vm:91805504kB, anon-rss:55713500kB, file-rss:0kB, shmem-rss:0kB
- Nov 27 14:56:00 itstyledb1 systemd: mysqld.service: main process exited, code=killed, status=9/KILL
- Nov 27 14:56:00 itstyledb1 systemd: Unit mysqld.service entered failed state.
- Nov 27 14:56:00 itstyledb1 systemd: mysqld.service failed.
- Nov 27 14:56:00 itstyledb1 systemd: mysqld.service holdoff time over, scheduling restart.
- Nov 27 14:56:01 itstyledb1 systemd: Starting MySQL Server...
當(dāng)out of memory發(fā)生時(shí),outofmemory函數(shù)會(huì)選擇一個(gè)內(nèi)核認(rèn)為犯有分配過(guò)多內(nèi)存 “罪行”的進(jìn)程,并殺死該進(jìn)程。顯然 Mysql 就是哪個(gè)“罪人”。
隨后 MySql 會(huì)自動(dòng)重啟。重啟以后,內(nèi)存是下來(lái)了,但是臨近下班的時(shí)候,差不多又又又占滿(mǎn)了。
- [root@itstyledb1 ~]# free -m
- total used free shared buff/cache available
- Mem: 55803 54976 241 10 585 349
- Swap: 32064 25036 7028
找到MySql進(jìn)程,執(zhí)行以下top -p pid,內(nèi)存使用52.4g
- PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
- 935 mysql 20 0 79.7g 52.4g 7336 S 0.3 96.1 255:44.76 mysqld
計(jì)算內(nèi)存使用
1)查看MySQL全局占用多少內(nèi)存
- SELECT (@@innodb_buffer_pool_size
- +@@innodb_log_buffer_size
- +@@key_buffer_size) / 1024 /1024 AS MEMORY_MB;
查詢(xún)結(jié)果為:
- +----------------+
- | MEMORY_MB |
- +----------------+
- | 20512.00000000 |
- +----------------+
2)查看performance_schema占用多少內(nèi)存
- SELECT SUBSTRING_INDEX(event_name,'/',2) AS
- code_area, sys.format_bytes(SUM(current_alloc))
- AS current_alloc
- FROM sys.x$memory_global_by_current_bytes
- GROUP BY SUBSTRING_INDEX(event_name,'/',2)
- ORDER BY SUM(current_alloc) DESC;
查詢(xún)結(jié)果為:
- +---------------------------+---------------+
- | code_area | current_alloc |
- +---------------------------+---------------+
- | memory/performance_schema | 349.80 MiB |
- +---------------------------+---------------+
3)查看每個(gè)線(xiàn)程占用多少內(nèi)存
- SELECT ( ( @@read_buffer_size
- + @@read_rnd_buffer_size
- + @@sort_buffer_size
- + @@join_buffer_size
- + @@binlog_cache_size
- + @@thread_stack
- + @@max_allowed_packet
- + @@net_buffer_length )
- ) / (1024*1024) AS MEMORY_MB;
查詢(xún)結(jié)果為:
- +-----------+
- | MEMORY_MB |
- +-----------+
- | 87.5156 |
- +-----------+
查看當(dāng)前線(xiàn)程
- show full processlist
最終結(jié)果為:
- +-----------+
- | MEMORY_MB |
- +-----------+
- | 87.5156*37|
- +-----------+
4)查看 memory 存儲(chǔ)引擎占用多少內(nèi)存
- SELECT SUM(max_data_length)/1024/1024 AS MEMORY_MB FROM information_schema.tables WHERE ENGINE='memory';
查詢(xún)結(jié)果為:
- +---------------+
- | MEMORY_MB |
- +---------------+
- | 3857.37713909 |
- +---------------+
以上四項(xiàng)加起來(lái)差不多也就27975MB,差不錯(cuò)28G的樣子,但是 MySql 進(jìn)程顯示占用了52.4G,那么剩下24.4G去哪了?
線(xiàn)程池
此線(xiàn)程池非彼連接池,其實(shí)兩者是有很大區(qū)別的,連接池一般在客戶(hù)端設(shè)置,而線(xiàn)程池是在DB服務(wù)器上配置;另外連接池可以取到避免了連接頻繁創(chuàng)建和銷(xiāo)毀,但是無(wú)法取到控制MySQL活動(dòng)線(xiàn)程數(shù)的目標(biāo),在高并發(fā)場(chǎng)景下,無(wú)法取到保護(hù)DB的作用。比較好的方式是將連接池和線(xiàn)程池結(jié)合起來(lái)使用。
關(guān)于線(xiàn)程池的一些參數(shù):
- mysql> show variables like 'thread%';
- +-------------------------------+---------------------------+
- | Variable_name | Value |
- +-------------------------------+---------------------------+
- | thread_handling | one-thread-per-connection |
- | thread_pool_high_prio_mode | transactions |
- | thread_pool_high_prio_tickets | 4294967295 |
- | thread_pool_idle_timeout | 60 |
- | thread_pool_max_threads | 100000 |
- | thread_pool_oversubscribe | 3 |
- | thread_pool_size | 12 |
- | thread_pool_stall_limit | 500 |
- +-------------------------------+---------------------------+
thread_handling:
該參數(shù)是配置線(xiàn)程模型,默認(rèn)情況是one-thread-per-connection,也就是不啟用線(xiàn)程池。將該參數(shù)設(shè)置為pool-of-threads即啟用了線(xiàn)程池。
threadpoolsize:
該參數(shù)是設(shè)置線(xiàn)程池的Group的數(shù)量,默認(rèn)為系統(tǒng)CPU的個(gè)數(shù),充分利用CPU資源。
threadpooloversubscribe:
該參數(shù)設(shè)置group中的最大線(xiàn)程數(shù),每個(gè)group的最大線(xiàn)程數(shù)為threadpooloversubscribe+1,注意listener線(xiàn)程不包含在內(nèi)。
threadpoolhighpriomode:
高優(yōu)先級(jí)隊(duì)列的控制參數(shù),有三個(gè)值(transactions/statements/none),默認(rèn)是transactions,三個(gè)值的含義如下:
- transactions:對(duì)于已經(jīng)啟動(dòng)事務(wù)的語(yǔ)句放到高優(yōu)先級(jí)隊(duì)列中,不過(guò)還取決于后面的threadpoolhighpriotickets參數(shù)
- statements:這個(gè)模式所有的語(yǔ)句都會(huì)放到高優(yōu)先級(jí)隊(duì)列中,不會(huì)使用到低優(yōu)先級(jí)隊(duì)列
- none:這個(gè)模式不使用高優(yōu)先級(jí)隊(duì)列
threadpoolhighpriotickets:
該參數(shù)控制每個(gè)連接最多語(yǔ)序多少次被放入高優(yōu)先級(jí)隊(duì)列中,默認(rèn)為4294967295,注意這個(gè)參數(shù)只有在threadpoolhighpriomode為transactions的時(shí)候才有效果。
threadpoolidle_timeout:
worker線(xiàn)程最大空閑時(shí)間,默認(rèn)為60秒,超過(guò)限制后會(huì)退出。
threadpoolmax_threads:
該參數(shù)用來(lái)限制線(xiàn)程池最大的線(xiàn)程數(shù),超過(guò)該限制后將無(wú)法再創(chuàng)建更多的線(xiàn)程,默認(rèn)為100000。
threadpoolstall_limit:
該參數(shù)設(shè)置timer線(xiàn)程的檢測(cè)group是否異常的時(shí)間間隔,默認(rèn)為500ms。
最終配置如下:
- #thread pool
- thread_handling=pool-of-threads
- #Group的數(shù)量,默認(rèn)為系統(tǒng)CPU的個(gè)數(shù),充分利用CPU資源
- thread_pool_size=24
- #每個(gè)group的最大線(xiàn)程數(shù)為thread_pool_oversubscribe+1
- thread_pool_oversubscribe=3
- performance_schema=off
- #extra connection,防止線(xiàn)程池滿(mǎn)的情況下無(wú)法登錄MySQL
- extra_max_connections = 8
- extra_port = 33333
備注:線(xiàn)程池在Percona,MariaDB,Oracle MySQL企業(yè)版中提供,Oracle MySQL社區(qū)版并不提供。
線(xiàn)程池貌似并不會(huì)直接導(dǎo)致內(nèi)存不回收,網(wǎng)上有說(shuō)同時(shí)開(kāi)啟Thread pool和PS會(huì)出現(xiàn)內(nèi)存泄露,但是 目前Percona server 5.7.21-20+版本已經(jīng)修復(fù)了這個(gè)問(wèn)題,顯然是不存在的。
慢查詢(xún)
由于是生產(chǎn)環(huán)境,這個(gè)問(wèn)題拖得時(shí)間有點(diǎn)長(zhǎng),那么慢查詢(xún)會(huì)不會(huì)影響內(nèi)存使用問(wèn)題呢?帶著這個(gè)問(wèn)題,查看了慢查詢(xún)后臺(tái)列表,在數(shù)據(jù)庫(kù)奔潰的前一個(gè)時(shí)間段,的確有不少慢查詢(xún)語(yǔ)句。但是這并不能在一定程度上說(shuō)明問(wèn)題,由于服務(wù)器的 MySql 服務(wù)在殺死之前,內(nèi)存已經(jīng)見(jiàn)底,此時(shí)連接數(shù)并不多,也就三四十來(lái)個(gè)左右,大多處于休眠狀態(tài),并且此時(shí)已經(jīng)占用了大部分的Swap空間。也就是說(shuō),在資源有限的情況下必定會(huì)出現(xiàn)不少慢查詢(xún)語(yǔ)句。
小結(jié)
其實(shí)這個(gè)"意外"一點(diǎn)也不意外,其實(shí)已經(jīng)發(fā)生了多次了。但是還是做個(gè)小結(jié)吧,因?yàn)樽罱K沒(méi)有確認(rèn)問(wèn)題出現(xiàn)在哪里,所以還是發(fā)布了吧,萬(wàn)一有專(zhuān)業(yè)的DBA遇到類(lèi)似的問(wèn)題還可以小小的解惑一下。