0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

求求你们别再用kill -9了,这才是Spring Boot停机的正确方式!

jf_ro2CN3Fa 来源:CSDN 2023-05-15 14:56 次阅读


再谈为了提醒明知故犯(在一坑里迭倒两次不是不多见),由于业务系统中大量使用了spring Boot embedded tomcat的模式运行,在一些运维脚本中经常看到Linuxkill 指令,然而它的使用也有些讲究,要思考如何能做到优雅停机。

何为优雅关机

就是为确保应用关闭时,通知应用进程释放所占用的资源

  • 线程池,shutdown(不接受新任务等待处理完)还是shutdownNow(调用 Thread.interrupt进行中断)
  • socket 链接,比如:netty、mq
  • 告知注册中心快速下线(靠心跳机制客服早都跳起来了),比如:eureka
  • 清理临时文件,比如:poi
  • 各种堆内堆外内存释放

总之,进程强行终止会带来数据丢失或者终端无法恢复到正常状态,在分布式环境下还可能导致数据不一致的情况。

基于 Spring Boot + MyBatis Plus + Vue & Element 实现的后台管理系统 + 用户小程序,支持 RBAC 动态权限、多租户、数据权限、工作流、三方登录、支付、短信、商城等功能

  • 项目地址:https://github.com/YunaiV/ruoyi-vue-pro
  • 视频教程:https://doc.iocoder.cn/video/

kill指令

kill -9 pid 可以模拟了一次系统宕机,系统断电等极端情况,而kill -15 pid 则是等待应用关闭,执行阻塞操作,有时候也会出现无法关闭应用的情况(线上理想情况下,是bug就该寻根溯源)

#查看jvm进程pid
jps
#列出所有信号名称
kill-l


>基于SpringCloudAlibaba+Gateway+Nacos+RocketMQ+Vue&Element实现的后台管理系统+用户小程序,支持RBAC动态权限、多租户、数据权限、工作流、三方登录、支付、短信、商城等功能
>
>*项目地址://github.com/YunaiV/yudao-cloud>
>*视频教程//doc.iocoder.cn/video/>

#Windows下信号常量值
#简称全称数值
#INTSIGINT2Ctrl+C中断
#ILLSIGILL4非法指令
#FPESIGFPE8floatingpointexception(浮点异常)
#SEGVSIGSEGV11segmentviolation(段错误)
#TERMSIGTERM5Softwareterminationsignalfromkill(Kill发出的软件终止)
#BREAKSIGBREAK21Ctrl-Breaksequence(Ctrl+Break中断)
#ABRTSIGABRT22abnormalterminationtriggeredbyabortcall(Abort)

#linux信号常量值
#简称全称数值
#HUPSIGHUP1终端断线
#INTSIGINT2中断(同Ctrl+C)
#QUITSIGQUIT3退出(同Ctrl+)
#KILLSIGKILL9强制终止
#TERMSIGTERM15终止
#CONTSIGCONT18继续(与STOP相反,fg/bg命令)
#STOPSIGSTOP19暂停(同Ctrl+Z)
#....

#可以理解为操作系统从内核级别强行杀死某个进程
kill-9pid
#理解为发送一个通知,等待应用主动关闭
kill-15pid
#也支持信号常量值全称或简写(就是去掉SIG后)
kill-lKILL

思考:jvm是如何接受处理linux信号量的?

当然是在jvm启动时就加载了自定义SignalHandler,关闭jvm时触发对应的handle。

publicinterfaceSignalHandler{
SignalHandlerSIG_DFL=newNativeSignalHandler(0L);
SignalHandlerSIG_IGN=newNativeSignalHandler(1L);

voidhandle(Signalvar1);
}
classTerminator{
privatestaticSignalHandlerhandler=null;

Terminator(){
}
//jvm设置SignalHandler,在System.initializeSystemClass中触发
staticvoidsetup(){
if(handler==null){
SignalHandlervar0=newSignalHandler(){
publicvoidhandle(Signalvar1){
Shutdown.exit(var1.getNumber()+128);//调用Shutdown.exit
}
};
handler=var0;

try{
Signal.handle(newSignal("INT"),var0);//中断时
}catch(IllegalArgumentExceptionvar3){
;
}

try{
Signal.handle(newSignal("TERM"),var0);//终止时
}catch(IllegalArgumentExceptionvar2){
;
}

}
}
}

