日本综合一区二区|亚洲中文天堂综合|日韩欧美自拍一区|男女精品天堂一区|欧美自拍第6页亚洲成人精品一区|亚洲黄色天堂一区二区成人|超碰91偷拍第一页|日韩av夜夜嗨中文字幕|久久蜜综合视频官网|精美人妻一区二区三区

RELATEED CONSULTING
相關(guān)咨詢
選擇下列產(chǎn)品馬上在線溝通
服務(wù)時(shí)間:8:30-17:00
你可能遇到了下面的問題
關(guān)閉右側(cè)工具欄

新聞中心

這里有您想知道的互聯(lián)網(wǎng)營銷解決方案
如何在SpringBoot優(yōu)雅關(guān)閉加入一些自定義機(jī)制

我們知道從 Spring Boot 2.3.x 這個(gè)版本開始,引入了優(yōu)雅關(guān)閉的機(jī)制。我們也在線上部署了這個(gè)機(jī)制,來增加用戶體驗(yàn)。雖然現(xiàn)在大家基本上都通過最終一致性,以及事務(wù)等機(jī)制,來保證了就算非優(yōu)雅關(guān)閉,也可以保持業(yè)務(wù)正確。但是,這樣總會(huì)帶來短時(shí)間的數(shù)據(jù)不一致,影響用戶體驗(yàn)。所以,引入優(yōu)雅關(guān)閉,保證當(dāng)前請(qǐng)求處理完,再開始 Destroy 所有 ApplicationContext 中的 Bean。

創(chuàng)新互聯(lián)主營索縣網(wǎng)站建設(shè)的網(wǎng)絡(luò)公司,主營網(wǎng)站建設(shè)方案,手機(jī)APP定制開發(fā),索縣h5微信小程序定制開發(fā)搭建,索縣網(wǎng)站營銷推廣歡迎索縣等地區(qū)企業(yè)咨詢

優(yōu)雅關(guān)閉存在的問題

ApplicationContext 的關(guān)閉過程簡(jiǎn)單來說分為以下幾個(gè)步驟(對(duì)應(yīng)源碼AbstractApplicationContext 的 doClose 方法):

  1. 取消當(dāng)前 ApplicationContext 在 LivBeanView 的注冊(cè)(目前其實(shí)只包含從 JMX 上取消注冊(cè))。
  2. 發(fā)布 ContextClosedEvent 事件,同步處理所有這個(gè)事件的 Listener。
  3. 處理所有實(shí)現(xiàn) Lifecycle 接口的 Bean,解析他們的關(guān)閉順序,并調(diào)用他們的 stop 方法。
  4. Destroy 所有 ApplicationContext 中的 Bean。
  5. 關(guān)閉 BeanFactory。

簡(jiǎn)單理解優(yōu)雅關(guān)閉,其實(shí)就是在上面的第三步中加入優(yōu)雅關(guān)閉的邏輯實(shí)現(xiàn)的 Lifecycle,包括如下兩步:

  • 切斷外部流量入口:具體點(diǎn)說就是讓 Spring Boot 的 Web 容器直接拒絕所有新收到的請(qǐng)求,不再處理新請(qǐng)求,例如直接返回 503。
  • 等待承載的 Dispatcher 的線程池處理完所有請(qǐng)求:對(duì)于同步的 Servlet 進(jìn)程其實(shí)就是處理 Servlet 請(qǐng)求的線程池,對(duì)于異步響應(yīng)式的 WebFlux 進(jìn)程其實(shí)就是所有 Web 請(qǐng)求的 Reactor 線程池處理完當(dāng)前所有 Publisher 發(fā)布的事件。

首先,切斷外部流量入口保證不再有新的請(qǐng)求到來,線程池處理完所有請(qǐng)求之后,正常的業(yè)務(wù)邏輯也是正常走完的,在這之后就可以開始關(guān)閉其他各種元素了。

