【踩坑經驗】Spring Webflux 的優雅關閉(Graceful Shutdown)

背景

  • 最近在吃 webflux 這隻螃蟹,發現雖然文檔中寫的會優雅關閉,但其實並無等待全部請求返回再 shutdown. 若是有還未完成的請求(如sleep 10s的請求),會直接 Empty reply (環境: spring boot 2.1.5 with reactor-netty 0.8.8)

根因&解法

  • 跳過度析不表,直接說結論
  • 雖然 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
  2. 真實關閉流程 org.springframework.context.support.AbstractApplicationContext#doClose
    1. 關閉上下文
    2. 關閉 lifecycle bean
    3. 關閉 beanFactory 生成的單例 bean (NettyServer 的關閉就在這)
    4. 關閉 BeanFactory
    5. 關閉 DisposableServer
    6. 移除監聽器,設置狀態爲 inactive
  3. 測試發現,在關閉 bean 過程當中,當reactorServerResourceFactory關閉後(org.springframework.http.client.reactive.ReactorResourceFactory#destroy),端口就已經關閉,但還未響應的請求還可繼續響應。
    • 所以還有種 hacker 方式是利用類複寫在這邊改寫關閉邏輯,在關閉 reactorServer 後等待一段時間。但過於 hacker ,萬不得已不要使用

參考資料

相關文章
相關標籤/搜索