Spring的@PreDestroy注解不执行引发事故
Spring的@PreDestroy注解引发事故功能说明事故原由新的改变功能快捷键合理的创建标题,有助于目录的生成如何改变文本的样式插入链接与图片如何插入一段漂亮的代码片生成一个适合你的列表创建一个表格设定内容居中、居左、居右SmartyPants创建一个自定义列表如何创建一个注脚注释也是必不可少的KaTeX数学公式新的甘特图功能,丰富你的文章UML 图表FLowchart流程图导出与导入导出导入
事故原由
由于公司发号器通过雪花算法来生成,而雪花算法由:时间缀+机器码mid+自增序列组成,为了保证每个实例mid唯一性,机器码池midPool是保存在redis中的,每次服务后生成一个唯一mid丢到midPool里,服务下线时的清理掉此mid。项目代码通过@PreDestroy注解来处理服务下线后的清理工作,但是实际生产服务重启时部分实例并未执行清理,midPool满了,导致新启动的实例无法正确的初始化mid,使生产出现事故。
midPool结构:
池结构很简单,key永久有效,大小1000。
java清理
@PreDestroy
public void exit(){
// 清理midPool池中当前实例的的mid
redisTemplate.del("midPool"+ip):
log.info("我退出了");
}
容器启动
CMD ["/home/start.sh"]
/home/start.sh
#!/bin/bash
java -jar app.jar
容器停止
- 执行 kill -15 pid
- 然后等待 30s (默认时间,也可以修改成其他时间)
- 如果30s之内服务还没停止,将执行 kill -9
原因定位
以上代码一切看起来都没有问题,并且生产环境只有部分实例未执行清理的代码块。通过网络文章与运维团队测试定位到问题,容器的启动方式存在问题,start脚本是主进程,java为子进程。kill -15命令时,start主进程收到SIGTERM信号,但是并未将信号量传递到java子进程,所以java程序未能触发清理代码。
解决方案
既然知道了原因,那么就好解决了,有几种方案:
- 将SIGTERM信号传送给子进程
#!/bin/bash
java -jar app.jar &
pid="$!"
_kill() {
kill $pid
wait $pid
exit 143
}
trap _kill SIGTERM
wait
步骤是把java程序后台启动以获得它的PID,最后一行加入wait命令防止shell退出,trap命令捕捉SIGTERM信号并执行一个命令
- 以java作为容器的主进程方式启动
CMD ["java", "-jar", "app.jar"]
- 对mid池进行改造,使可以redis中的mid可以过期时间,同时存活的实例定期的续约
- zookeeper临时节点:临时节点的生命周期和客户端会话绑定在一起,客户端会话失效,则这个节点就会被自动清除。
最终我们采用方案2+方案3的方式处理的,不能单靠退出清理的方式,避免可能在极端(oom、机器断电等)的情况下还是无法执行到清理代码。
参考文章
感谢大佬的文章帮忙定位到了问题
https://www.orchome.com/9962
更多推荐
所有评论(0)