但是,我們首先要保證,優(yōu)雅關(guān)閉的邏輯,需要在所有的 Lifecycle 的第一個(gè)最保險(xiǎn)。這樣保證一定所有請(qǐng)求處理完,才會(huì)開始 stop 其他的 Lifecycle。如果不這樣會(huì)有啥問題呢?舉個(gè)例子,例如某個(gè) Lifecycle 是負(fù)載均衡器的,stop 方法會(huì)關(guān)閉負(fù)載均衡器,如果這個(gè) Lifecycle 在優(yōu)雅關(guān)閉的 Lifecycle 的 stop 之前進(jìn)行 stop,那么可能會(huì)造成某些在 負(fù)載均衡器 stop 后還沒處理完的請(qǐng)求,并且這些請(qǐng)求需要使用負(fù)載均衡器調(diào)用其他微服務(wù),執(zhí)行失敗。

優(yōu)雅關(guān)閉還有另一個(gè)問題就是,默認(rèn)的優(yōu)雅關(guān)閉功能不是那么全面,有時(shí)候我們需要在此基礎(chǔ)上,添加更多的關(guān)閉邏輯。例如,你的項(xiàng)目中不止 有 web 容器處理請(qǐng)求的線程池,你自己還使用了其他線程池,并且線程池可能還比較復(fù)雜,一個(gè)向另一個(gè)提交,互相提交,各種提交等等,我們需要在 web 容器處理請(qǐng)求的線程池處理完所有請(qǐng)求后,再等待這些線程池的執(zhí)行完所有請(qǐng)求后再關(guān)閉。還有一個(gè)例子就是針對(duì) MQ 消費(fèi)者的,當(dāng)優(yōu)雅關(guān)閉時(shí),其實(shí)應(yīng)該停止消費(fèi)新的消息,等待當(dāng)前所有消息處理完。這些問題可以看下圖:

源碼分析接入點(diǎn) - Spring Boot + Undertow & 同步 Servlet 環(huán)境

我們從源碼觸發(fā),分析在 Spring Boot 中使用 Undertow 作為 Web 容器并且是同步 Servlet 環(huán)境下,如果接入自定義的機(jī)制。首先,在引入 spring boot 相關(guān)依賴并且配置好優(yōu)雅關(guān)閉之后:

pom.xml:


org.springframework.boot
spring-boot-starter-web



org.springframework.boot
spring-boot-starter-tomcat





org.springframework.boot
spring-boot-starter-undertow

application.yml:

server:
# 設(shè)置關(guān)閉方式為優(yōu)雅關(guān)閉
shutdown: graceful
management:
endpoint:
health:
show-details: always
# actuator 暴露 /actuator/shutdown 接口用于關(guān)閉(由于這里開啟了優(yōu)雅關(guān)閉所以其實(shí)是優(yōu)雅關(guān)閉)
shutdown:
enabled: true
endpoints:
jmx:
exposure:
exclude: '*'
web:
exposure:
include: '*'

在設(shè)置關(guān)閉方式為優(yōu)雅關(guān)閉之后,Spring Boot 啟動(dòng)時(shí),在創(chuàng)建基于 Undertow 實(shí)現(xiàn)的 WebServer 的時(shí)候,會(huì)添加優(yōu)雅關(guān)閉的 Handler,參考源碼:

UndertowWebServerFactoryDelegate:

static List createHttpHandlerFactories(Compression compression, boolean useForwardHeaders,
String serverHeader, Shutdown shutdown, HttpHandlerFactory... initialHttpHandlerFactories) {
List factories = new ArrayList<>(Arrays.asList(initialHttpHandlerFactories));
if (compression != null && compression.getEnabled()) {
factories.add(new CompressionHttpHandlerFactory(compression));
}
if (useForwardHeaders) {
factories.add(Handlers::proxyPeerAddress);
}
if (StringUtils.hasText(serverHeader)) {
factories.add((next) -> Handlers.header(next, "Server", serverHeader));
}
//如果指定了優(yōu)雅關(guān)閉,則添加 gracefulShutdown
if (shutdown == Shutdown.GRACEFUL) {
factories.add(Handlers::gracefulShutdown);
}
return factories;
}

添加的這個(gè) Handler 就是 Undertow 的 GracefulShutdownHandler,GracefulShutdownHandler 是一個(gè) HttpHandler,這個(gè)接口很簡(jiǎn)單:

