模板說明
守護由linux crontab定時調度,守護程序不負責任務調度(crontab穩定性高,守護程序需要使用循環語法,穩定性無法保證,如進程退出)
守護的驗證標準
- 開機能啟動
- 正常運行時不守護
- 手動關閉進程,守護啟動
- 同時只有一個進程
crontab
crontab -e 與 vim /etc/crontab 的區別
-
crontab -e 系統會檢查語法,而vim /etc/crontab不檢查語法。
-
crontab -e的寫法與vi /etc/crontab也有微小差異,在vim /etc/crontab時,一定要加入用戶,否則不會生效;而crontab -e定制定時任務時,則不需要 添加用戶,否則也會失效。
crontab -e的格式: * * * * * command vim /etc/crontab的格式: * * * * * user command
以下為 crontab -e 語法說明
# Example of job definition: # .---------------- minute (0 - 59) # | .------------- hour (0 - 23) # | | .---------- day of month (1 - 31) # | | | .------- month (1 - 12) OR jan,feb,mar,apr ... # | | | | .---- day of week (0 - 6) (Sunday=0 or 7) OR sun,mon,tue,wed,thu,fri,sat # | | | | | # * * * * * command to be executed
crontab運行跟蹤
查看定時任務是否配置成功可以使用:tail -f /var/log/cron 來進行跟蹤任務是否執行
特殊語法
支持特殊關鍵字實現任務調度,代替 5個時間設置標識符,特殊語法以 @ 符號為前綴
具體請參考:官方文檔
語法:@xxx command
@reboot : Run once after reboot. 重啟時執行一次,實測比 /etc/rc.d/rc.local 更早執行(提前1-2s),比默認1分鐘定時提前30s左右 @yearly : Run once a year, ie. "0 0 1 1 *". 一年執行一次,1月1號0點0分執行 @annually : Run once a year, ie. "0 0 1 1 *". 一年執行一次,1月1號0點0分執行 @monthly : Run once a month, ie. "0 0 1 * *". 一月執行一次,每月1號0點0分執行 @weekly : Run once a week, ie. "0 0 * * 0". 一周執行一次 @daily : Run once a day, ie. "0 0 * * *". 一天執行一次 @hourly : Run once an hour, ie. "0 * * * *". 一小時執行一次
守護程序模板
#! /bin/bash #守護由linux crontab調度,本程序不負責任務調度(crontab穩定性高) #本腳本支持根據 服務名稱 或 服務端口 守護,請根據守護的組件特性調用對應方法 #crontab不會自動加載全局環境變量,此處需要主動加載環境變量 source /etc/profile #!!!需要關注的內容!!!# SERVICE_NAME= SERVICE_PORT= #kafka命令中JMX_PORT=9999為賦值語句,而在執行時默認會被識別為命令語句,所以會提示異常:JMX_PORT=9999: 未找到命令 #所以在執行復雜命令時(賦值、一行多條命令等),使用eval語法: eval ${SERVICE_CMD} SERVICE_CMD= MONITOR_LOG= #!!!需要關注的內容!!!# #格式化日期時間函數 function getDatetime(){ local cur=`date "+%Y-%m-%d %H:%M:%S"` echo ${cur} } #根據 服務端口 守護 #此方法適合對外提供服務的程序,監聽端口只能被唯一程序占用 function processByPort(){ ... } #根據 服務名稱 守護, 使用 `killall 服務名稱` 結束進程 #此方法適合進程名與服務名稱唯一的程序,不適合java程序(進程名為java,而不是jar包) function processByNameKillByName(){ ... } #根據 服務名稱 守護, 使用 `kill pid` 結束進程 #此方法適合java程序 function processByNameKillByPid(){ ... } #!!!需要關注的內容!!!# processByPort #!!!需要關注的內容!!!#
#根據 服務端口 守護方案
#根據 服務端口 守護 #此方法適合對外提供服務的程序,監聽端口只能被唯一程序占用 function processByPort(){ #tcp 0 0 0.0.0.0:7379 0.0.0.0:* LISTEN 5593/redis-server 0 #指定 -w 選項全字匹配,避免多個端口(7379,73791)同時匹配,造成誤殺 NUM=`netstat -lntup | grep -w ${SERVICE_PORT} | wc -l` #進程數少于1,啟動進程 if [ ${NUM} -lt 1 ];then echo `getDatetime` "found 0, start ${SERVICE_NAME}" >> ${MONITOR_LOG} eval $SERVICE_CMD #大于1,殺掉所有進程,重啟 elif [ ${NUM} -gt 1 ];then echo `getDatetime` "more than 1 found, killall and start ${SERVICE_NAME}" >> ${MONITOR_LOG} pids=`netstat -lntup | grep -w ${SERVICE_PORT} | awk -F '[ /]+' '{print $7}'` for pid in ${pids} do #此處可以根據實際服務決定是否發送相關進程信號,如kill -9強制停止進程 #默認會通知進程優雅關閉,不會強制停止進程 #有些服務如 MySQL 自身存在守護程序,監聽進程信號,此時執行kill、killall、pkill等如果發送信號,MySQL會重置新的進程ID繼續運行 #停止 MySQL 可以使用服務自身命令 service mysqld stop 或執行kill、killall、pkill等不發送進程信號 #!!!需要關注的內容!!!# echo "kill ${pid}" >> ${MONITOR_LOG} kill -9 ${pid} #!!!需要關注的內容!!!# done #!!!需要關注的內容!!!# #如果停止進程時沒有發送強制停止信號,則此處需要延遲一定時間(單位:秒),待進程優雅退出后才能啟動服務,否則服務啟動失敗(絕大部分服務不允許重復啟動) #sleep 5 #!!!需要關注的內容!!!# #啟動服務 eval $SERVICE_CMD #else # echo `getDatetime` "found 1, ${SERVICE_NAME} is alive" >> ${MONITOR_LOG} fi }
#根據 服務名稱 守護方案
進程名稱與服務名稱一致,使用 `killall 服務名稱` 結束進程
#根據 服務名稱 守護, 使用 `killall 服務名稱` 結束進程 #此方法適合進程名與服務名稱唯一的程序,不適合java程序(進程名為java,而不是jar包) function processByNameKillByName(){ #root 5593 1 0 13:06 ? 00:00:08 /usr/local/redis/redis-5.0.13/bin/redis-server 0.0.0.0:7379 [cluster] #指定 -w 選項全字匹配,避免多個名稱(7379,73791)同時匹配,造成誤殺 NUM=`ps -ef | grep -w ${SERVICE_NAME} | grep -v grep | wc -l` #進程數少于1,啟動進程 if [ ${NUM} -lt 1 ];then echo `getDatetime` "found 0, start ${SERVICE_NAME}" >> ${MONITOR_LOG} eval $SERVICE_CMD #大于1,殺掉所有進程,重啟 elif [ ${NUM} -gt 1 ];then echo `getDatetime` "more than 1 found, killall and start ${SERVICE_NAME}" >> ${MONITOR_LOG} #此處可以根據實際服務決定是否發送相關進程信號,如kill -9強制停止進程 #默認會通知進程優雅關閉,不會強制停止進程 #有些服務如 MySQL 自身存在守護程序,監聽進程信號,此時執行kill、killall、pkill等如果發送信號,MySQL會重置新的進程ID繼續運行 #停止 MySQL 可以使用服務自身命令 service mysqld stop 或執行kill、killall、pkill等不發送進程信號 #!!!需要關注的內容!!!# killall -9 ${SERVICE_NAME} eval $SERVICE_CMD #!!!需要關注的內容!!!# #else #echo `getDatetime` "found 1, ${SERVICE_NAME} is alive" >> ${MONITOR_LOG} fi }
#根據 服務名稱 守護方案
進程名稱與服務器名稱不一致,使用 `kill pid` 結束進程
#此方法適合java程序
#根據 服務名稱 守護, 使用 `kill pid` 結束進程 #此方法適合java程序 function processByNameKillByPid(){ #root 5593 1 0 13:06 ? 00:00:08 /usr/local/redis/redis-5.0.13/bin/redis-server 0.0.0.0:7379 [cluster] #指定 -w 選項全字匹配,避免多個名稱(7379,73791)同時匹配,造成誤殺 NUM=`ps -ef | grep -w ${SERVICE_NAME} | grep -v grep | wc -l` #進程數少于1,啟動進程 if [ ${NUM} -lt 1 ];then echo `getDatetime` "found 0, start ${SERVICE_NAME}" >> ${MONITOR_LOG} eval $SERVICE_CMD #大于1,殺掉所有進程,重啟 elif [ ${NUM} -gt 1 ];then echo `getDatetime` "more than 1 found, killall and start ${SERVICE_NAME}" >> ${MONITOR_LOG} pids=`ps -ef | grep -w ${SERVICE_NAME} | grep -v grep | awk '{print $2}'` for pid in ${pids} do #此處可以根據實際服務決定是否發送相關進程信號,如kill -9強制停止進程 #默認會通知進程優雅關閉,不會強制停止進程 #有些服務如 MySQL 自身存在守護程序,監聽進程信號,此時執行kill、killall、pkill等如果發送信號,MySQL會重置新的進程ID繼續運行 #停止 MySQL 可以使用服務自身命令 service mysqld stop 或執行kill、killall、pkill等不發送進程信號 #!!!需要關注的內容!!!# echo "kill ${pid}" >> ${MONITOR_LOG} kill -9 ${pid} #!!!需要關注的內容!!!# done #!!!需要關注的內容!!!# #如果停止進程時沒有發送強制停止信號,則此處需要延遲一定時間(單位:秒),待進程優雅退出后才能啟動服務,否則服務啟動失敗(絕大部分服務不允許重復啟動) #sleep 5 #!!!需要關注的內容!!!# #啟動服務 eval $SERVICE_CMD #else #echo `getDatetime` "found 1, ${SERVICE_NAME} is alive" >> ${MONITOR_LOG} fi }
特別關注:特殊情況
請根據要守護的應用服務組件的實際特性,合理調整守護腳本,適當增加必要的操作,主要為SERVICE_CMD內容,比如先cd到特定目錄,先創建特定目錄,先刪除特定文件,等。
以kafka、kafka-manager為例(詳細內容可參考教程kafka-manager安裝)
- kafka需要配置特定參數才能被kafka-manager監控,所以啟動前需要配置相關環境變量
- kafka-manager重啟前需要刪除PID文件
特別關注:關于進程定位
不論使用端口守護 還是 進程名稱守護,一定要確認唯一性,即通過指定的條件能精準定位到具體進程
如:java程序的進程名為 java,而不是jar包名,所以通過 killall 進程名 是無法精確定位進程的,即如果執行 killall java,則會將其他java進程退出
- 建議1:使用端口號守護方案
- 建議2:進程定位使用進程名,但停止進程使用 kill pid,不能使用killall 進程名 或 pkill 進程名
[root@localhost ~]# ps -ef | grep V2XApiServer.jar root 1754 1 0 3月09 ? 00:15:26 java -jar V2XApiServer.jar [root@localhost ~]# [root@localhost ~]# killall V2XApiServer.jar V2XApiServer.jar: no process found [root@localhost ~]# pgrep -l java 1707 java 1753 java 1754 java 1755 java 1756 java 1757 java
附錄:redis守護步驟
1.創建統一數據目錄
存放守護程序 與 守護程序執行日志
[root@localhost ~]# mkdir -p /usr/local/daemonProcess
2.編輯守護程序腳本
需要關注:不同守護方案需要關注不同信息
2.1 通過端口守護
redis服務啟動后,默認會監聽2個主要端口,可以配置任意一個
- 6379,redis對外提供服務的端口,可以在配置文件中指定
- 16379,redis集群各節點通信的端口,在配置的對外端口基礎上 +10000
建議使用 netstat -lntup | grep -w ${SERVICE_PORT} | wc -l
[root@localhost ~]# netstat -lntup | grep 7379 tcp 0 0 0.0.0.0:7379 0.0.0.0:* LISTEN 1057/redis-server 0 tcp 0 0 0.0.0.0:17379 0.0.0.0:* LISTEN 1057/redis-server 0 [root@localhost ~]# [root@localhost ~]# netstat -lntup | grep -w 7379 tcp 0 0 0.0.0.0:7379 0.0.0.0:* LISTEN 1057/redis-server 0
2.2 通過進程名守護
redis服務啟動后,進程名為 redis-server
[root@localhost ~]# ps -ef | grep -v grep | grep redis root 1057 1 0 3月15 ? 00:05:05 /usr/local/redis/redis-5.0.13/bin/redis-server 0.0.0.0:7379 [cluster]
2.3 完整守護程序腳本建議 daemonProcessRedis.sh
#! /bin/bash #守護由linux crontab調度,本程序不負責任務調度(crontab穩定性高) #本腳本支持根據 服務名稱 或 服務端口 守護,請根據守護的組件特性調用對應方法 #crontab不會自動加載全局環境變量,此處需要主動加載環境變量 source /etc/profile #!!!需要關注的內容!!!# SERVICE_NAME=redis-server SERVICE_PORT=7379 #kafka命令中JMX_PORT=9999為賦值語句,而在執行時默認會被識別為命令語句,所以會提示異常:JMX_PORT=9999: 未找到命令 #所以在執行復雜命令時(賦值、一行多條命令等),使用eval語法: eval ${SERVICE_CMD} SERVICE_CMD="/usr/local/redis/bin/redis-server /usr/local/redis/conf/redis.conf" MONITOR_LOG="/usr/local/daemonProcess/daemonProcessRedis.log" #!!!需要關注的內容!!!# #格式化日期時間函數 function getDatetime(){ local cur=`date "+%Y-%m-%d %H:%M:%S"` echo ${cur} } #根據 服務端口 守護 #此方法適合對外提供服務的程序,監聽端口只能被唯一程序占用 function processByPort(){ #tcp 0 0 0.0.0.0:7379 0.0.0.0:* LISTEN 5593/redis-server 0 NUM=`netstat -lntup | grep -w ${SERVICE_PORT} | wc -l` #進程數少于1,啟動進程 if [ ${NUM} -lt 1 ];then echo `getDatetime` "found 0, start ${SERVICE_NAME}" >> ${MONITOR_LOG} eval $SERVICE_CMD #大于1,殺掉所有進程,重啟 elif [ ${NUM} -gt 1 ];then echo `getDatetime` "more than 1 found, killall and start ${SERVICE_NAME}" >> ${MONITOR_LOG} pids=`netstat -lntup | grep -w ${SERVICE_PORT} | awk -F '[ /]+' '{print $7}'` for pid in ${pids} do #此處可以根據實際服務決定是否發送相關進程信號,如kill -9強制停止進程 #默認會通知進程優雅關閉,不會強制停止進程 #有些服務如 MySQL 自身存在守護程序,監聽進程信號,此時執行kill、killall、pkill等如果發送信號,MySQL會重置新的進程ID繼續運行 #停止 MySQL 可以使用服務自身命令 service mysqld stop 或執行kill、killall、pkill等不發送進程信號 #!!!需要關注的內容!!!# kill -9 ${pid} #!!!需要關注的內容!!!# done #!!!需要關注的內容!!!# #如果停止進程時沒有發送強制停止信號,則此處需要延遲一定時間(單位:秒),待進程優雅退出后才能啟動服務,否則服務啟動失敗(絕大部分服務不允許重復啟動) #sleep 5 #!!!需要關注的內容!!!# #啟動服務 eval $SERVICE_CMD #else # echo `getDatetime` "found 1, ${SERVICE_NAME} is alive" >> ${MONITOR_LOG} fi } #!!!需要關注的內容!!!# processByPort #!!!需要關注的內容!!!#
3.設置守護程序腳本可執行權限
[root@localhost ~]# cd /usr/local/daemonProcess/ [root@localhost daemonProcess]# chmod +x daemonProcessRedis.sh [root@localhost daemonProcess]# ll 總用量 12 -rwxr-xr-x. 1 root root 2087 3月 16 18:16 daemonProcessRedis.sh
4.配置定時任務
[root@localhost daemonProcess]# crontab -e # Example of job definition: # .---------------- minute (0 - 59) # | .------------- hour (0 - 23) # | | .---------- day of month (1 - 31) # | | | .------- month (1 - 12) OR jan,feb,mar,apr ... # | | | | .---- day of week (0 - 6) (Sunday=0 or 7) OR sun,mon,tue,wed,thu,fri,sat # | | | | | # * * * * * command to be executed #開機啟動 @reboot /usr/local/daemonProcess/daemonProcessRdis.sh #守護 * * * * * /usr/local/daemonProcess/daemonProcessRedis.sh
5.測試
停止redis進程,模擬服務故障,查看守護程序執行日志 daemonProcessRedis.log,此處略
浙公網安備 33010602011771號