前言
golang实现守护进程,包含功能:
守护进程只创建一次
平滑创建业务进程
业务进程挂起,守护进程能监听,并重启新启业务进程
守护进程退出,也能保证业务进程退出
业务进程≈子进程
不影响业务进程逻辑
以Linux平台为主,其他平台暂时没有实施条件
分析
目前实现方式是golang+shell脚本形式,所以golang创建进程的方式放在下一篇进行阐述
实现
编写gt-daemon.sh脚本,作为守护进程
#!/bin/bash
# dos2unix gt-daemon.sh
# sh gt-daemon.sh -t start -c param1 -d param2 -e param3
BASE_PATH=$(
cd $(dirname $0)
pwd
)
LOG_FILE=$BASE_PATH/gt-daemon.log
PID_FILE=$BASE_APTH/gt-daemon.pid
USAGE="Usage: gt-daemon.sh [start|stop|status|help]"
# project-name=go_start
isE=false
count=0
scount=0
function startSubProcess() {
while true; do
ts=$(date "+%Y-%m-%d-%H-%M-%S")
procnum=$(ps -ef | grep "go_start" | grep java | grep -v grep | wc -l)
if [ $procnum -eq 0 ]; then
nohup $1/go_start -c $1/$PARAM1 -d $PARAM2 -e $PARAM3 >/dev/null 2>&1 &
isE=true
scount++
echo $ts: start! flag=$isE basepath=$1 param1=$PARAM1 param2=$PARAM2 param3=$PARAM3 >>$LOG_FILE
fi
if [[ $scount -ge 3 ]]; then
break
fi
sleep 5
done
}
function stopSubProcess() {
while [ true ]; do
if [ $isE ]; then
proc=$(ps -ef | grep "go_start" | grep go_start | grep -v grep | wc -l)
ts2=$(date "+%Y-%m-%d-%H-%M-%S")
if [[ $proc -ne 0 ]]; then
tmpproc=$(ps -ef | grep "go_start" | grep -v grep | awk '{print $2}')
echo "Stop subProcess, pid is ${tmpproc}"
kill -9 $tmpproc
echo $ts2: kill subProcess >>$LOG_FILE
isE=false
break
fi
else
if [ $count -ge 3 ]; then
echo $ts2: subProcess is killed! >>$LOG_FILE
break
fi
count++
sleep 1
fi
done
}
function stopPrarentProcess() {
if [ ! -f ${PID_FILE} ]; then
echo "gt-daemon.sh is not running!"
else
pid=$(cat ${PID_FILE})
procnum=$(ps -ef | grep "gt-daemon.sh" | grep -v grep | wc -l)
if [ $procnum -ge 1 ]; then
echo "Stop process, pid is ${pid}"
kill -9 $pid
if [[ $? -eq 0 ]]; then
echo "Remove pid file"
rm -rf ${PID_FILE}
else
echo "Stop process is failed!"
fi
else
echo "gt-daemon.sh process is not found, remove pid file"
rm -rf ${PID_FILE}
fi
fi
}
function stop() {
if [ ! -f ${PID_FILE} ]; then
echo "gt-daemon.sh is not running"
else
stopSubProcess
stopPrarentProcess
fi
}
function status() {
if [ ! -f ${PID_FILE} ]; then
echo "gt-daemon.sh is not running!"
else
pid=$(cat ${PID_FILE})
echo "gt-daemon.sh is running, pid is ${pid}"
fi
}
case $1 in
"start")
if [ $# -lt 2 ]; then
echo "参数小于8个,退出!"
exit 1
fi
if [ -f ${PID_FILE} ]; then
pid=$(cat ${PID_FILE})
procnum=$(ps -ef | grep "gt-daemon.sh" | grep -v grep | wc -l)
if [ $procnum -ge 3 ]; then
echo "gt-daemon.sh is already running, pid is ${pid}"
exit 1
fi
fi
PARAM1=$4
PARAM2=$6
PARAM3=$8
startSubProcess $BASE_APTH &
pid=$!
echo $pid >$PID_FILE
echo "Start success, pid is ${pid}"
;;
"stop")
stop
;;
"status")
status
;;
"help")
echo ${USAGE}
;;
*)
echo ${USAGE}
;;
esac
main()方法中启动守护进程
func main() {
// 全局变量设置...
// 判断是否启动守护进程
basePath, _ := os.Getwd()
baseDir := filepath.Dir(basePath)
fmt.Println(fmt.Sprintf("basePath is %s and baseDir is %s", basePath, baseDir))
// 获取go_start二进制文件所在路径,
// gt-daemon.sh与go_start二进制文件同级,
// 判断该路径下是否有gt-daemon.sh
// 如果有,则启动(由业务进程启动),然后由守护进程监听业务进程
// 如果没有,则不启动守护进程,直接启动业务进程
gwpath := basePath + "gt-daemon.sh"
fmt.Println(fmt.Sprintf("gt-daemon.sh path is %s", gwpath))
exists, _ := PathExists(gwpath)
fmt.Println(fmt.Sprintf("文件路径存在: %v\n", exists))
if exists {
res := execShell("ps -ef | grep 'gt-daemon.sh' | grep -v grep | wc -l")
procnum, err2 := strconv.Atoi(res)
if err2 != nil {
fmt.Println(fmt.Sprintf("gt-daemon.sh result exchange err: %s", err2.Error()))
} else {
fmt.Println(fmt.Sprintf("gt-daemon.sh process num is %d\n", procnum))
if procnum == 0 {
cmd := fmt.Sprintf("sh %s -t start -c %s -chost %s -cpsw %s >/dev/null 2>&1 &", gwpath, argv.config, argv.chHost, argv.chPassword)
fmt.Println("command to execute : " + cmd)
execShell(cmd)
}
}
}
// 其他业务进程相关代码...
}
相关工具方法
func PathExists(path string) (bool, error) {
_, err := os.Stat(path)
if err == nil {
return true, nil
}
if os.IsNotExist(err) {
return false, nil
}
return false, err
}
func execShell(command string) string {
cmd := exec.Command("/bin/bash", "-c", command)
bytes, err := cmd.Output()
if err != nil {
fmt.Println(fmt.Sprintf("execute [%s] error, %s", command, err.Error()))
return ""
}
resp := strings.Replace(string(bytes), "\n", "", -1)
return resp
}
启动方式
gt-daemon.sh
go_start
两个文件在同一个目录下,比如linux环境的/tmp/
启动时:sh gt-daemon.sh -t start -c param1 -d param2 -e param3
验证方式
首先查看守护进程和业务进程是否启动,查看方式
ps -ef | grep gt-daemon
ps -ef | grep go_start
杀掉子进程,查看是否新起子进程
kill 子进程PID
ps -ef | grep go_start -- 不存在go_start进程,说明kill成功
ps -ef | grep go_start -- 一会再执行,发现存在go_start进程,说明新起go_start进程成功
结论
优点:
实现简单,守护进程独立代码之外
缺点:
需要单独维护守护进程脚本
综上:考虑纯代码形式再实现一版守护进程