golang实现守护进程(1)

前言

golang实现守护进程,包含功能:

  1. 守护进程只创建一次

  1. 平滑创建业务进程

  1. 业务进程挂起,守护进程能监听,并重启新启业务进程

  1. 守护进程退出,也能保证业务进程退出

  1. 业务进程≈子进程

  1. 不影响业务进程逻辑

  1. 以Linux平台为主,其他平台暂时没有实施条件

分析

目前实现方式是golang+shell脚本形式,所以golang创建进程的方式放在下一篇进行阐述

实现

  1. 编写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
  1. 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

验证方式

  1. 首先查看守护进程和业务进程是否启动,查看方式

  • ps -ef | grep gt-daemon

  • ps -ef | grep go_start

  1. 杀掉子进程,查看是否新起子进程

  1. kill 子进程PID

  1. ps -ef | grep go_start -- 不存在go_start进程,说明kill成功

  1. ps -ef | grep go_start -- 一会再执行,发现存在go_start进程,说明新起go_start进程成功

结论

  • 优点:

实现简单,守护进程独立代码之外

  • 缺点:

需要单独维护守护进程脚本

综上:考虑纯代码形式再实现一版守护进程

你可能感兴趣的:(大数据,go)