国产成人精品亚洲777人妖,欧美日韩精品一区视频,最新亚洲国产,国产乱码精品一区二区亚洲

您的位置:首頁技術(shù)文章
文章詳情頁

詳解Spring Boot最新版優(yōu)雅停機的方法

瀏覽:123日期:2023-08-08 16:33:34

什么是優(yōu)雅停機先來一段簡單的代碼,如下:

@RestControllerpublic class DemoController { @GetMapping('/demo') public String demo() throws InterruptedException { // 模擬業(yè)務(wù)耗時處理流程 Thread.sleep(20 * 1000L); return 'hello'; }}

當(dāng)我們流量請求到此接口執(zhí)行業(yè)務(wù)邏輯的時候,若服務(wù)端此時執(zhí)行關(guān)機 (kill),spring boot 默認(rèn)情況會直接關(guān)閉容器(tomcat 等),導(dǎo)致此業(yè)務(wù)邏輯執(zhí)行失敗。在一些業(yè)務(wù)場景下:會出現(xiàn)數(shù)據(jù)不一致的情況,事務(wù)邏輯不會回滾。

詳解Spring Boot最新版優(yōu)雅停機的方法

開源項目:

分布式監(jiān)控(Gitee GVP最有價值開源項目 ):https://gitee.com/sanjiankethree/cubic

攝像頭視頻流采集:https://gitee.com/sanjiankethree/cubic-video

優(yōu)雅停機

目前Spring Boot已經(jīng)發(fā)展到了2.3.4.RELEASE,伴隨著2.3版本的到來,優(yōu)雅停機機制也更加完善了。

目前版本的Spring Boot 優(yōu)雅停機支持Jetty, Reactor Netty, Tomcat和 Undertow 以及反應(yīng)式和基于 Servlet 的 web 應(yīng)用程序都支持優(yōu)雅停機功能。

優(yōu)雅停機的目的:

如果沒有優(yōu)雅停機,服務(wù)器此時直接直接關(guān)閉(kill -9),那么就會導(dǎo)致當(dāng)前正在容器內(nèi)運行的業(yè)務(wù)直接失敗,在某些特殊的場景下產(chǎn)生臟數(shù)據(jù)。

增加了優(yōu)雅停機配置后:

在服務(wù)器執(zhí)行關(guān)閉(kill -2)時,會預(yù)留一點時間使容器內(nèi)部業(yè)務(wù)線程執(zhí)行完畢,此時容器也不允許新的請求進入。新請求的處理方式跟web服務(wù)器有關(guān),Reactor Netty、 Tomcat將停止接入請求,Undertow的處理方式是返回503.

新版配置

YAML配置

新版本配置非常簡單,server.shutdown=graceful 就搞定了(注意,優(yōu)雅停機配置需要配合Tomcat 9.0.33(含)以上版本)

server: port: 6080 shutdown: graceful #開啟優(yōu)雅停機spring: lifecycle: timeout-per-shutdown-phase: 20s #設(shè)置緩沖時間 默認(rèn)30s

在設(shè)置了緩沖參數(shù)timeout-per-shutdown-phase 后,在規(guī)定時間內(nèi)如果線程無法執(zhí)行完畢則會被強制停機。

下面我們來看下停機時,加了優(yōu)雅停日志和不加的區(qū)別:

//未加優(yōu)雅停機配置Disconnected from the target VM, address: ’127.0.0.1:49754’, transport: ’socket’Process finished with exit code 130 (interrupted by signal 2: SIGINT)

加了優(yōu)雅停機配置后,可明顯發(fā)現(xiàn)的日志 Waiting for active requests to cpmplete,此時容器將在ShutdownHook執(zhí)行完畢后停止。

詳解Spring Boot最新版優(yōu)雅停機的方法

關(guān)閉方式

1、 一定不要使用kill -9 操作,使用kill -2 來關(guān)閉容器。這樣才會觸發(fā)java內(nèi)部ShutdownHook操作,kill -9不會觸發(fā)ShutdownHook。

2、可以使用端點監(jiān)控 POST 請求 /actuator/shutdown 來執(zhí)行優(yōu)雅關(guān)機。

添加ShutdownHook

通過上面的日志我們發(fā)現(xiàn)Druid執(zhí)行了自己的ShutdownHook,那么我們也來添加下ShutdownHook,有幾種簡單的方式:

1、實現(xiàn)DisposableBean接口,實現(xiàn)destroy方法

@Slf4j@Servicepublic class DefaultDataStore implements DisposableBean { private final ExecutorService executorService = new ThreadPoolExecutor(OSUtil.getAvailableProcessors(), OSUtil.getAvailableProcessors() + 1, 1, TimeUnit.MINUTES, new ArrayBlockingQueue<>(200), new DefaultThreadFactory('UploadVideo')); @Override public void destroy() throws Exception { log.info('準(zhǔn)備優(yōu)雅停止應(yīng)用使用 DisposableBean'); executorService.shutdown(); }}

2、使用@PreDestroy注解

@Slf4j@Servicepublic class DefaultDataStore { private final ExecutorService executorService = new ThreadPoolExecutor(OSUtil.getAvailableProcessors(), OSUtil.getAvailableProcessors() + 1, 1, TimeUnit.MINUTES, new ArrayBlockingQueue<>(200), new DefaultThreadFactory('UploadVideo')); @PreDestroy public void shutdown() { log.info('準(zhǔn)備優(yōu)雅停止應(yīng)用 @PreDestroy'); executorService.shutdown(); }}

這里注意,@PreDestroy 比 DisposableBean 先執(zhí)行

關(guān)閉原理

1、使用kill pid關(guān)閉,源碼很簡單,大家可以看下GracefulShutdown

private void doShutdown(GracefulShutdownCallback callback) {List<Connector> connectors = getConnectors();connectors.forEach(this::close);try {for (Container host : this.tomcat.getEngine().findChildren()) {for (Container context : host.findChildren()) {while (isActive(context)) {if (this.aborted) {logger.info('Graceful shutdown aborted with one or more requests still active');callback.shutdownComplete(GracefulShutdownResult.REQUESTS_ACTIVE);return;}Thread.sleep(50);}}}}catch (InterruptedException ex) {Thread.currentThread().interrupt();}logger.info('Graceful shutdown complete');callback.shutdownComplete(GracefulShutdownResult.IDLE);}

2、使用端點監(jiān)控 POST 請求 /actuator/shutdown關(guān)閉

因為actuator 都使用了SPI的擴展方式,所以我們看下AutoConfiguration,可以看到關(guān)鍵點就是ShutdownEndpoint

@Configuration( proxyBeanMethods = false)@ConditionalOnAvailableEndpoint( endpoint = ShutdownEndpoint.class)public class ShutdownEndpointAutoConfiguration { public ShutdownEndpointAutoConfiguration() { } @Bean( destroyMethod = '' ) @ConditionalOnMissingBean public ShutdownEndpoint shutdownEndpoint() { return new ShutdownEndpoint(); }}

ShutdownEndpoint,為了節(jié)省篇幅只留了一點重要的

@Endpoint( id = 'shutdown', enableByDefault = false)public class ShutdownEndpoint implements ApplicationContextAware { @WriteOperation public Map<String, String> shutdown() { if (this.context == null) { return NO_CONTEXT_MESSAGE; } else { boolean var6 = false; Map var1; try { var6 = true; var1 = SHUTDOWN_MESSAGE; var6 = false; } finally { if (var6) { Thread thread = new Thread(this::performShutdown); thread.setContextClassLoader(this.getClass().getClassLoader()); thread.start(); } } Thread thread = new Thread(this::performShutdown); thread.setContextClassLoader(this.getClass().getClassLoader()); thread.start(); return var1; } } private void performShutdown() { try { Thread.sleep(500L); } catch (InterruptedException var2) { Thread.currentThread().interrupt(); } this.context.close(); //這里才是核心 }}

在調(diào)用了 this.context.close() ,其實就是AbstractApplicationContext 的close() 方法 (重點是其中的doClose())

/** * Close this application context, destroying all beans in its bean factory. * <p>Delegates to {@code doClose()} for the actual closing procedure. * Also removes a JVM shutdown hook, if registered, as it’s not needed anymore. * @see #doClose() * @see #registerShutdownHook() */@Overridepublic void close() {synchronized (this.startupShutdownMonitor) {doClose(); //重點:銷毀bean 并執(zhí)行jvm shutdown hook// If we registered a JVM shutdown hook, we don’t need it anymore now:// We’ve already explicitly closed the context.if (this.shutdownHook != null) {try {Runtime.getRuntime().removeShutdownHook(this.shutdownHook);}catch (IllegalStateException ex) {// ignore - VM is already shutting down}}}}

后記

到這里,關(guān)于單機版本的Spring Boot優(yōu)雅停機就說完了。為什么說單機?因為大家也能發(fā)現(xiàn),在關(guān)閉時,其實只是保證了服務(wù)端內(nèi)部線程執(zhí)行完畢,調(diào)用方的狀態(tài)是沒關(guān)注的。

不論是Dubbo還是Cloud 的分布式服務(wù)框架,需要關(guān)注的是怎么能在服務(wù)停止前,先將提供者在注冊中心進行反注冊,然后在停止服務(wù)提供者,這樣才能保證業(yè)務(wù)系統(tǒng)不會產(chǎn)生各種503、timeout等現(xiàn)象。

好在當(dāng)前Spring Boot 結(jié)合Kubernetes已經(jīng)幫我們搞定了這一點,也就是Spring Boot 2.3版本新功能Liveness(存活狀態(tài)) 和Readiness(就緒狀態(tài))

簡單的提下這兩個狀態(tài):

Liveness(存活狀態(tài)):Liveness 狀態(tài)來查看內(nèi)部情況可以理解為health check,如果Liveness失敗就就意味著應(yīng)用處于故障狀態(tài)并且目前無法恢復(fù),這種情況就重啟吧。此時Kubernetes如果存活探測失敗將殺死Container。 Readiness(就緒狀態(tài)):用來告訴應(yīng)用是否已經(jīng)準(zhǔn)備好接受客戶端請求,如果Readiness未就緒那么k8s就不能路由流量過來。

到此這篇關(guān)于Spring Boot最新版優(yōu)雅停機的文章就介紹到這了,更多相關(guān)Spring Boot優(yōu)雅停機內(nèi)容請搜索好吧啦網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持好吧啦網(wǎng)!

標(biāo)簽: Spring
相關(guān)文章:
主站蜘蛛池模板: 永新县| 余姚市| 霍州市| 沅江市| 通许县| 定西市| 宝坻区| 西盟| 龙陵县| 彭泽县| 石城县| 大化| 吉林省| 廊坊市| 乌苏市| 沙湾县| 蓬莱市| 巨野县| 巧家县| 昌都县| 桦川县| 罗平县| 乌海市| 陆川县| 中江县| 筠连县| 出国| 鸡泽县| 邳州市| 华池县| 衢州市| 桂平市| 礼泉县| 乡城县| 邢台市| 高阳县| 武城县| 渝中区| 静乐县| 黎城县| 东源县|