mysqld:哥,我起不來了……
作者:賁紹華,愛可生研發中心工程師,負責項目的需求與維護工作。其他身份:柯基鏟屎官。
愛可生開源社區出品,原創內容未經授權不得隨意使用,轉載請聯繫小編並註明來源。
本文約 2100 字,預計閱讀需要 7 分鐘。
引言
正如題目所述,在自動化測試場景下,通過 systemd 無法啓動 MySQL。
連續 kill -9
結束實例進程,檢測 mysqld 在退出後是否會被正確拉起。
具體信息如下:
- 主機信息:CentOS 8(Docker 容器)
- 使用 systemd 的方式管理 mysqld 進程
- systemd service 的運行模式爲:forking
- 啓動命令如下:
# systemd 啓動命令
sudo -S systemctl start mysqld_11690.service
# systemd service 內的 ExecStart 啓動命令
/opt/mysql/base/8.0.34/bin/mysqld --defaults-file=/opt/mysql/etc/11690/my.cnf --daemonize --pid-file=/opt/mysql/data/11690/mysqld.pid --user=actiontech-mysql --socket=/opt/mysql/data/11690/mysqld.sock --port=11690
現象描述
啓動命令持續 hang 住,既不成功,也無任何返回,嘗試幾次後均無法手動復現該場景。
下圖爲復現場景,service 端口號不一致請忽略。
MySQL 錯誤日誌無任何信息。查看 systemd service 狀態,發現啓動腳本中由於缺少參數 MAIN PID
,執行失敗。
systemd 最後輸出的信息爲:New main PID 31036 does not exist or is a zombie
原因總結
systemd 啓動 mysqld 的過程中,會先根據 service 模板中的配置,執行:
- ExecStart(啓動 mysqld)
- mysqld 啓動創建
pid
文件 - ExecStartPost(自定義的一些後置腳本:調整權限、將
pid
寫入 cgroup 等)
在 步驟 2-3 的中間態,也就是 pid
文件剛創建出來時,主機上接收到了自動化測試下發的命令:sudo -S kill -9 $(cat /opt/mysql/data/11690/mysqld.pid)
。
由於這個 pid
文件和 pid
進程確實存在(如果不存在 kill
命令或 cat
會報錯),自動化的 CASE 認爲 kill
操作已成功結束。但由於 mysqld.pid
這個文件是由 MySQL 自身維護的,在 systemd 的視角中,還需要繼續等待 步驟 3 完成,才認爲啓動成功。
在 systemd 使用 forking 模式時,會根據子進程的 PID
值判斷服務是否成功啓動。
如果子進程成功啓動,並且沒有發生意外退出,則 systemd 會認爲服務已啓動,並將子進程的 PID
作爲 MAIN PID
。
而如果子進程啓動失敗或意外退出,則 systemd 會認爲服務未能成功啓動。
結論
在執行 ExecStartPost 時,由於子進程 ID 31036 已經被 kill
掉,後置 shell
缺少了啓動參數,但 ExecStart 步驟已完成,導致 MAIN PID 31036 成爲了只存在於 systemd 裏的 殭屍進程。
排查過程
當遇到這個問題時是有點懵的,簡單檢查了一下內存、磁盤基本信息。符合預期並沒有出現資源不足的情況。
先從 MySQL 的 Error Log 看看有什麼發現。查看結果如下:
...無關內容省略...
2024-02-05T05:08:42.538326+08:00 0 [Warning] [MY-010539] [Repl] Recovery from source pos 3943309 and file mysql-bin.000001 for channel ''. Previous relay log pos and relay log file had been set to 4, /opt/mysql/log/relaylog/11690/mysql-relay.000004 respectively.
2024-02-05T05:08:42.548513+08:00 0 [System] [MY-010931] [Server] /opt/mysql/base/8.0.34/bin/mysqld: ready for connections. Version: '8.0.34' socket: '/opt/mysql/data/11690/mysqld.sock' port: 11690 MySQL Community Server - GPL.
2024-02-05T05:08:42.548633+08:00 0 [System] [MY-013292] [Server] Admin interface ready for connections, address: '127.0.0.1' port: 6114
2024-02-05T05:08:42.548620+08:00 5 [Note] [MY-010051] [Server] Event Scheduler: scheduler thread started with id 5
通過觀察 Error Log 發現並無任何有用信息,因爲啓動的時間點之後無任何日誌信息輸出。
查看 systemctl status 確認服務當前狀態:
下圖爲正常情況下的 status 信息:
通過對比後,整理兩條有用信息:
- 後置
shell
由於缺少-p
參數導致執行失敗(-p
參數爲MAIN PID
,也就是 fork 子進程啓動後的PID
)。 - systemd 無法獲取
PID 31036
,不存在或者爲殭屍進程。
先來檢查進程 ID
與 mysqld.pid
看看:
確認線索:
PID 31036
不存在mysqld.pid
文件存在,且文件內容爲 31036top
命令查看不存在殭屍進程
還需要獲取更多的線索來確認原因,檢查 journalctl -u
內容,看看是否有幫助:
sh-4.4# journalctl -u mysqld_11690.service
-- Logs begin at Mon 2024-02-05 04:00:35 CST, end at Mon 2024-02-05 17:08:01 CST. --
Feb 05 05:07:54 udp-11 systemd[1]: Starting MySQL Server...
Feb 05 05:07:56 udp-11 systemd[1]: Started MySQL Server.
Feb 05 05:08:31 udp-11 systemd[1]: mysqld_11690.service: Main process exited, code=killed, status=9/KILL
Feb 05 05:08:31 udp-11 systemd[1]: mysqld_11690.service: Failed with result 'signal'.
Feb 05 05:08:32 udp-11 systemd[1]: Starting MySQL Server...
Feb 05 05:08:36 udp-11 systemd[1]: Started MySQL Server.
Feb 05 05:08:37 udp-11 systemd[1]: mysqld_11690.service: Main process exited, code=killed, status=9/KILL
Feb 05 05:08:37 udp-11 systemd[1]: mysqld_11690.service: Failed with result 'signal'.
Feb 05 05:08:39 udp-11 systemd[1]: Starting MySQL Server...
Feb 05 05:08:42 udp-11 u_set_iops.sh[31507]: /etc/systemd/system/mysqld_11690.service.d/u_set_iops.sh: option requires an argument -- p
Feb 05 05:08:42 udp-11 systemd[1]: mysqld_11690.service: New main PID 31036 does not exist or is a zombie.
這裏的 journalctl -u
內容也只描述了現象,無法分析具體原因,與 systemctl status 的內容相差不多,幫助不大。
查看 /var/log/messages
系統日誌內容:
發現循環報出了一些內存方面的錯誤信息,通過搜索後發現該錯誤可能爲硬件問題。詢問了自動化測試的同事後,得到結論:
- 場景爲偶發問題,執行 4 次用例,2 次成功,2 次失敗
- 每次執行均爲同一臺宿主機,同一份容器鏡像
- 失敗時 hang 住的容器爲同一個
既然有成功執行的結果,這裏就先忽略硬件問題導致的。
既然提到了容器,此時想到了 cgroup 會不會映射宿主機的時候出現了問題?在上邊排查的 systemctl status 中,觀察可知 cgroup 映射的宿主機目錄爲:CGroup: /docker/3a72b2cdc7bd9beb1c7b2abec24763046604602a38f0fcb7406d17f5d33353d2/system.slice/mysqld_11690.service
檢查父級文件夾 system.slice
的讀寫權限並無異常。先暫時排除 cgroup 的映射問題(因爲主機上還有其他 systemd 接管的 service 也在使用同一份 cgroup)。
打算試試 pstack 能不能看到 systemd 具體 hang 在了哪個地方,3048143
爲 systemctl start 的 pid
:
sh-4.4# pstack 3048143
#0 0x00007fdfaef33ade in ppoll () from /lib64/libc.so.6
#1 0x00007fdfaf7768ee in bus_poll () from /usr/lib/systemd/libsystemd-shared-239.so
#2 0x00007fdfaf6a8f3d in bus_wait_for_jobs () from /usr/lib/systemd/libsystemd-shared-239.so
#3 0x000055b4c2d59b2e in start_unit ()
#4 0x00007fdfaf7457e3 in dispatch_verb () from /usr/lib/systemd/libsystemd-shared-239.so
#5 0x000055b4c2d4c2b4 in main ()
觀察發現 start_unit 比較可疑,start_unit()
函數位於可執行文件中,它用於啓動 systemd units,並沒有什麼幫助。
根據已有線索,推測後可知:
mysqld.pid
文件存在,則表示之前確實有一個 mysqld 且進程號爲31036
的進程被啓動了- 進程啓動後被自動化用例
kill -9
結束掉 - systemd 獲取到了一個已經被結束的
MAIN PID
,後置 shell 執行失敗,fork 流程失敗
通過梳理 systemd 啓動流程的步驟,推測可能性。MySQL 實例只有在 mysqld 成功啓動後纔會生成 mysqld.pid
文件,所以可能是在後續步驟裏被意外 kill -9
結束掉導致的。
復現方式
既然沒什麼其他頭緒和線索了,打算根據推測結論嘗試復現一下試試。
4.1 調整 systemd mysql serivce 模板
編輯模板文件 /etc/systemd/system/mysqld_11690.service
,在 mysqld 啓動後,sleep10
秒,方便在這時間窗口內模擬kill掉實例進程的場景。
4.2 配置重載
執行 systemctl daemon-reload
令變更生效。
4.3 場景重現
- [ssh seesion A] 首先準備一個新的容器,做好相關配置後執行
sudo -S systemctl start mysqld_11690.service
啓動一個 mysqld 進程,此時會因爲sleep
的原因 hang 住會話。 - [ssh seesion B] 在另一個會話窗口,
start
命令 hang 住時,檢查mysqld.pid
文件,一旦文件被創建後,立刻執行sudo -S kill -9 $(cat /opt/mysql/data/11690/mysqld.pid)
。 - 此時觀察 systemctl status,表現與預期一致
解決方式
先 kill
掉 hang 住的 systemctl start 命令,執行 systemctl stop mysqld_11690.service
,這可以讓 systemd 主動結束殭屍進程,雖然 stop
命令可能會報錯但這並不影響。
等待 stop
執行完成後再次使用 start
命令啓動,恢復正常。
更多技術文章,請訪問:https://opensource.actionsky.com/
關於 SQLE
SQLE 是一款全方位的 SQL 質量管理平臺,覆蓋開發至生產環境的 SQL 審覈和管理。支持主流的開源、商業、國產數據庫,爲開發和運維提供流程自動化能力,提升上線效率,提高數據質量。