public interface HttpHandler {
void handleRequest(HttpServerExchange exchange) throws Exception;
}

其實(shí)就是對(duì)于收到的每個(gè) HTTP 請(qǐng)求,都會(huì)經(jīng)過每個(gè) HttpHandler 的 handleRequest 方法。GracefulShutdownHandler 的實(shí)現(xiàn)思路也很簡(jiǎn)單,既然每個(gè)請(qǐng)求都會(huì)經(jīng)過這個(gè)類的 handleRequest 方法,那么我就在收到請(qǐng)求的時(shí)候?qū)⒁粋€(gè)原子計(jì)數(shù)器原子 + 1,請(qǐng)求處理完后(注意是返回響應(yīng)之后,不是方法返回,因?yàn)檎?qǐng)求可能是異步的,所以這個(gè)做成了回調(diào)),將原子計(jì)數(shù)器原子 - 1,如果這個(gè)計(jì)數(shù)器為零,就證明沒有任何正在處理的請(qǐng)求了。源碼是:

GracefulShutdownHandler:

@Override
public void handleRequest(HttpServerExchange exchange) throws Exception {
//原子更新,請(qǐng)求計(jì)數(shù)器加一,返回的 snapshot 是包含是否關(guān)閉狀態(tài)位的數(shù)字
long snapshot = stateUpdater.updateAndGet(this, incrementActive);
//通過狀態(tài)位判斷是否正在關(guān)閉
if (isShutdown(snapshot)) {
//如果正在關(guān)閉,直接請(qǐng)求數(shù)原子減一
decrementRequests();
//設(shè)置響應(yīng)碼為 503
exchange.setStatusCode(StatusCodes.SERVICE_UNAVAILABLE);
//標(biāo)記請(qǐng)求完成
exchange.endExchange();
//直接返回,不繼續(xù)走其他的 HttpHandler
return;
}
//添加請(qǐng)求完成時(shí)候的 listener,這個(gè)在請(qǐng)求完成返回響應(yīng)時(shí)會(huì)被調(diào)用,將計(jì)數(shù)器原子減一
exchange.addExchangeCompleteListener(listener);
//繼續(xù)走下一個(gè) HttpHandler
next.handleRequest(exchange);
}

那么,是什么時(shí)候調(diào)用的這個(gè)關(guān)閉呢?前面我們說過 ApplicationContext 的關(guān)閉過程的第三步:處理所有實(shí)現(xiàn) Lifecycle 接口的 Bean,解析他們的關(guān)閉順序,并調(diào)用他們的 stop 方法,其實(shí)優(yōu)雅關(guān)閉就在這里被調(diào)用。當(dāng) Spring Boot + Undertow & 同步 Servlet 環(huán)境啟動(dòng)時(shí),到了創(chuàng)建 WebServer 這一步,會(huì)創(chuàng)建一個(gè)優(yōu)雅關(guān)閉的 Lifecycle,對(duì)應(yīng)源碼:

ServletWebServerApplicationContext:

private void createWebServer() {
WebServer webServer = this.webServer;
ServletContext servletContext = getServletContext();
if (webServer == null && servletContext == null) {
StartupStep createWebServer = this.getApplicationStartup().start("spring.boot.webserver.create");
ServletWebServerFactory factory = getWebServerFactory();
createWebServer.tag("factory", factory.getClass().toString());
this.webServer = factory.getWebServer(getSelfInitializer());
createWebServer.end();
//就是這里,創(chuàng)建一個(gè) WebServerGracefulShutdownLifecycle 并注冊(cè)到當(dāng)前 ApplicationContext 的 BeanFactory 中
getBeanFactory().registerSingleton("webServerGracefulShutdown",
new WebServerGracefulShutdownLifecycle(this.webServer));
getBeanFactory().registerSingleton("webServerStartStop",
new WebServerStartStopLifecycle(this, this.webServer));
}
else if (servletContext != null) {
try {
getSelfInitializer().onStartup(servletContext);
}
catch (ServletException ex) {
throw new ApplicationContextException("Cannot initialize servlet context", ex);
}
}
initPropertySources();
}

