zoukankan      html  css  js  c++  java
  • 优雅停机(还有一个在程序中保持事务原子性的思路)

    笔者的项目需要改造成支持优雅停机,停止服务时不在接受新的请求(不接受请求所,不是拒绝请求,而是客户端不再往服务端分发),但是已经接受的请求,需要在处理完,再停止服务。

    目前大致的思路是:微服务框架实现优雅停机的功能,destroy方法里微服务框架先把本地服务从zk上下线、等待5秒(等待处理中的业务结束),后续再判断业务是否结束,如果未结束根据配置确定是否再等待,然后关闭连接。

    为此,需要对系统进行一些重构,将系统部署的服务器进行分组,分为现网业务、MQ、定时任务3

    通过流控(nginx)确保现网用户的流量流量指向现网业务组,MQ和定时任务组不接受现网流量

    MQ和定时任务设计统一的配置中心,各个模块暴露启停接口,通过接受配置中心的通知来控制启停

    MQ和定时任务需要中间状态监控和停机后的续传业务,以确保停机后可以继续执行中断的业务。

    shutdownHook主要用于:

    • Application正常退出,在退出时执行特定的业务逻辑,或者关闭资源等操作。
    • 虚拟机非正常退出,比如用户按下ctrl+c、OOM宕机、操作系统关闭(kill pid)等。在退出时执行必要的挽救措施。

    以下博文来自:https://www.cnblogs.com/shuo1208/p/5871224.html

    JDK在1.3之后提供了Java Runtime.addShutdownHook(Thread hook)方法,可以注册一个JVM关闭的钩子,这个钩子可以在以下几种场景被调用:

    • 1)程序正常退出
    • 2)使用System.exit()
    • 3)终端使用Ctrl+C触发的中断
    • 4)系统关闭
    • 5)使用Kill pid命令干掉进程

    注:在使用kill -9 pid是不会JVM注册的钩子不会被调用。
    在JDK中方法的声明:
    public void addShutdownHook(Thread hook)
    参数
    hook -- 一个初始化但尚未启动的线程对象,注册到JVM钩子的运行代码。
    异常
    IllegalArgumentException -- 如果指定的钩已被注册,或如果它可以判定钩已经运行或已被运行
    IllegalStateException -- 如果虚拟机已经是在关闭的过程中
    SecurityException -- 如果存在安全管理器并且它拒绝的RuntimePermission(“shutdownHooks”)

    代码示例:
    使用Timer模拟一个工作线程,该线程重复工作十次,使用System.exit()退出,在清理现场代码CleanWorkThread 中,取消timer运行,并输出必要的日志信息。

    复制代码
     1 package com.netease.test.java.lang;
     2 
     3 import java.util.Timer;
     4 import java.util.TimerTask;
     5 import java.util.concurrent.atomic.AtomicInteger;
     6 
     7 /**
     8  * Date: 14-6-18
     9  * Time: 11:01
    10  * 测试ShutdownHook
    11  */
    12 public class TestShutdownHook {
    13 
    14     //简单模拟干活的
    15     static Timer timer = new Timer("job-timer");
    16 
    17     //计数干活次数
    18     static AtomicInteger count = new AtomicInteger(0);
    19 
    20     /**
    21      * hook线程
    22      */
    23     static class CleanWorkThread extends Thread{
    24         @Override
    25         public void run() {
    26             System.out.println("clean some work.");
    27             timer.cancel();
    28             try {
    29                 Thread.sleep(2 * 1000);//sleep 2s
    30             } catch (InterruptedException e) {
    31                 e.printStackTrace();
    32             }
    33         }
    34     }
    35     public static void main(String[] args) throws InterruptedException {
    36         //将hook线程添加到运行时环境中去
    37         Runtime.getRuntime().addShutdownHook(new CleanWorkThread());
    38         System.out.println("main class start ..... ");
    39         //简单模拟
    40         timer.schedule(new TimerTask() {
    41             @Override
    42             public void run() {
    43                 count.getAndIncrement();
    44                 System.out.println("doing job " + count);
    45                 if (count.get() == 10) {  //干了10次退出
    46                     System.exit(0);
    47                 }
    48             }
    49         }, 0, 2 * 1000);
    50 
    51     }
    52 }
    复制代码

    运行后,可以模拟以上五种场景进行测试,只有kill -9 pid不会执行Hook里面的代码。

    使用shutdownHook需注意:

    1.不一定总是被执行,例如,JVM崩溃了。

    2.shutdownHook可以在完成前强行停止

    虽然shutdownHook开始执行,但是在操作系统关闭的情况下,任然可以在完成之前终止它。在这种情况下,一旦SIGTERM被提供,O/S等待一个进程终止指定的时间。如果进程在该时间限制内没有终止,则O/S通过发出SIGTERM(或Windows中的对等方)强制终止进程。所以有可能这是在shutdownHook中途执行时发生的。

    需要保证其调用的服务不会被其他Hook影响。否则会出现当前Hook所依赖的服务被另外一个Hook终止了的情况。

    因此,建议谨慎地编写shutdownHook,确保它们快速完成,并且不会造成死锁等情况。另外特别注意的是,不应该执行长时间计算或等待用户I/O操作在钩子。

    3.可以有多个shutdownHook,但其执行顺序无法保证,因为它是存储在IdentityHashMap中的,JVM并不能保证其执行顺序。但是可以同时执行所有的shutdownHook。

    4.关闭顺序开始后,无法注册/取消注册shutdownHook

    一旦关闭顺序是由JVM发起的,将不在允许添加或删除任何现有的shutdownHook,否则抛出IllegalStateException异常。

    5.关闭顺序开始后,只能由Runtime.halt()停止

    关闭顺序开始后,只能通过Runtime.halt()(强制终止JVM),可以停止关闭顺序的执行(外部影响除外,如SIGKILL)。

    6.使用shutdownHook需要安全权限

    如果我们使用Java Security Managers,则执行添加/删除shutdownHook的代码需要在运行时具有shutdownHooks权限。否则会导致SecurityException

    在程序当中保持事务原子性的一个思路:

    在开发中,遇到这种情况,多个线程同时工作,突然一个线程遇到了fetal的错误,需要立即终止程序,等人工排查解决了问题之后重新启动。但是这样会有一个问题,程序终止时,其他线程可能正在进行重要操作,比如发一个message到另一个模块,并更新数据库状态。突然终止,可能会让这个操作只完成一半,从而导致数据不一致。

    解决方案是:参考数据库Transaction原子性的概念,将这一系列重要操作看作一个整体,要么全部完成,要么全部不完成。为方便表述,我们把这一系列重要操作记为操作X。
    当程序即将退出时,查看当前是否有操作X在执行中,

    • 如果有,等待其完成然后退出。且期间不再接受新的操作X。如果操作X执行之间过长,终止并回滚所有状态。
    • 如果没有,则可以立即退出。

    在程序退出的时候,做一些Check,保证已经开始的操作X的原子性,这里就用到了Runtime.ShutdownHook

    假如在系统中通过 Runtime.getRuntime().exec(String command) 或 new ProcessBuilder(List<String> command) 启动了子进程(Process),这个子进程一直在运行中,在当前进程退出时,子进程未必会退出,但此时业务上希望将它退出,就可以利用 ShutdownHook 。

    Java的Signal在

    Linux下支持的信号:

    SEGV, ILL, FPE, BUS, SYS, CPU, FSZ, ABRT, INT, TERM, HUP, USR1, USR2, QUIT, BREAK, TRAP, PIPE

    Windows下支持的信号:

    SEGV, ILL, FPE, ABRT, INT, TERM, BREAK

  • 相关阅读:
    ASP.NET和PHP全面对比
    GridView事件DataBinding,DataBound,RowCreated,RowDataBound区别及执行顺序分析
    OA、CRM、ERP之间的区别和联系是什么?
    C#继承
    对软件项目管理的几点认识
    冒泡
    经典排序算法
    asp.net遍历页面中所有TextBox,并赋值为String.Empty的方法
    String.Format用法
    frame和iframe
  • 原文地址:https://www.cnblogs.com/yedu/p/9224813.html
Copyright © 2011-2022 走看看