问题
- 综合客户反馈的现场情况,服务前一天可以使用正常,第二天早晨发现报错; 初步怀疑是晚上什么操作导致的,大概是定时任务
- 查看服务日志,综合几天日志,发现每晚都会执行一个日志清除的定时任务
scheduledtask.CleanLogJob
。
{"log":"\u001b[2m2022-10-30 00:00:00.001\u001b[0;39m \u001b[32m INFO\u001b[0;39m \u001b[35m1\u001b[0;39m \u001b[2m---\u001b[0;39m \u001b[2m[ne-scheduling-1]\u001b[0;39m \u001b[36mc.newatc.api.scheduledtask.CleanLogJob \u001b[0;39m \u001b[2m:\u001b[0;39m ----开启定时清理日志文件----2022-10-29T16:00:00.001343Z\n","stream":"stdout","time":"2022-10-29T16:00:00.002200283Z"}
{"log":"\n","stream":"stderr","time":"2022-10-29T16:00:32.158196254Z"}
{"log":"Exception: java.lang.OutOfMemoryError thrown from the UncaughtExceptionHandler in thread \"Hikari housekeeper\"\n","stream":"stderr","time":"2022-10-29T16:00:32.158216755Z"}
{"log":"\u001b[2m2022-10-30 00:01:22.799\u001b[0;39m \u001b[31mERROR\u001b[0;39m \u001b[35m1\u001b[0;39m \u001b[2m---\u001b[0;39m \u001b[2m[ne-scheduling-1]\u001b[0;39m \u001b[36mo.s.s.s.TaskUtils$LoggingErrorHandler \u001b[0;39m \u001b[2m:\u001b[0;39m Unexpected error occurred in scheduled task\n","stream":"stdout","time":"2022-10-29T16:01:22.800399506Z"}
{"log":"\n","stream":"stdout","time":"2022-10-29T16:01:22.800414807Z"}
{"log":"java.lang.OutOfMemoryError: Java heap space\n","stream":"stdout","time":"2022-10-29T16:01:22.800417418Z"}
{"log":"\n","stream":"stdout","time":"2022-10-29T16:01:22.800419609Z"}
- 定时任代码是先根据当前日期查询7天前的所有数据,再全部删除。当某一天数据量特别大,无法全部加载到内存时,就会内存溢出,程序就会假死
/*** 每天零点定时清理日志*/@Scheduled(cron = "0 0 0 * * ?")public void deleteLog(){log.info("----开启定时清理日志文件----{}", Instant.now());try{SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");Calendar c = Calendar.getInstance();//过去七天c.setTime(new Date());c.add(Calendar.DATE, -7);String day = format.format(c.getTime());List opLogList = findOpLogByDayNum(day);opLogRepository.deleteAll(opLogList);List signalAlarmList = findSignalAlarmByDayNum(day);signalAlarmRepository.deleteAll(signalAlarmList);}catch (Exception e){log.error("deleteLog error..", e);}}
- 定时任务执行后,内存溢出
java.lang.OutOfMemoryError: Java heap space
。查看服务的jvm配置,只分配了 512M 内存,对于大量数据读入内存,确实不够用 - 查看代码提交记录,综合更新部署时间和问题出现时间,基本确定是此问题
- 让运维人员协助查询了数据库,发现每天新增10万条信号机报警日志,仍有13天(100多万条)的数据存在,确实是删除日志时内存溢出失败
分析
- 数据量问题确实是到客户现场,有实际这么多设备和用户才暴露出来的,公司测试无法进行此种场景的压力测试,只能把这款产品的第一个用户作为小白鼠了
- SpringBoot服务因异常宕机或假死时,不再提供服务,会从注册中心掉线,并且整个应用就不可用了
- 如果只是某些菜单功能不可用,对于客户来说还可以接受,毕竟试运行阶段,有点小问题客户也能理解
- 如果是整个服务宕机,界面打不开,客户就会对系统的稳定性产生怀疑,印象会不太好
- 或者说,某些导致宕机的问题,如果及时处理了,客户就暂时发现不了这个问题,就能减少对客户的负面印象
处理
- 针对于此,写了个shell脚本,监控SpringBoot服务暴露的端点,服务不可用时,进行重启操作
- 为了防止网络波动造成的影响,重试三次请求,都得不到健康的结果,才认为不可用
- 具体脚本如下:
#!/bin/bash# 检查服务是否可用,判断服务状态
function checkHealth() {currTime=`date +"%Y-%m-%d %H:%M:%S"`echo "$currTime $@"; # 以列表的方式一次性打印所有的参数ConsulResult=$(curl -s --connect-timeout 500 --max-time 2 http://$1/actuator/health )target="UP"local health=1if [[ $ConsulResult =~ $target ]]thenecho "$2结果健康";elsesleep 1s;ConsulResult=$(curl -s --connect-timeout 500 --max-time 2 http://$1/actuator/health )if [[ $ConsulResult =~ $target ]]thenecho "$2结果健康";elsesleep 1s;ConsulResult=$(curl -s --connect-timeout 500 --max-time 2 http://$1/actuator/health )if [[ $ConsulResult =~ $target ]]thenecho "$2结果健康";elsehealth=0;echo "$2 结果不健康";fififireturn $health;
}# 检查服务健康状态,并处理异常
function dealCheckResult() {ipport=$1name=$2echo "start $name ======================="checkHealth $ipport $namehealth=$?if [[ $health -eq 1 ]]thenecho "$name 检查完毕,无问题!"elseecho "重启 $name 预计2分钟"# 重启服务docker stop $namesleep 5sdocker start $namesleep 120s# 重新检查一次是否启动成功,不成功则再休眠60scheckHealth $ipport $namehealth=$?currTime=`date +"%Y-%m-%d %H:%M:%S"`if [[ $health -eq 1 ]];thenecho "$currTime $name 服务已重启完成"elseecho "$currTime $name 服务尚未重启完成,再休眠60s等待"sleep 60sfifiecho "end $name 所有处理完毕==========="
}# 获取系统启动时间
uptime=$(cat /proc/uptime)
echo "uptime: $uptime"
uptimeArr=(${uptime//./ })
startTime=${uptimeArr[0]}
echo $startTime
a=`expr $startTime + 1`
b=1800
# 系统启动30分钟内不执行
if [[ "$a" -lt "$b" ]];
thenecho "系统启动30分钟内不执行"exit
fi
echo "启动时间为$startTime秒,正常执行脚本"echo "先关闭定时任务"
/sbin/service crond stopipport1=172.16.1.122:8181
name1=datacenter
dealCheckResult $ipport1 $name1ipport2=172.16.1.122:8282
name2=unit
dealCheckResult $ipport2 $name2ipport3=172.16.1.122:8383
name3=core
dealCheckResult $ipport3 $name3echo "重新启动定时任务"
/sbin/service crond start