前面說到, ApplicationContext 的關(guān)閉過程的第三步調(diào)用所有 Lifecycle 的 stop 方法,這里即WebServerGracefulShutdownLifecycle 中的 stop 方法:

WebServerGracefulShutdownLifecycle:

@Override
public void stop(Runnable callback) {
this.running = false;
this.webServer.shutDownGracefully((result) -> callback.run());
}

這里的 webServer,由于我們使用的是 Undertow,對(duì)應(yīng)實(shí)現(xiàn)就是 UndertowWebServer,看一下他的 shutDownGracefully 實(shí)現(xiàn):

UndertowWebServer:

//這里的這個(gè) GracefulShutdownHandler 就是前面說的在啟動(dòng)時(shí)加的 GracefulShutdownHandler
private volatile GracefulShutdownHandler gracefulShutdown;
@Override
public void shutDownGracefully(GracefulShutdownCallback callback) {
// 如果 GracefulShutdownHandler 不為 null,證明開啟了優(yōu)雅關(guān)閉(server.shutdown=graceful)
if (this.gracefulShutdown == null) {
//為 null,就證明沒開啟優(yōu)雅關(guān)閉,什么都不等
callback.shutdownComplete(GracefulShutdownResult.IMMEDIATE);
return;
}
//開啟優(yōu)雅關(guān)閉,需要等待請(qǐng)求處理完
logger.info("Commencing graceful shutdown. Waiting for active requests to complete");
this.gracefulShutdownCallback.set(callback);
//調(diào)用 GracefulShutdownHandler 的 shutdown 進(jìn)行優(yōu)雅關(guān)閉
this.gracefulShutdown.shutdown();
//調(diào)用 GracefulShutdownHandler 的 addShutdownListener 添加關(guān)閉后調(diào)用的操作,這里是調(diào)用 notifyGracefulCallback
//其實(shí)就是調(diào)用方法參數(shù)的 callback(就是外部的回調(diào))
this.gracefulShutdown.addShutdownListener((success) -> notifyGracefulCallback(success));
}
private void notifyGracefulCallback(boolean success) {
GracefulShutdownCallback callback = this.gracefulShutdownCallback.getAndSet(null);
if (callback != null) {
if (success) {
logger.info("Graceful shutdown complete");
callback.shutdownComplete(GracefulShutdownResult.IDLE);
}
else {
logger.info("Graceful shutdown aborted with one or more requests still active");
callback.shutdownComplete(GracefulShutdownResult.REQUESTS_ACTIVE);
}
}
}

再看下 GracefulShutdownHandler 的 shutdown 方法以及 addShutdownListener 方法:

GracefulShutdownHandler:

public void shutdown() {
//設(shè)置關(guān)閉狀態(tài)位,并原子 + 1
stateUpdater.updateAndGet(this, incrementActiveAndShutdown);
//直接請(qǐng)求數(shù)原子減一
decrementRequests();
}
private void decrementRequests() {
long snapshot = stateUpdater.updateAndGet(this, decrementActive);
// Shutdown has completed when the activeCount portion is zero, and shutdown is set.
//如果與 關(guān)閉狀態(tài)位 MASK 完全相等,證明其他位都是 0,證明剩余處理中的請(qǐng)求數(shù)量為 0
if (snapshot == SHUTDOWN_MASK) {
//調(diào)用 shutdownComplete
shutdownComplete();
}
}
private void shutdownComplete() {
synchronized (lock) {
lock.notifyAll();
//調(diào)用每個(gè) ShutdownListener 的 shutdown 方法
for (ShutdownListener listener : shutdownListeners) {
listener.shutdown(true);
}
shutdownListeners.clear();
}
}
/**
* 這個(gè)方法并不只是字面意思,首先如果不是關(guān)閉中不能添加 ShutdownListener
* 然后如果沒有請(qǐng)求了,就直接調(diào)用傳入的 shutdownListener 的 shutdown 方法
* 如果還有請(qǐng)求,則添加入 shutdownListeners,等其他調(diào)用 shutdownComplete 的時(shí)候遍歷 shutdownListeners 調(diào)用 shutdown
* lock 主要為了 addShutdownListener 與 shutdownComplete 對(duì) shutdownListeners 的訪問安全
* lock 的 wait notify 主要為了實(shí)現(xiàn) awaitShutdown 機(jī)制,我們這里沒有提
*/
public void addShutdownListener(final ShutdownListener shutdownListener) {
synchronized (lock) {
if (!isShutdown(stateUpdater.get(this))) {
throw UndertowMessages.MESSAGES.handlerNotShutdown();
}
long count = activeCount(stateUpdater.get(this));
if (count == 0) {
shutdownListener.shutdown(true);
} else {
shutdownListeners.add(shutdownListener);
}
}
}

