背景
我们的服务专为金融领域的客户量身定制,分为多个独立而协同的模块。在这些模块中,Biz模块扮演着枢纽的角色,它不仅承担着对外提供接口的重要职责,而且还负责建立和维护与Mysql数据库的关键连接。其他所有模块都依赖于Biz模块。
为了实现自动化部署,我们编写了一套启动脚本。其中,validStart.sh脚本被设计用于判断服务是否成功启动。在第一版中,我们通过检查服务进程是否存在来判断服务是否启动完成,具体方法是使用ps命令加上pid参数来查询进程信息。
在客户实际使用中,我们发现了一些问题:
-
- 服务实际上并未启动完成时,系统却提示服务已经成功启动。
-
- 在多节点部署的情况下,由于服务尚未启动完成,其他服务就开始启动了,导致其他服务的请求被转移到了一个正在启动的节点中。
这些问题可能会影响客户的使用体验和系统的可靠性。因此,我们需要进一步优化validStart.sh脚本,以确保服务启动的准确性和稳定性。
实现方式
第一版
在第一版中我们在启动脚本中通过$!命令将pid记录到pidFile文件中,在validStart.sh脚本中轮训多次判断pid是否存在,用于判断服务是否启动成功。
源码如下:
bash
#!/bin/bash
pidFile="biz-server.pid"
pidTmpFile="biz-server.tmp.pid"
function validate(){
if [ "${3}" == "false" ]; then
return 1
fi
pidFile=$1
totalTime=$2
while [[ $totalTime -gt 0 ]]; do
if [ -f "$pidFile" ]; then
sleep 5
let totalTime-=5
pid=`head -n +1 $pidFile`
check_start=`ps axu | grep java | grep -w $pid |grep -v grep| awk '{printf $2}'`
if [[ -n "$check_start" && "$pid" == "$check_start" ]]; then
return 0
else
return 1
fi
fi
let totalTime-=5
sleep 5
done
return 1
}
sumTime=120
validate ${pidFile} $sumTime
if [ "$?" == "0" ];then
echo "0"
else
#启动失败后,需要看一下是否有临时pid文件,需要把该残留的进程也删除掉
if [ -f "$pidTmpFile" ];then
PID=$(cat ${pidTmpFile})
alias rm='rm'
kill -9 $PID 2>&1
rm -rf $pidTmpFile 2>&1
fi
echo "start failed." 1>&2
fi
第二版本
在第二版本中,我们可以通过发送HTTP请求到服务的health接口来判断服务是否完全启动完成。具体来说,我们可以使用curl命令发送一个GET请求到health接口的URL,并检查响应内容来确定服务是否已经启动成功。
源码如下:
bash
#!/bin/bash
# 声明接口地址
url=http://127.0.0.1:7082/health
# 添加服务状态检查方法
checkHealth(){
totalTime=$1
while [[ $totalTime -gt 0 ]]; do
sleep 5
result=$(curl -s ${url} | grep "\\\"status\\\": \\\"UP\\\"")
echo $result
if [ "$result" != "" ]; then
return "1"
else
return "0"
fi
let totalTime-=5
sleep 5
done
return 1
}
sumTime=120
checkHealth $sumTime
if [ "$?" == "0" ];then
echo "0"
else
#启动失败后,需要看一下是否有临时pid文件,需要把该残留的进程也删除掉
if [ -f "$pidTmpFile" ];then
PID=$(cat ${pidTmpFile})
alias rm='rm'
kill -9 $PID 2>&1
rm -rf $pidTmpFile 2>&1
fi
echo "start failed." 1>&2
fi
第三版本
在第二个版本中,我们使用health接口来判断服务是否启动完成。然而,在实际使用中,我们发现即使health接口能够调用成功,服务并不一定能够正常提供服务,这主要是由于组件尚未注册好的原因。
为了解决这个问题,我们需要对health接口进行改造添加自定义的监控项进去。添加方法参考Spring Boot Actuator自定义监控集成到/health接口实战
查看Spring源码知道Spring提供了一个SpringApplicationRunListener接口用于监控启动的各个阶段。
以上方法分别是:
- contextLoaded:在加载应用程序上下文后调用,但在加载之前调用刷新。
- contextPrepared:创建并准备ApplicationContext后调用,但在加载源之前。
- environmentPrepared:在准备好环境后,但在创建ApplicationContext之前调用。
- failed:在运行应用程序时发生故障时调用。
- running:在刷新应用程序上下文并且调用了所有CommandLineRunner和ApplicationRunner之后,在运行方法完成之前立即调用。
- started:上下文已刷新,应用程序已启动,但尚未调用CommandLineRunners和ApplicationRunners。
- starting:当run方法首次启动时立即调用。
而SpringBoot默认实现了一个EventPublishingRunListener监听器,并在running阶段会发布ApplicationReadyEvent事件。所以我们只需要监听这个事件就可以判断服务是否完全启动完成。
创建ApplicationReadyListener类继承ApplicationListener监听ApplicationReadyEvent事件,并定义一个全局变量readFlag用于记录服务状态。
源码如下:
java
@Component
public class JresApplicationReadyListener implements ApplicationListener<ApplicationReadyEvent> {
private static boolean readFlag=false;
/**
* 获取获取初始化状态
* @return
*/
public static boolean getReadyStatus(){
return readFlag;
}
@Override
public void onApplicationEvent(ApplicationReadyEvent event) {
readFlag=true;
}
}
然后自定义一个HealthCheck判断服务状态。