求求你们别再用 kill 您所在的位置:网站首页 rbac全称 求求你们别再用 kill

求求你们别再用 kill

2023-05-17 12:13| 来源: 网络整理| 查看: 265

点击上方“芋道源码”,选择“设为星标”

管她前浪,还是后浪?

能浪的浪,才是好浪!

每天 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

7e6722c0e1b0eb2bd14c2c3302d78cfc.jpeg

再谈为了提醒明知故犯(在一坑里迭倒两次不是不多见),由于业务系统中大量使用了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=SUPERUSER

spring 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;         }     } }

欢迎加入我的知识星球,一起探讨架构,交流源码。加入方式,长按下方二维码噢:

c3a4a783b88709e555f1de948945d1e4.png

已在知识星球更新源码解析如下:

c91a7091730d5505b826dfedc1f524f2.jpeg

4b5e00a6eac42cc626b1af4fe158e590.jpeg

422689c0ef2495270acad8738f8560cb.jpeg

472837a306cc7a47ef9a848736abae5f.jpeg

最近更新《芋道 SpringBoot 2.X 入门》系列,已经 101 余篇,覆盖了 MyBatis、Redis、MongoDB、ES、分库分表、读写分离、SpringMVC、Webflux、权限、WebSocket、Dubbo、RabbitMQ、RocketMQ、Kafka、性能测试等等内容。

提供近 3W 行代码的 SpringBoot 示例,以及超 4W 行代码的电商微服务项目。

获取方式:点“在看”,关注公众号并回复 666 领取,更多内容陆续奉上。

文章有帮助的话,在看,转发吧。 谢谢支持哟 (*^__^*)


【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有