這就是優(yōu)雅關(guān)閉的底層原理,但是我們還沒有分析清楚 ApplicationContext 的關(guān)閉過程的第三步以及優(yōu)雅關(guān)閉與其他 Lifecycle Bean 的 stop 先后順序,我們這里來理清一下,首先我們看一下 Smart開始關(guān)閉 Lifecycle Bean 的入口:

DefaultLifecycleProcessor:

private void stopBeans() {
//讀取所有的 Lifecycle bean,返回的是一個(gè) LinkedHashMap,遍歷它的順序和放入的順序一樣
//放入的順序就是從 BeanFactory 讀取所有 Lifecycle 的 Bean 的返回順序,這個(gè)和 Bean 加載順序有關(guān),不太可控,可能這個(gè)版本加載順序升級(jí)一個(gè)版本就變了
Map lifecycleBeans = getLifecycleBeans();
//按照每個(gè) Lifecycle 的 Phase 值進(jìn)行分組
//如果實(shí)現(xiàn)了 Phased 接口就通過其 phase 方法返回得出 phase 值
//如果沒有實(shí)現(xiàn) Phased 接口則認(rèn)為 Phase 是 0
Map phases = new HashMap<>();
lifecycleBeans.forEach((beanName, bean) -> {
int shutdownPhase = getPhase(bean);
LifecycleGroup group = phases.get(shutdownPhase);
if (group == null) {
group = new LifecycleGroup(shutdownPhase, this.timeoutPerShutdownPhase, lifecycleBeans, false);
phases.put(shutdownPhase, group);
}
group.add(beanName, bean);
});
//如果不為空,證明有需要關(guān)閉的 Lifecycle,開始關(guān)閉
if (!phases.isEmpty()) {
//按照 Phase 值倒序
List keys = new ArrayList<>(phases.keySet());
keys.sort(Collections.reverseOrder());
//挨個(gè)關(guān)閉
for (Integer key : keys) {
phases.get(key).stop();
}
}
}

總結(jié)起來,其實(shí)就是:

  1. 獲取當(dāng)前 ApplicationContext 的 Beanfactory 中的所有實(shí)現(xiàn)了 Lifecycle 接口的 Bean。
  2. 讀取每個(gè) Bean 的 Phase 值,如果這個(gè) Bean 實(shí)現(xiàn)了 Phased 接口,就取接口方法返回的值,如果沒有實(shí)現(xiàn)就是 0。
  3. 按照 Phase 值將 Bean 分組。
  4. 按照 Phase 值從大到小的順序,依次遍歷每組進(jìn)行關(guān)閉。
  5. 具體關(guān)閉每組的邏輯我們就不詳細(xì)看代碼了,知道關(guān)閉的時(shí)候其實(shí)還看了當(dāng)前這個(gè) Lifecycle 的 Bean 是否還依賴了其他的 Lifecycle 的 Bean,如果依賴了,優(yōu)先關(guān)掉被依賴的 Lifecycle Bean。

我們來看下前面提到的優(yōu)雅關(guān)閉相關(guān)的。

WebServerGracefulShutdownLifecycle 的 Phase 是:

class WebServerGracefulShutdownLifecycle implements SmartLifecycle {
....
}

SmartLifecycle 包含了 Phased 接口以及默認(rèn)實(shí)現(xiàn):