Runtime.addShutdownHook

在了解Shutdown.exit之前,先看Runtime.getRuntime().addShutdownHook(shutdownHook);则是为jvm中增加一个关闭的钩子,当jvm关闭的时候调用。

publicclassRuntime{
publicvoidaddShutdownHook(Threadhook){
SecurityManagersm=System.getSecurityManager();
if(sm!=null){
sm.checkPermission(newRuntimePermission("shutdownHooks"));
}
ApplicationShutdownHooks.add(hook);
}
}
classApplicationShutdownHooks{
/*Thesetofregisteredhooks*/
privatestaticIdentityHashMaphooks;
staticsynchronizedvoidadd(Threadhook){
if(hooks==null)
thrownewIllegalStateException("Shutdowninprogress");

if(hook.isAlive())
thrownewIllegalArgumentException("Hookalreadyrunning");

if(hooks.containsKey(hook))
thrownewIllegalArgumentException("Hookpreviouslyregistered");

hooks.put(hook,hook);
}
}
//它含数据结构和逻辑管理虚拟机关闭序列
classShutdown{
/*Shutdown系列状态*/
privatestaticfinalintRUNNING=0;
privatestaticfinalintHOOKS=1;
privatestaticfinalintFINALIZERS=2;
privatestaticintstate=RUNNING;
/*是否应该运行所以finalizers来exit?*/
privatestaticbooleanrunFinalizersOnExit=false;
//系统关闭钩子注册一个预定义的插槽.
//关闭钩子的列表如下:
//(0)Consolerestorehook
//(1)Applicationhooks
//(2)DeleteOnExithook
privatestaticfinalintMAX_SYSTEM_HOOKS=10;
privatestaticfinalRunnable[]hooks=newRunnable[MAX_SYSTEM_HOOKS];
//当前运行关闭钩子的钩子的索引
privatestaticintcurrentRunningHook=0;
/*前面的静态字段由这个锁保护*/
privatestaticclassLock{};
privatestaticObjectlock=newLock();

/*为nativehalt方法提供锁对象*/
privatestaticObjecthaltLock=newLock();

staticvoidadd(intslot,booleanregisterShutdownInProgress,Runnablehook){
synchronized(lock){
if(hooks[slot]!=null)
thrownewInternalError("Shutdownhookatslot"+slot+"alreadyregistered");

if(!registerShutdownInProgress){//执行shutdown过程中不添加hook
if(state>RUNNING)//如果已经在执行shutdown操作不能添加hook
thrownewIllegalStateException("Shutdowninprogress");
}else{//如果hooks已经执行完毕不能再添加hook。如果正在执行hooks时,添加的槽点小于当前执行的槽点位置也不能添加
if(state>HOOKS||(state==HOOKS&&slot<= currentRunningHook))
                    thrownewIllegalStateException("Shutdowninprogress");
}

hooks[slot]=hook;
}
}
/*执行所有注册的hooks
*/
privatestaticvoidrunHooks(){
for(inti=0;i< MAX_SYSTEM_HOOKS; i++) {
            try{
Runnablehook;
synchronized(lock){
//acquirethelocktomakesurethehookregisteredduring
//shutdownisvisiblehere.
currentRunningHook=i;
hook=hooks[i];
}
if(hook!=null)hook.run();
}catch(Throwablet){
if(tinstanceofThreadDeath){
ThreadDeathtd=(ThreadDeath)t;
throwtd;
}
}
}
}
/*关闭JVM的操作
*/
staticvoidhalt(intstatus){
synchronized(haltLock){
halt0(status);
}
}
//JNI方法
staticnativevoidhalt0(intstatus);
//shutdown的执行顺序:runHooks>runFinalizersOnExit
privatestaticvoidsequence(){
synchronized(lock){
/*Guardagainstthepossibilityofadaemonthreadinvokingexit
*afterDestroyJavaVMinitiatestheshutdownsequence
*/
if(state!=HOOKS)return;
}
runHooks();
booleanrfoe;
synchronized(lock){
state=FINALIZERS;
rfoe=runFinalizersOnExit;
}
if(rfoe)runAllFinalizers();
}
//Runtime.exit时执行,runHooks>runFinalizersOnExit>halt
staticvoidexit(intstatus){
booleanrunMoreFinalizers=false;
synchronized(lock){
if(status!=0)runFinalizersOnExit=false;
switch(state){
caseRUNNING:/*Initiateshutdown*/
state=HOOKS;
break;
caseHOOKS:/*Stallandhalt*/
break;
caseFINALIZERS:
if(status!=0){
/*Haltimmediatelyonnonzerostatus*/
halt(status);
}else{
/*Compatibilitywitholdbehavior:
*Runmorefinalizersandthenhalt
*/
runMoreFinalizers=runFinalizersOnExit;
}
break;
}
}
if(runMoreFinalizers){
runAllFinalizers();
halt(status);
}
synchronized(Shutdown.class){
/*Synchronizeontheclassobject,causinganyotherthread
*thatattemptstoinitiateshutdowntostallindefinitely
*/
sequence();
halt(status);
}
}
//shutdown操作,与exit不同的是不做halt操作(关闭JVM)
staticvoidshutdown(){
synchronized(lock){
switch(state){
caseRUNNING:/*Initiateshutdown*/
state=HOOKS;
break;
caseHOOKS:/*Stallandthenreturn*/
caseFINALIZERS:
break;
}
}
synchronized(Shutdown.class){
sequence();
}
}
}

