求求你们别再用 kill | 您所在的位置:网站首页 › rbac全称 › 求求你们别再用 kill |
点击上方“芋道源码”,选择“设为星标” 管她前浪,还是后浪? 能浪的浪,才是好浪! 每天 10:33 更新文章,每天掉亿点点头发... 源码精品专栏 原创 | Java 2021 超神之路,很肝~ 中文详细注释的开源项目 RPC 框架 Dubbo 源码解析 网络应用框架 Netty 源码解析 消息中间件 RocketMQ 源码解析 数据库中间件 Sharding-JDBC 和 MyCAT 源码解析 作业调度中间件 Elastic-Job 源码解析 分布式事务中间件 TCC-Transaction 源码解析 Eureka 和 Hystrix 源码解析 Java 并发源码 来源:blog.csdn.net/alex_xfboy/ article/details/90404691/ 何为优雅关机 kill指令 Runtime.addShutdownHook spring 3.2.12 spring boot 再谈为了提醒明知故犯(在一坑里迭倒两次不是不多见),由于业务系统中大量使用了spring Boot embedded tomcat的模式运行,在一些运维脚本中经常看到Linux 中 kill 指令,然而它的使用也有些讲究,要思考如何能做到优雅停机。 何为优雅关机就是为确保应用关闭时,通知应用进程释放所占用的资源 线程池,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 > 基于 Spring Cloud Alibaba + Gateway + Nacos + RocketMQ + Vue & Element 实现的后台管理系统 + 用户小程序,支持 RBAC 动态权限、多租户、数据权限、工作流、三方登录、支付、短信、商城等功能 > > * 项目地址: > * 视频教程: # Windows下信号常量值 # 简称 全称 数值 # INT SIGINT 2 Ctrl+C中断 # ILL SIGILL 4 非法指令 # FPE SIGFPE 8 floating point exception(浮点异常) # SEGV SIGSEGV 11 segment violation(段错误) # TERM SIGTERM 5 Software termination signal from kill(Kill发出的软件终止) # BREAK SIGBREAK 21 Ctrl-Break sequence(Ctrl+Break中断) # ABRT SIGABRT 22 abnormal termination triggered by abort call(Abort) #linux信号常量值 # 简称 全称 数值 # HUP SIGHUP 1 终端断线 # INT SIGINT 2 中断(同 Ctrl + C) # QUIT SIGQUIT 3 退出(同 Ctrl + \) # KILL SIGKILL 9 强制终止 # TERM SIGTERM 15 终止 # CONT SIGCONT 18 继续(与STOP相反, fg/bg命令) # STOP SIGSTOP 19 暂停(同 Ctrl + Z) #.... #可以理解为操作系统从内核级别强行杀死某个进程 kill -9 pid #理解为发送一个通知,等待应用主动关闭 kill -15 pid #也支持信号常量值全称或简写(就是去掉SIG后) kill -l KILL思考:jvm是如何接受处理linux信号量的? 当然是在jvm启动时就加载了自定义SignalHandler,关闭jvm时触发对应的handle。 public interface SignalHandler { SignalHandler SIG_DFL = new NativeSignalHandler(0L); SignalHandler SIG_IGN = new NativeSignalHandler(1L); void handle(Signal var1); } class Terminator { private static SignalHandler handler = null; Terminator() { } //jvm设置SignalHandler,在System.initializeSystemClass中触发 static void setup() { if (handler == null) { SignalHandler var0 = new SignalHandler() { public void handle(Signal var1) { Shutdown.exit(var1.getNumber() + 128);//调用Shutdown.exit } }; handler = var0; try { Signal.handle(new Signal("INT"), var0);//中断时 } catch (IllegalArgumentException var3) { ; } try { Signal.handle(new Signal("TERM"), var0);//终止时 } catch (IllegalArgumentException var2) { ; } } } } Runtime.addShutdownHook在了解Shutdown.exit之前,先看Runtime.getRuntime().addShutdownHook(shutdownHook);则是为jvm中增加一个关闭的钩子,当jvm关闭的时候调用。 public class Runtime { public void addShutdownHook(Thread hook) { SecurityManager sm = System.getSecurityManager(); if (sm != null) { sm.checkPermission(new RuntimePermission("shutdownHooks")); } ApplicationShutdownHooks.add(hook); } } class ApplicationShutdownHooks { /* The set of registered hooks */ private static IdentityHashMap hooks; static synchronized void add(Thread hook) { if(hooks == null) throw new IllegalStateException("Shutdown in progress"); if (hook.isAlive()) throw new IllegalArgumentException("Hook already running"); if (hooks.containsKey(hook)) throw new IllegalArgumentException("Hook previously registered"); hooks.put(hook, hook); } } //它含数据结构和逻辑管理虚拟机关闭序列 class Shutdown { /* Shutdown 系列状态*/ private static final int RUNNING = 0; private static final int HOOKS = 1; private static final int FINALIZERS = 2; private static int state = RUNNING; /* 是否应该运行所以finalizers来exit? */ private static boolean runFinalizersOnExit = false; // 系统关闭钩子注册一个预定义的插槽. // 关闭钩子的列表如下: // (0) Console restore hook // (1) Application hooks // (2) DeleteOnExit hook private static final int MAX_SYSTEM_HOOKS = 10; private static final Runnable[] hooks = new Runnable[MAX_SYSTEM_HOOKS]; // 当前运行关闭钩子的钩子的索引 private static int currentRunningHook = 0; /* 前面的静态字段由这个锁保护 */ private static class Lock { }; private static Object lock = new Lock(); /* 为native halt方法提供锁对象 */ private static Object haltLock = new Lock(); static void add(int slot, boolean registerShutdownInProgress, Runnable hook) { synchronized (lock) { if (hooks[slot] != null) throw new InternalError("Shutdown hook at slot " + slot + " already registered"); if (!registerShutdownInProgress) {//执行shutdown过程中不添加hook if (state > RUNNING)//如果已经在执行shutdown操作不能添加hook throw new IllegalStateException("Shutdown in progress"); } else {//如果hooks已经执行完毕不能再添加hook。如果正在执行hooks时,添加的槽点小于当前执行的槽点位置也不能添加 if (state > HOOKS || (state == HOOKS && slot runFinalizersOnExit > halt static void exit(int status) { boolean runMoreFinalizers = false; synchronized (lock) { if (status != 0) runFinalizersOnExit = false; switch (state) { case RUNNING: /* Initiate shutdown */ state = HOOKS; break; case HOOKS: /* Stall and halt */ break; case FINALIZERS: if (status != 0) { /* Halt immediately on nonzero status */ halt(status); } else { /* Compatibility with old behavior: * Run more finalizers and then halt */ runMoreFinalizers = runFinalizersOnExit; } break; } } if (runMoreFinalizers) { runAllFinalizers(); halt(status); } synchronized (Shutdown.class) { /* Synchronize on the class object, causing any other thread * that attempts to initiate shutdown to stall indefinitely */ sequence(); halt(status); } } //shutdown操作,与exit不同的是不做halt操作(关闭JVM) static void shutdown() { synchronized (lock) { switch (state) { case RUNNING: /* Initiate shutdown */ state = HOOKS; break; case HOOKS: /* Stall and then return */ case FINALIZERS: break; } } synchronized (Shutdown.class) { sequence(); } } } spring 3.2.12在spring中通过ContextClosedEvent事件来触发一些动作(可以拓展),主要通过LifecycleProcessor.onClose来做stopBeans。由此可见spring也基于jvm做了拓展。 public abstract class AbstractApplicationContext extends DefaultResourceLoader { public void registerShutdownHook() { if (this.shutdownHook == null) { // No shutdown hook registered yet. this.shutdownHook = new Thread() { @Override public void run() { doClose(); } }; Runtime.getRuntime().addShutdownHook(this.shutdownHook); } } protected void doClose() { boolean actuallyClose; 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(new ContextClosedEvent(this)); } catch (Throwable ex) { logger.warn("Exception thrown from ApplicationListener handling ContextClosedEvent", ex); } // 停止所有的Lifecycle beans. try { getLifecycleProcessor().onClose(); } catch (Throwable ex) { logger.warn("Exception thrown from LifecycleProcessor on context close", ex); } // 销毁spring 的 BeanFactory可能会缓存单例的 Bean. destroyBeans(); // 关闭当前应用上下文(BeanFactory) closeBeanFactory(); // 执行子类的关闭逻辑 onClose(); synchronized (this.activeMonitor) { this.active = false; } } } } public interface LifecycleProcessor extends Lifecycle { /** * Notification of context refresh, e.g. for auto-starting components. */ void onRefresh(); /** * Notification of context close phase, e.g. for auto-stopping components. */ void onClose(); } 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=SUPERUSERspring boot的shutdown原理也不复杂,其实还是通过调用AbstractApplicationContext.close实现的。 @ConfigurationProperties( prefix = "endpoints.shutdown" ) public class ShutdownMvcEndpoint extends EndpointMvcAdapter { public ShutdownMvcEndpoint(ShutdownEndpoint delegate) { super(delegate); } //post请求 @PostMapping( produces = {"application/vnd.spring-boot.actuator.v1+json", "application/json"} ) @ResponseBody public Object invoke() { return !this.getDelegate().isEnabled() ? new ResponseEntity(Collections.singletonMap("message", "This endpoint is disabled"), HttpStatus.NOT_FOUND) : super.invoke(); } } @ConfigurationProperties( prefix = "endpoints.shutdown" ) public class ShutdownEndpoint extends AbstractEndpoint implements ApplicationContextAware { private static final Map NO_CONTEXT_MESSAGE = Collections.unmodifiableMap(Collections.singletonMap("message", "No context to shutdown.")); private static final Map SHUTDOWN_MESSAGE = Collections.unmodifiableMap(Collections.singletonMap("message", "Shutting down, bye...")); private ConfigurableApplicationContext context; public ShutdownEndpoint() { super("shutdown", true, false); } //执行关闭 public Map invoke() { if (this.context == null) { return NO_CONTEXT_MESSAGE; } else { boolean var6 = false; Map var1; class NamelessClass_1 implements Runnable { NamelessClass_1() { } public void run() { try { Thread.sleep(500L); } catch (InterruptedException var2) { Thread.currentThread().interrupt(); } //这个调用的就是AbstractApplicationContext.close ShutdownEndpoint.this.context.close(); } } try { var6 = true; var1 = SHUTDOWN_MESSAGE; var6 = false; } finally { if (var6) { Thread thread = new Thread(new NamelessClass_1()); thread.setContextClassLoader(this.getClass().getClassLoader()); thread.start(); } } Thread thread = new Thread(new NamelessClass_1()); thread.setContextClassLoader(this.getClass().getClassLoader()); thread.start(); return var1; } } }欢迎加入我的知识星球,一起探讨架构,交流源码。加入方式,长按下方二维码噢: 已在知识星球更新源码解析如下: 最近更新《芋道 SpringBoot 2.X 入门》系列,已经 101 余篇,覆盖了 MyBatis、Redis、MongoDB、ES、分库分表、读写分离、SpringMVC、Webflux、权限、WebSocket、Dubbo、RabbitMQ、RocketMQ、Kafka、性能测试等等内容。 提供近 3W 行代码的 SpringBoot 示例,以及超 4W 行代码的电商微服务项目。 获取方式:点“在看”,关注公众号并回复 666 领取,更多内容陆续奉上。 文章有帮助的话,在看,转发吧。 谢谢支持哟 (*^__^*) |
CopyRight 2018-2019 实验室设备网 版权所有 |