【踩坑经验】Spring Webflux 的优雅关闭(Graceful Shutdown)

背景

  • 最近在吃 webflux 这只螃蟹,发现虽然文档中写的会优雅关闭,但其实并没有等待所有请求返回再 shutdown. 如果有还未完成的请求(如sleep 10s的请求),会直接 Empty reply (环境: spring boot 2.1.5 with reactor-netty 0.8.8)
    【踩坑经验】Spring Webflux 的优雅关闭(Graceful Shutdown)

根因&解法

  • 跳过分析不表,直接说结论
  • 虽然 netty 本身有 graceful shutdown,并且在关闭时也的确调用到了,但是 reactor netty 调用的方式如下
//reactor.netty.resources.LoopResources#dispose
@Override
default void dispose() {
    //noop default
    disposeLater().subscribe();
}
  • 直接调用了 subscribe ,并没有 blocking 到完成,因此如果后续 spring shutdown 了,这个过程会直接中断,就造成了上述问题。
  • 那么如何解决呢?

    • 由于不太熟悉 webflux 这一套,不知道怎么等待所有 subscribe 完成,所以采用了一个比较 hack 的方法,启动后通过反射拿到内部的 HttpResources 然后注册了一个关闭钩子,直接在调用 blocking 方法,阻塞得释放掉LoopResources,这样就没问题了
/**
 * @author Lambda.J
 * @version $Id: GracefulShutdown.java, v 0.1 2019-05-27 
 */
@Component
public class GracefulShutdown {
    @Autowired
    ReactorResourceFactory reactorResourceFactory;

    LoopResources loopResources;

    // SpringBoot 2.1.5 reactor.netty.resources.LoopResources#dispose 只 subscribe 没有 block 造成没有等待关闭,这边手工调用,后面如果修复了直接删除就好
    @PostConstruct
    void init() throws Exception {
        Field field = TcpResources.class.getDeclaredField("defaultLoops");
        field.setAccessible(true);
        loopResources = (LoopResources) field.get(reactorResourceFactory.getLoopResources());
        field.setAccessible(false);

        Runtime.getRuntime().addShutdownHook(new Thread(() -> {
            System.out.println("Graceful: block long to 20s before real shutdown!");
            loopResources.disposeLater().block(Duration.ofSeconds(20));
        }));
    }
}

关联知识

Spring 关闭流程

  1. 启动时会注册一个关闭钩子 org.springframework.context.support.AbstractApplicationContext#registerShutdownHook
    【踩坑经验】Spring Webflux 的优雅关闭(Graceful Shutdown)
  2. 真实关闭流程 org.springframework.context.support.AbstractApplicationContext#doClose
    【踩坑经验】Spring Webflux 的优雅关闭(Graceful Shutdown)

    1. 关闭上下文
    2. 关闭 lifecycle bean
    3. 关闭 beanFactory 生成的单例 bean (NettyServer 的关闭就在这)
      【踩坑经验】Spring Webflux 的优雅关闭(Graceful Shutdown)
    4. 关闭 BeanFactory
    5. 关闭 DisposableServer
    6. 移除监听器,设置状态为 inactive
  3. 测试发现,在关闭 bean 过程中,当reactorServerResourceFactory关闭后(org.springframework.http.client.reactive.ReactorResourceFactory#destroy),端口就已经关闭,但还未响应的请求还可继续响应。

    • 因此还有种 hacker 方式是利用类复写在这边改写关闭逻辑,在关闭 reactorServer 后等待一段时间。但过于 hacker ,万不得已不要使用
      【踩坑经验】Spring Webflux 的优雅关闭(Graceful Shutdown)

参考资料

上一篇:C#委托基础2——多路委托


下一篇:Redhat KVM网络配置