spring 3.2.12

在spring中通过ContextClosedEvent事件来触发一些动作(可以拓展),主要通过LifecycleProcessor.onClose来做stopBeans。由此可见spring也基于jvm做了拓展。

publicabstractclassAbstractApplicationContextextendsDefaultResourceLoader{
publicvoidregisterShutdownHook(){
if(this.shutdownHook==null){
//Noshutdownhookregisteredyet.
this.shutdownHook=newThread(){
@Override
publicvoidrun(){
doClose();
}
};
Runtime.getRuntime().addShutdownHook(this.shutdownHook);
}
}
protectedvoiddoClose(){
booleanactuallyClose;
synchronized(this.activeMonitor){
actuallyClose=this.active&&!this.closed;
this.closed=true;
}

if(actuallyClose){
if(logger.isInfoEnabled()){
logger.info("Closing"+this);
}

LiveBeansView.unregisterApplicationContext(this);

try{
//发布应用内的关闭事件
publishEvent(newContextClosedEvent(this));
}
catch(Throwableex){
logger.warn("ExceptionthrownfromApplicationListenerhandlingContextClosedEvent",ex);
}

//停止所有的Lifecyclebeans.
try{
getLifecycleProcessor().onClose();
}
catch(Throwableex){
logger.warn("ExceptionthrownfromLifecycleProcessoroncontextclose",ex);
}

//销毁spring的BeanFactory可能会缓存单例的Bean.
destroyBeans();

//关闭当前应用上下文(BeanFactory)
closeBeanFactory();

//执行子类的关闭逻辑
onClose();

synchronized(this.activeMonitor){
this.active=false;
}
}
}
}
publicinterfaceLifecycleProcessorextendsLifecycle{
/**
*Notificationofcontextrefresh,e.g.forauto-startingcomponents.
*/
voidonRefresh();

/**
*Notificationofcontextclosephase,e.g.forauto-stoppingcomponents.
*/
voidonClose();
}

spring boot

