文章目录

  • 前言
  • 一、程序设计思想
  • 二、程序实现
  • 1.实现代码
  • 总结



前言

部署在Linux服务器上的一个应用跑的时间长了以后有可能会出现进程假死的情况,导致程序不能正常提供服务,通过观察发现当应用假死后,应用日志不会再更新,所以针对该应用本人实现了通过日志监控进而监控进程的目的,当两个检查点之间发生应用假死,则监控进程会重启被监控进程。


一、程序设计思想

功能实现的一个核心概念就是checkPoint,本脚本实现是轮询读取应用日志的最大行数,作为新的checkPoint与上一次的checkPoint(checkPoint.chk文件中记录的数值)作比较,若两次轮询(600秒轮询一次)的checkPoint的值一致,则说明发生假死(被监控进程每100秒会有一轮日志输出),监控进程会杀死假死的被监控进程,并重启被监控进程。若两次轮询的checkPoint值不一致,则说明被监控进程运行正常,监控进程无需做其他操作。

二、程序实现

1.实现代码

脚本名称cmnetMonitor.sh,实际使用可根据需求自定义:

#!/bin/bash
# 脚本用于监控告警发送到故障系统的进程,本例关键字:jzpd2mq_cmnet
# author@charlie
# date@2021-06-04
# 轮询的方式监控应用日志中的最大行数,两次检查点间的最大行数一致则说明程序未运行或吊死,则进行重启,否则不做处理

function isOnProcess(){
  # $1:进程关键字
  echo [`date +'%Y-%m-%d %H:%M:%S'`] "INTO FUNCTION--> isOnProcess" >> monitorLog${operateDate}.log
  processId=`ps -ef | grep ${1} | awk '{if($(NF-1) != "grep") print $2}'`
  if [[ ! $processId ]]
    then
    echo [`date +'%Y-%m-%d %H:%M:%S'`] process ${1} is not running  >> monitorLog${operateDate}.log
    return 1
  else
    echo [`date +'%Y-%m-%d %H:%M:%S'`] process ${1} is running  >> monitorLog${operateDate}.log
    return 0
  fi
}

function rebootProcess(){
   # $1:全路径进程名;$2:程序家目录;$3:是否主动要求启动
   echo "INTO FUNCTION--> rebootProcess" >> monitorLog${operateDate}.log
   oldPids=`ps -ef | grep jzpd2mq_cmnet | awk '{if($(NF-1) != "grep") print $2}'`
   pidCnt=`ps -ef | grep jzpd2mq_cmnet | awk '{if($(NF-1) != "grep") print $2}' | wc -l`
   if [  $pidCnt -eq 0 ]
      then  
        echo [`date +'%Y-%m-%d %H:%M:%S'`] $1 is not running!Now to start it... >> monitorLog${operateDate}.log
        nohup $1 &
        sleep 3
        newPids=`ps -ef | grep jzpd2mq_cmnet | awk '{if($(NF-1) != "grep") print $2}'`
        if [[ ! $newPids ]]
          then
          echo [`date +'%Y-%m-%d %H:%M:%S'`] Start Failed... >> monitorLog${operateDate}.log
          return 1
          else
          echo [`date +'%Y-%m-%d %H:%M:%S'`] Start Over,New Pid is:$newPids >> monitorLog${operateDate}.log
          return 0
        fi
    fi
      
    if [[ ! ${3} ]]
         then
         echo [`date +'%Y-%m-%d %H:%M:%S'`] you do not want to start it... >> monitorLog${operateDate}.log
    else
        echo [`date +'%Y-%m-%d %H:%M:%S'`] $1 Old Pid is:${oldPids} >> monitorLog${operateDate}.log
        echo ${oldPids} | xargs kill -9
        sleep 3
        # 开始重启
        nohup $1 &
        sleep 3
        newPids=`ps -ef | grep jzpd2mq_cmnet | awk '{if($(NF-1) != "grep") print $2}'`
        newCnt=`ps -ef | grep jzpd2mq_cmnet | awk '{if($(NF-1) != "grep") print $2}' | wc -l`
        if [ $newCnt -eq 0 ]
          then
          echo [`date +'%Y-%m-%d %H:%M:%S'`] Start Failed... >> monitorLog${operateDate}.log
          return 1
          else
          echo [`date +'%Y-%m-%d %H:%M:%S'`] Start Over,New Pid is:$newPids >> monitorLog${operateDate}.log
          return 0
        fi
     fi
     
}

function checkWithLog(){
  # $1:日志名; $2:程序全路径名 $3:程序家目录 $4:日志日期
  if [[ ! -f monitorLog${4}.log ]]
    then
    echo [`date +'%Y-%m-%d %H:%M:%S'`] "INIT LOG NOW" >> monitorLog${4}.log
  fi 
  echo -e "\n[`date +'%Y-%m-%d %H:%M:%S'`]" "INTO FUNCTION--> checkWithLog:$#" >> monitorLog${4}.log
  if [[ ! -f checkPoint.chk ]]
    then
    echo `cat ${1} | wc -l` >> checkPoint.chk
  fi
  read lastChk < checkPoint.chk
  # 检查本次检查点与前次检查点是否一致
  nowChk=`cat ${1} | wc -l`
  # echo nowChk:$nowChk
  isOnProcess jzpd2mq_cmnet || rebootProcess ${2} ${3}
  if [[ "$nowChk" -eq "$lastChk" ]]
    then
    echo [`date +'%Y-%m-%d %H:%M:%S'`] Chkpoint is same,need to restart... >> monitorLog${4}.log
    rebootProcess ${2} ${3} yes
    echo ${nowChk} > checkPoint.chk  
    return 0
  else
    # 程序正常运行,无需重启
    echo [`date +'%Y-%m-%d %H:%M:%S'`] your progress is health,no need to restart >> monitorLog${4}.log  
  fi
  
}


# 主程序开始
PRO_DIR=/u03/Inspur/apps/jzpd2mq_cmnet/bin
LOG_DIR=${PRO_DIR}
# 轮询执行
while true
do
operateDate=`date '+%Y%m%d'`
checkWithLog ${LOG_DIR}/nohup.out  ${PRO_DIR}/startup.sh ${PRO_DIR} $operateDate
sleep 600
done

总结

本文实现是以监控应用日志的最大行数作为切入点展开,之前还实现过监控应用日志中的特定关键字作为判断依据的监控进程。所以进程监控的实现方式要根据具体的业务应用特点实现。
若对本文的代码有疑问或者更好的实现方式,欢迎留言讨论。