public interface SmartLifecycle extends Lifecycle, Phased {
int DEFAULT_PHASE = Integer.MAX_VALUE;
@Override
default int getPhase() {
return DEFAULT_PHASE;
}
}

可以看出,只要實(shí)現(xiàn)了 SmartLifecycle,Phase 默認(rèn)就是最大值。所以優(yōu)雅關(guān)閉的 Lifecycle:

WebServerGracefulShutdownLifecycle 的 Phase 就是最大值,也就是屬于最先被關(guān)閉的那一組。

總結(jié)接入點(diǎn) - Spring Boot + Undertow & 同步 Servlet 環(huán)境。

接入點(diǎn)一 - 通過添加實(shí)現(xiàn) SmartLifecycle 接口的 Bean,指定 Phase 比 WebServerGracefulShutdownLifecycle 的 Phase 小。

前面的分析中,我們已經(jīng)知道了:

WebServerGracefulShutdownLifecycle 的 Phase 就是最大值,也就是屬于最先被關(guān)閉的那一組。我們想要實(shí)現(xiàn)的是在這之后加入一些優(yōu)雅關(guān)閉的邏輯,同時(shí)在 Destroy Bean (前面提到的 ApplicationContext 關(guān)閉的第四步)之前(即 Bean 銷毀之前,某些 Bean 銷毀中就不能用了,比如微服務(wù)調(diào)用中的一些 Bean,這時(shí)候如果還有任務(wù)沒完成調(diào)用他們就會(huì)報(bào)異常)。那我們首先想到的就是加入一個(gè) Phase 在這時(shí)候的 Lifecycle,在里面實(shí)現(xiàn)我們的優(yōu)雅關(guān)閉接入,例如:

@Log4j2
@Component
public class BizThreadPoolShutdownLifecycle implements SmartLifecycle {
private volatile boolean running = false;
@Override
public int getPhase() {
//在 WebServerGracefulShutdownLifecycle 那一組之后
return SmartLifecycle.DEFAULT_PHASE - 1;
}
@Override
public void start() {
this.running = true;
}
@Override
public void stop() {
//在這里編寫的優(yōu)雅關(guān)閉邏輯
this.running = false;
}
@Override
public boolean isRunning() {
return running;
}
}

這樣實(shí)現(xiàn)兼容性比較好,并且升級(jí)底層框架依賴版本基本上不用修改。但是問題就是,可能會(huì)引入某個(gè)框架里面帶 Lifecycle bean,雖然他的 Phase 是正確的,小于

WebServerGracefulShutdownLifecycle 的,但是 SmartLifecycle.DEFAULT_PHASE - 1 即等于我們自定義的 Lifecyce, 并且這個(gè)正好是需要等待我們的優(yōu)雅關(guān)閉結(jié)束再關(guān)閉的,并且由于 Bean 加載順序問題導(dǎo)致框架的 Lifecycle 又跑到了我們自定義的 Lifecycle 前進(jìn)行 stop。這樣就會(huì)有問題,但是問題出現(xiàn)的概率并不大。

接入點(diǎn)二 - 通過反射向 Undertow 的 GracefulShutdownHandler 的List shutdownListeners中添加 ShutdownListener 實(shí)現(xiàn)。

這種實(shí)現(xiàn)方式,很明顯,限定了容器必須是 undertow,并且可能升級(jí)的兼容性不好。但是可以在 Http 線程池優(yōu)雅關(guān)閉后立刻執(zhí)行我們的優(yōu)雅關(guān)閉邏輯,不用擔(dān)心引入某個(gè)依賴導(dǎo)致我們自定義的優(yōu)雅關(guān)閉順序有問題。與第一種孰優(yōu)孰劣,請(qǐng)大家自行判斷,簡(jiǎn)單實(shí)現(xiàn)是:

@Log4j2
@Componenet
//僅在包含 Undertow 這個(gè)類的時(shí)候加載 分享標(biāo)題:如何在SpringBoot優(yōu)雅關(guān)閉加入一些自定義機(jī)制
文章地址:http://www.dlmjj.cn/article/dpdeheg.html