到这里就进入重点了,spring boot中有spring-boot-starter-actuator 模块提供了一个 restful 接口,用于优雅停机。执行请求 curl -X POST http://127.0.0.1:8088/shutdown ,待关闭成功则返回提示。

注:线上环境该url需要设置权限,可配合 spring-security使用或在nginx中限制内网访问

#启用shutdown
endpoints.shutdown.enabled=true
#禁用密码验证
endpoints.shutdown.sensitive=false
#可统一指定所有endpoints的路径
management.context-path=/manage
#指定管理端口和IP
management.port=8088
management.address=127.0.0.1

#开启shutdown的安全验证(spring-security)
endpoints.shutdown.sensitive=true
#验证用户名
security.user.name=admin
#验证密码
security.user.password=secret
#角色
management.security.role=SUPERUSER

spring boot的shutdown原理也不复杂,其实还是通过调用AbstractApplicationContext.close实现的。

@ConfigurationProperties(
prefix="endpoints.shutdown"
)
publicclassShutdownMvcEndpointextendsEndpointMvcAdapter{
publicShutdownMvcEndpoint(ShutdownEndpointdelegate){
super(delegate);
}
//post请求
@PostMapping(
produces={"application/vnd.spring-boot.actuator.v1+json","application/json"}
)
@ResponseBody
publicObjectinvoke(){
return!this.getDelegate().isEnabled()?newResponseEntity(Collections.singletonMap("message","Thisendpointisdisabled"),HttpStatus.NOT_FOUND):super.invoke();
}
}
@ConfigurationProperties(
prefix="endpoints.shutdown"
)
publicclassShutdownEndpointextendsAbstractEndpoint<Map<String,Object>>implementsApplicationContextAware{
privatestaticfinalMapNO_CONTEXT_MESSAGE=Collections.unmodifiableMap(Collections.singletonMap("message","Nocontexttoshutdown."));
privatestaticfinalMapSHUTDOWN_MESSAGE=Collections.unmodifiableMap(Collections.singletonMap("message","Shuttingdown,bye..."));
privateConfigurableApplicationContextcontext;

publicShutdownEndpoint(){
super("shutdown",true,false);
}
//执行关闭
publicMapinvoke(){
if(this.context==null){
returnNO_CONTEXT_MESSAGE;
}else{
booleanvar6=false;

Mapvar1;

classNamelessClass_1implementsRunnable{
NamelessClass_1(){
}

publicvoidrun(){
try{
Thread.sleep(500L);
}catch(InterruptedExceptionvar2){
Thread.currentThread().interrupt();
}
//这个调用的就是AbstractApplicationContext.close
ShutdownEndpoint.this.context.close();
}
}

try{
var6=true;
var1=SHUTDOWN_MESSAGE;
var6=false;
}finally{
if(var6){
Threadthread=newThread(newNamelessClass_1());
thread.setContextClassLoader(this.getClass().getClassLoader());
thread.start();
}
}

Threadthread=newThread(newNamelessClass_1());
thread.setContextClassLoader(this.getClass().getClassLoader());
thread.start();
returnvar1;
}
}
}


审核编辑 :李倩


声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • spring
    +关注

    关注

    0

    文章

    332

    浏览量

    14161
  • Boot
    +关注

    关注

    0

    文章

    142

    浏览量

    35248
  • kill
    +关注

    关注

    0

    文章

    9

    浏览量

    2062

原文标题:求求你们别再用 kill -9 了,这才是 Spring Boot 停机的正确方式!!!

文章出处:【微信号:芋道源码,微信公众号:芋道源码】欢迎添加关注!文章转载请注明出处。

