Spring Cloud: 使用kill命令優雅關閉微服務, 解決退出時Eureka取消註冊操作耗時過長的坑
阿新 • • 發佈:2019-01-23
關於Spring Cloud服務優雅關閉的方案有很多種了,這裡介紹一下使用kill
命令優雅關閉的方案,並解決會出現的問題。
所謂的優雅指兩方面,一是程式在退出時要主動向Eureka取消註冊自己,二是完成資源清理工作。比如我的程式裡用到了執行緒池來非同步執行一些任務,如果退出時不做清理,那麼就有非同步任務被異常中斷導致業務資料不一致的風險。首先我們不能使用kill -9
。如果加了-9
,那麼系統就不會給JVM呼叫 shutdown hook 的機會,也就無法完成資源清理了。
退出取消Euerka註冊隱藏的坑
Spring Cloud預設的EurekaClientAutoConfiguration
通過追蹤原始碼,我發現取消註冊的邏輯是在EurekaAutoServiceRegistration#onApplicationEvent()
ContextCloseEvent
,然後呼叫stop()
方法取消註冊,耗時的也是這個stop()
方法。因此我們可以自己編寫一個類繼承此類,覆蓋stop()
方法,新增超時邏輯:
@Slf4j
public static class EngineEurekaAutoServiceRegistration extends EurekaAutoServiceRegistration {
public EngineEurekaAutoServiceRegistration(ApplicationContext context,
EurekaServiceRegistry serviceRegistry,
EurekaRegistration registration) {
super (context, serviceRegistry, registration);
}
/**
* 上下文關閉時會呼叫此方法, 在另一個執行緒中取消註冊, 防止超時
*/
@Override
public void stop() {
log.info("unregsiter eureka in another thread");
Thread stopThread = new Thread(() -> super.stop());
stopThread.start();
try {
stopThread.join(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
log.info("unregister done");
}
}
在這裡我們在新的執行緒中呼叫父類的stop()
, 然後通過join()
方法控制超時時間,設定為2s。
要想讓自己的類起效,我們還要做一些工作。首先我們要編寫一個配置類註冊自己的bean:
@Configuration
public class EngineEurekaConfig extends EurekaClientAutoConfiguration {
public EngineEurekaConfig(ConfigurableEnvironment env) {
super(env);
}
@Bean
@ConditionalOnBean(AutoServiceRegistrationProperties.class)
@ConditionalOnProperty(value = "spring.cloud.service-registry.auto-registration.enabled", matchIfMissing = true)
@Override
public EurekaAutoServiceRegistration eurekaAutoServiceRegistration(ApplicationContext context,
EurekaServiceRegistry registry,
EurekaRegistration registration) {
return new EngineEurekaAutoServiceRegistration(context, registry, registration);
}
}
然後在@SpringBootApplication
註解中排除EurekaClientAutoConfiguration.class
:
@SpringBootApplication( exclude = {
EurekaClientAutoConfiguration.class
})
這樣Spring就使用我們的類來響應關閉事件了,當unregister過程超時後會直接忽略,程序退出。
執行緒池清理
這部分就比較簡單了,寫一個@PreDestroy
方法,在裡面依次呼叫執行緒池的pool.shutdown()
和pool.awaitTermination()
即可。