暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

深度解析:JVM ShutdownHook在Flink应用案例详述

大数据从业者 2022-08-03
1838

背景

      最近在排查FlinkOnYarn作业被人为通过yarn kill之后未清理HDFS临时文件的问题,发现FlinkOnYarn作业启动时候虽然注册了JVM Shutdown Hook,但并没有在该Hook线程中进行相关清理操作,这部分我已进行简单的源码适配,后续发文详解。

     本文主要描述已有JVMShutdownHook线程类JvmShutdownSafeguard有意思的用法:防止shutdownHooks线程异常导致JVM不能正常退出。

JVM ShutdownHook

     介绍JvmShutdownSafeguard线程类之前,我们先描述下JVM原生ShutdownHook机制。JDK源码Runtime.java类文件有详细的描述。

      大概内容如下:通过Runtime.getRuntime().addShutdownHook方法就可以注册一个Hook线程。所谓注册该线程仅仅是进行线程初始化,并不是线程执行。JVM ShutdownHook适用于:

正常退出场景:程序运行完成、kill -15、ctrl + c、System.exit(0)等。

异常退出场景:OOM/RuntimeException造成的程序中断等。

并不适用于强制退出场景:kill -9、Runtime.halt、系统关机/crash等)。

      当JVM正常退出或者异常退出的时候,会触发执行之前注册的所有Hook线程,这些Hook线程执行特点:并发且无序、要求线程安全、尽量避免死锁、要求耗时短。一旦Hook线程触发执行,只能通过halt方法强制停止。

      通过几个小case可以模拟上述场景,以验证Hook线程触发机制、触发时间。场景完整测试用例见github:

https://github.com/felixzh2020/felixzh-learning-java/tree/master/JVMHookCase/src/main/java/cases

正常退出场景

    //添加钩子
    Runtime.getRuntime().addShutdownHook(new ShutDownHookThread(0));
    //业务工作
    work();
    //正常退出
    System.out.println("==== normal exit ====");

    可以看出,触发了ShutdownHookThread。

    异常退出场景

      //添加钩子
      Runtime.getRuntime().addShutdownHook(new ShutDownHookThread(0));
      //业务工作
      work();
      //异常退出
      throw new RuntimeException("exception");

      可以看出,触发了ShutdownHookThread。

      强制退出场景

        //添加钩子
        Runtime.getRuntime().addShutdownHook(new ShutDownHookThread(0));
        //业务工作
        work();
        //模拟常驻
        Thread.sleep(600_000);

        将打包jar拷贝到Linux机器,启动之后,通过kill -9 杀掉进程。

        可以看出,ctrl+c可以触发,但是kil -9 不能触发ShutdownHookThread。

        所以,如果通过kill -9强制停止进程,想通过ShutdownHook进行清理操作并不可行。

        JvmShutdownSafeguard

               言归正传,我们回到正题,看下Flink中JvmShutdownSafeguard实现机制。该类的源码属于flink-runtime模块,位于:

          flink\flink-runtime\src\main\java\org\apache\flink\runtime\util\JvmShutdownSafeguard.java

                该类为Hook线程类,所以继承了Thread类。

              考虑上述提到的Hook线程类的特点,该类在run方法中另拉起一个Daemon线程。该Daemon线程执行一段时间sleep(即容忍的JVM退出超时时间),一旦超时不管其他Hook线程是否执行完成,通过调用Runtime.getRuntime().halt方法强制关闭JVM。

                通过这样的方法保证JVM的关闭退出是可控的、不受Hook线程异常或者死锁之类的影响。

          关键代码如下:

            // JvmShutdownSafeguard线程run方法
            @Override
            public void run() {
            // Because this thread is registered as a shutdown hook, we cannot
            // wait here and then call for termination. That would always delay the JVM shutdown.
            // Instead, we spawn a non shutdown hook thread from here.
            // That thread is a daemon, so it does not keep the JVM alive.
            terminator.start();
            }
              //Daemon线程run方法
              @Override
              public void run() {
              try {
              Thread.sleep(delayMillis);
              } catch (Throwable t) {
              // catch all, including thread death, etc
              }


              Runtime.getRuntime().halt(EXIT_CODE);
              }

                   JvmShutdownSafeguard线程类超时保护机制可以用于任何使用ShutdownHook的项目中,比如我将其应用到上述描述的正常退出场景:

                //添加钩子,Hook线程sleep 10s
                Runtime.getRuntime().addShutdownHook(new ShutDownHookThread(10));
                //增加超时保护,超时5s,强制关闭JVM
                Runtime.getRuntime().addShutdownHook(new JvmShutdownSafeguard(5));
                //业务工作
                work();
                //正常退出
                System.out.println("==== normal exit ====");

                可以看出:ShutdownHookThread线程还没结束(结束会打印finish),JvmShutdownSafeguard超时保护机制已经触发,强制关闭了JVM。

                后续

                       本文梳理JVM ShutdownHook机制和Flink基于该机制实现的JVM退出时的超时保护机制。后续再深入介绍基于该机制我们如何让FlinkOnYarn作业被yarn kill时自动清理临时文件。

                       温馨提示:微信公众号私信有时效性、过期不可回复,很多朋友私信我没及时看到、未能回复。最近新建钉钉群(44703541),欢迎朋友加入!

                文章转载自大数据从业者,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

                评论