收藏 人收藏

    评论

    相关推荐

    Spring Boot如何实现异步任务

    Spring Boot 提供了多种方式来实现异步任务,这里介绍三种主要实现方式。 1、基于注解 @Async @Async 注解是 Spring
    的头像 发表于 09-30 10:32 488次阅读

    Spring boot中Redis的使用

    【本人秃顶程序员】springboot专辑:Spring boot中Redis的使用
    发表于 03-27 11:42

    启动Spring Boot项目应用的三种方法

    ,从而使开发人员不再需要定义样板化的配置。用我的话来理解,就是spring boot其实不是什么新的框架,它默认配置很多框架的使用方式,就像maven整合
    发表于 01-14 17:33

    Spring Boot嵌入式Web容器原理是什么

    ,不需要配置任何特殊的XML配置,为了这个目标,Spring BootSpring 4.0框架之上提供很多特性,帮助应用以“约定优于配置”“开箱即用”的
    发表于 12-16 07:57

    千万别再用这台示波器,我怕你会爱上它!

    千万别再用这台示波器,我怕你会爱上它!
    发表于 05-30 21:01

    Spring Boot框架错误处理

    》 《strong》翻译《/strong》:雁惊寒《/p》 《/blockquote》《p》《em》摘要:本文通过实例介绍了使用Spring Boot在设计API的时候如何正确地对异常进行处理。以下是译文《/em》《/p》《p》
    发表于 09-28 15:31 0次下载

    Spring Boot从零入门1 详述

    在开始学习Spring Boot之前,我之前从未接触过Spring相关的项目,Java基础还是几年前自学的,现在估计也忘得差不多了吧,写Spring
    的头像 发表于 12-10 22:18 417次阅读

    Spring Boot特有的实践

    Spring Boot是最流行的用于开发微服务的Java框架。在本文中,我将与你分享自2016年以来我在专业开发中使用Spring Boot所采用的最佳实践。这些内容是基于我的个人经验
    的头像 发表于 09-29 10:24 644次阅读

    强大的Spring Boot 3.0要来了

    来源:OSC开源社区(ID:oschina2013) Spring Boot 3.0 首个 RC 已发布,此外还为两个分支发布了更新:2.7.5 2.6.13。 3.0.0-RC1: https
    的头像 发表于 10-31 11:17 1174次阅读

    怎样使用Kiuwan保护Spring Boot应用程序呢?

    Spring Boot 提供了快速轻松地构建基于Spring 的应用程序所需的工具、功能和依赖项。
    的头像 发表于 03-16 09:10 532次阅读

    Spring Boot Web相关的基础知识

    上一篇文章我们已经学会了如何通过IDEA快速建立一个Spring Boot项目,还介绍了Spring Boot项目的结构,介绍了项目配置文件pom.xml的组成部分,并且撰写了我们
    的头像 发表于 03-17 15:03 460次阅读

    Spring Boot Actuator快速入门

    不知道大家在写 Spring Boot 项目的过程中,使用过 Spring Boot Actuator 吗?知道 Spring
    的头像 发表于 10-09 17:11 340次阅读

    Spring Boot启动 Eureka流程

    在上篇中已经说过了 Eureka-Server 本质上是一个 web 应用的项目,今天就来看看 Spring Boot 是怎么启动 Eureka 的。 Spring Boot 启动 E
    的头像 发表于 10-10 11:40 419次阅读
    <b class='flag-5'>Spring</b> <b class='flag-5'>Boot</b>启动 Eureka流程

    Spring Boot的启动原理

    可能很多初学者会比较困惑,Spring Boot 是如何做到将应用代码和所有的依赖打包成一个独立的 Jar 包,因为传统的 Java 项目打包成 Jar 包之后,需要通过 -classpath 属性
    的头像 发表于 10-13 11:44 369次阅读
    <b class='flag-5'>Spring</b> <b class='flag-5'>Boot</b>的启动原理

    Spring Boot 的设计目标

    ,这样我们就可以尽快的上手。 使用 Spring Boot 来不仅可以创建基于 war 方式部署的传统Java应用程序,也可以通过创建独立的不依赖任何容器(如 tomcat 等)
    的头像 发表于 10-13 14:56 310次阅读
    <b class='flag-5'>Spring</b> <b class='flag-5'>Boot</b> 的设计目标