哈嘍大家好,我是鹹魚。 今天分享一個在壓測過程中遇到的問題,當時排查這個問題費了我們好大的勁,所以我覺得有必要寫一篇文章來記錄一下。 問題出現 周末在進行壓測的時候,測試和開發的同事反映壓測有問題,請求打到 A 服務上被拒絕了。 我們登錄伺服器查看 A 服務的日誌,發現頻繁地報 Too many o ...
哈嘍大家好,我是鹹魚。
今天分享一個在壓測過程中遇到的問題,當時排查這個問題費了我們好大的勁,所以我覺得有必要寫一篇文章來記錄一下。
問題出現
周末在進行壓測的時候,測試和開發的同事反映壓測有問題,請求打到 A 服務上被拒絕了。
我們登錄伺服器查看 A 服務的日誌,發現頻繁地報 Too many open files
錯誤,可以看到壓測的時候該進程要處理大量的 socket,導致打開的文件描述符數量已經達到了操作系統允許的最大限制,因此無法再打開更多的文件。
java.io.IOException: Too many open files
...
既然是系統資源相關的問題,我們先 ulimit -n
看一下系統中進程能夠使用的最大文件描述符是多少個:
[root@localhost ~]# ulimit -n
100000
為了穩妥起見,我們還查看了 /etc/security/limits.conf
文件的內容:
[root@localhost ~]# cat /etc/security/limits.conf
* soft nofile 100000
* hard nofile 100000
可以看到系統限制進程能夠最多打開 100000 個文件(我們在伺服器初始化的時候設置的值)。但是壓測的量還沒上去,A 服務上的進程打開文件數就超過了 10 萬個嗎?
查看一下這個進程打開了多少個文件:
[root@localhost ~]# cat /proc/<該進程的 PID>/fd | wc -l
8295
我們發現該進程才打開了八千多個文件,遠遠沒有達到系統限制的 100000。
接著看下這個進程的文件描述符數量限制,通過 /proc/<Java 進程的 PID>/limits
文件來查看
[root@localhost ~]# cat /proc/<該進程的 PID>/limits
...
Max open files 8192 8192 files
...
奇怪,按理說每個進程的文件描述符使用限制應該是 100000,但是這裡卻顯示只有 8192,說明系統層面的資源限制在這個進程上沒有生效,而且這個 8192 是怎麼來的,為什麼是 8192 ?
我們重啟了一下這個服務,發現重啟之後該進程的資源限制生效了,Max open files
數量變成了 100000 !
# 重啟服務
[root@localhost ~]# sh spring-boot.sh restart
# 查看該進程的文件描述符數量限制
[root@localhost ~]# cat /proc/<該進程的 PID>/limits
...
Max open files 100000 100000 files
...
定位問題
發現了這個現象之後,我們接著排查了其他的服務,發現服務進程的 Max open files
數量都是 8192,而系統設置的卻是 100000。
如果我們一旦手動重啟服務,進程的 Max open files
數就變成了系統設置的 100000。
我們在初始化伺服器的時候,已經修改了進程的最大打開文件數為 100000,如果配置沒有生效,那也應該是系統的預設值 1024 ,而不是 8192。
就在一籌莫展的時候,我們註意到了一個細微差別:由於線上伺服器較多,平時我們都是通過 Saltstack 來管理服務(包括服務的啟動重啟停止),而今天是在終端上重啟服務的,所以會不會跟 Saltstack 相關?
然後我們為了驗證執行了下麵的步驟:
- 找到一臺伺服器,先查看了上面進程的最大打開文件數,發現是 8192。
- 手動重啟一下服務,發現進程的最大打開文件數變成 100000
- 我們在 salt-master 上遠程重啟這台服務,發現進程的最大打開文件數變成了 8192。
接著我們在 salt-master 上遠程執行 ulimit -a
命令
[root@salt-master ~]# salt <伺服器 ip> cmd.run 'ulimit -a'
...
open files (-n) 8192
...
排查到這裡,終於有點柳暗花明的感覺了,我們看一下這台伺服器上 salt-minion 進程的資源限制:
[root@localhost ~]# cat /proc/<salt-minion 進程的 PID>/limits
...
Max open files 8192 8192 files
...
又因為 salt-minion 是通過 systemctl 來管理的,所以我們在這台伺服器上查看 salt-minion 的服務註冊文件:
[root@localhost ~]# cat /usr/lib/systemd/system/salt-minion.service
[Unit]
...
[Service]
KillMode=process
Type=notify
NotifyAccess=all
LimitNOFILE=8192
ExecStart=/usr/bin/salt-minion
[Install]
...
果然,奇怪的 8192 出現在了這兩處地方!
關於 Linux 下 Ulimit 資源限制
首先,/etc/security/limits.conf
文件中的配置對於通過 PAM 認證登錄的用戶資源限制是有效的。
也就是說,登陸了系統的用戶,無論是互動式登錄還是非互動式登錄,其資源限制都會受到 limits.conf
中的配置影響。
但是,在 CentOS 7/RHEL 7 等系統中,預設採用 Systemd 作為 init 系統,取代了之前的 SysV init,對於 Systemd 啟動的服務(例如使用 systemctl
啟動的服務),limits.conf
中的配置對其資源限制是不生效的。
這是因為 Systemd 會忽略 limits.conf
中的設置,而是使用自己的資源管理機制。
這裡補充一下,在 CenOS 5/6 中,/etc/security/limits.conf
和 /etc/security/limits.d
中的配置文件是為通過PAM登錄的用戶設置資源限制的。這些限制在用戶登錄時由PAM模塊載入並應用(什麼是 PAM ,你可以簡單理解為一般情況登陸了終端都會載入 PAM 模塊),因此僅在用戶會話期間生效。
所以就會出現某進程在機器重啟後資源限制設置與 /etc/security/limits.conf
和 limits.d
下的文件不一致的問題,可能是因為進程是在系統啟動時自動啟動的,而不是通過用戶登錄而啟動的。因此不會受到 PAM 模塊載入的影響。在這種情況下,進程的資源限制可能受到系統級別的預設限制或其他配置文件的影響。
我們對某一臺 CentOS 6 的機器進行重啟後,發現上面設置了開機自啟動的進程的資源限制都發生了變化(變成了系統設置的預設值),一旦我們手動重啟,資源限制則設置成了跟 /etc/security/limits.conf
文件設置的一致
對於一些設置了開機自啟動的進程,如果在機器重啟後保持資源限制不發生變化,可以在進程的啟動腳本裡加上關於資源限制設置的命令,比如說
ulimit -SHn 10000
所以在 Systemd 中,可以通過在服務單元文件中設置 Limit*
選項來控制服務的資源限制,比如限制進程的最大打開文件數 LimitNOFILE
為 8192。
LimitNOFILE=8192
當我們通過 Salt-master 來管理遠程伺服器的時候(伺服器上面往往部署了 Salt-minion),即 Salt-master 發送命令給 Salt-minion 時,通常情況下,Salt-minion 會直接在自身進程中執行相應的操作。
如果是通過 Salt-minion 來啟動一個進程,這個進程則會繼承 Salt-minion 的資源限制配置。
這也就是為什麼通過 salt-minion 管理的進程的最大打開文件數都是 8192,因為 salt-minion 的最大打開文件數就是 8192。
解決問題
既然知道了這是關於 systemd services 的資源限制相關的問題,那就好解決了。
- 針對所有的 service :
配置 systemd services 的資源限制可以在全局範圍內進行。這些配置文件分別位於 /etc/systemd/system.conf
和 /etc/systemd/user.conf
。
system.conf
文件適用於系統級實例,而 user.conf
文件適用於用戶級實例。一般建議在 system.conf
中配置服務的資源限制,但如果在 /etc/systemd/system.conf
文件中修改配置,則需要重啟系統才能使更改生效。
此外,還可以通過在 /etc/systemd/system.conf.d/
和 /etc/systemd/user.conf.d/
目錄中放置 .conf
文件進行配置。
需要註意的是,system.conf.d/*.conf
中的配置會覆蓋 system.conf
中的配置。
如果你打算修改所有通過 systemctl 管理的服務進程的資源限制(比如修改最大文件打開數量)
那可以修改/etc/systemd/system.conf
[root@localhost ~]# vim /etc/systemd/system.conf
DefaultLimitNOFILE=100000
- 針對單個 service:
這次案例的解決方法就是要修改單個 service (即 salt-minion)的資源限制配置。
# 修改 salt-minion 的 service 文件,改成和系統一樣的資源限制配置
[root@localhost ~]# cat /usr/lib/systemd/system/salt-minion.service
...
[Service]
LimitNOFILE=100000
...
修改完之後別忘了重啟。
[root@localhost ~]# systemctl daemon-reload
[root@localhost ~]# systemctl restart salt-minion.service