Spring Cloud Gateway 啟動(dòng)流程源碼分析
以下分析以 spring-cloud-starter-gateway 4.1.0 源碼為分析樣本。
配置和啟動(dòng)類
如果我們要使用 Spring Cloud Gateway,需要在pom里引入如下依賴:
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-gateway</artifactId> <version>4.1.0</version> </dependency>
spring-cloud-starter-gateway里的依賴如下:
<dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-gateway-server</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-webflux</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-loadbalancer</artifactId> <optional>true</optional> </dependency> </dependencies>
看上面引入了 spring-boot-starter-webflux,為后面分析做鋪墊;
除了引入依賴,我們還需要有一個(gè)啟動(dòng)類,如下:
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
@EnableDiscoveryClient
@Slf4j
@SpringBootApplication
public class XXGatewayApp {
public static void main(String[] args) {
SpringApplication.run(XXGatewayApp.class, args);
log.info("XX網(wǎng)關(guān)啟動(dòng)成功!");
}
}啟動(dòng) nettyserver
在主類啟動(dòng)后,是如何啟動(dòng)一個(gè)nettyserver的,我們來(lái)分析一下;跟蹤啟動(dòng)類代碼來(lái)到如下方法:
org.springframework.boot.SpringApplication#run(java.lang.String…)
public ConfigurableApplicationContext run(String... args) {
Startup startup = SpringApplication.Startup.create();
if (this.registerShutdownHook) {
shutdownHook.enableShutdownHookAddition();
}
... 省略代碼...
try {
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
ConfigurableEnvironment environment = this.prepareEnvironment(listeners, bootstrapContext, applicationArguments);
Banner printedBanner = this.printBanner(environment);
// @A1 創(chuàng)建 ConfigurableApplicationContext
context = this.createApplicationContext();
context.setApplicationStartup(this.applicationStartup);
this.prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
// @A2 觸發(fā)創(chuàng)建server
this.refreshContext(context);
... 省略代碼...
} catch (Throwable ex) {
if (ex instanceof AbandonedRunException) {
throw ex;
}
this.handleRunFailure(context, ex, listeners);
throw new IllegalStateException(ex);
}
try {
if (context.isRunning()) {
listeners.ready(context, startup.ready());
}
return context;
} catch (Throwable ex) {
if (ex instanceof AbandonedRunException) {
throw ex;
} else {
this.handleRunFailure(context, ex, (SpringApplicationRunListeners)null);
throw new IllegalStateException(ex);
}
}
}@A1:這個(gè)方法會(huì)執(zhí)行以下邏輯
org.springframework.boot.WebApplicationType#deduceFromClasspath
計(jì)算web容器類型,而最上面的pom依賴引入了 spring-boot-starter-webflux
static WebApplicationType deduceFromClasspath() {
if (ClassUtils.isPresent("org.springframework.web.reactive.DispatcherHandler", (ClassLoader)null) && !ClassUtils.isPresent("org.springframework.web.servlet.DispatcherServlet", (ClassLoader)null) && !ClassUtils.isPresent("org.glassfish.jersey.servlet.ServletContainer", (ClassLoader)null)) {
return REACTIVE;
} else {
for(String className : SERVLET_INDICATOR_CLASSES) {
if (!ClassUtils.isPresent(className, (ClassLoader)null)) {
return NONE;
}
}
return SERVLET;
}
}上面代碼根據(jù)類路徑中加入的依賴,返回REACTIVE,最終返回 org.springframework.boot.web.reactive.context.AnnotationConfigReactiveWebServerApplicationContext 對(duì)象
創(chuàng)建websever
接上面 @A2方法,會(huì)觸發(fā)AnnotationConfigReactiveWebServerApplicationContext如下調(diào)用:
注:AnnotationConfigReactiveWebServerApplicationContext父類是
ReactiveWebServerApplicationContext#createWebServer
private void createWebServer() {
WebServerManager serverManager = this.serverManager;
if (serverManager == null) {
StartupStep createWebServer = this.getApplicationStartup().start("spring.boot.webserver.create");
String webServerFactoryBeanName = this.getWebServerFactoryBeanName();
//@B1 創(chuàng)建ReactiveWebServerFactory ,創(chuàng)建server的核心點(diǎn)
ReactiveWebServerFactory webServerFactory = this.getWebServerFactory(webServerFactoryBeanName);
createWebServer.tag("factory", webServerFactory.getClass().toString());
boolean lazyInit = this.getBeanFactory().getBeanDefinition(webServerFactoryBeanName).isLazyInit();
//@B2 WebServerManager構(gòu)造方法創(chuàng)建server
this.serverManager = new WebServerManager(this, webServerFactory, this::getHttpHandler, lazyInit);
this.getBeanFactory().registerSingleton("webServerGracefulShutdown", new WebServerGracefulShutdownLifecycle(this.serverManager.getWebServer()));
//@B3 很絕的方法
this.getBeanFactory().registerSingleton("webServerStartStop", new WebServerStartStopLifecycle(this.serverManager));
createWebServer.end();
}
this.initPropertySources();
}@B1 方法很繞,會(huì)從ReactiveWebServerFactoryConfiguration里去獲得NettyReactiveWebServerFactory的bean定義,而這個(gè)bean定義依賴 ReactorResourceFactory ,代碼如下:
@Bean
NettyReactiveWebServerFactory nettyReactiveWebServerFactory(ReactorResourceFactory resourceFactory, ObjectProvider<NettyRouteProvider> routes, ObjectProvider<NettyServerCustomizer> serverCustomizers) {
NettyReactiveWebServerFactory serverFactory = new NettyReactiveWebServerFactory();
serverFactory.setResourceFactory(resourceFactory);
Stream var10000 = routes.orderedStream();
Objects.requireNonNull(serverFactory);
var10000.forEach((xva$0) -> serverFactory.addRouteProviders(new NettyRouteProvider[]{xva$0}));
serverFactory.getServerCustomizers().addAll(serverCustomizers.orderedStream().toList());
return serverFactory;
}也就是說(shuō)在容器返回NettyReactiveWebServerFactory 對(duì)象前會(huì)把ReactorResourceFactory 對(duì)象初始化完畢;ReactorResourceFactory 這個(gè)類實(shí)現(xiàn)了InitializingBean,我們看看afterPropertiesSet方法初始化內(nèi)容:
public void start() {
synchronized(this.lifecycleMonitor) {
if (!this.isRunning()) {
if (!this.useGlobalResources) {
if (this.loopResources == null) {
this.manageLoopResources = true;
this.loopResources = (LoopResources)this.loopResourcesSupplier.get();
}
if (this.connectionProvider == null) {
this.manageConnectionProvider = true;
this.connectionProvider = (ConnectionProvider)this.connectionProviderSupplier.get();
}
} else {
Assert.isTrue(this.loopResources == null && this.connectionProvider == null, "'useGlobalResources' is mutually exclusive with explicitly configured resources");
// @C1
HttpResources httpResources = HttpResources.get();
if (this.globalResourcesConsumer != null) {
this.globalResourcesConsumer.accept(httpResources);
}
this.connectionProvider = httpResources;
this.loopResources = httpResources;
}
this.running = true;
}
}
}@C1方法最終執(zhí)行的是 reactor.netty.resources.LoopResources#create(java.lang.String)
static LoopResources create(String prefix) {
if (((String)Objects.requireNonNull(prefix, "prefix")).isEmpty()) {
throw new IllegalArgumentException("Cannot use empty prefix");
} else {
return new DefaultLoopResources(prefix, DEFAULT_IO_SELECT_COUNT, DEFAULT_IO_WORKER_COUNT, true);
}
}是不是很熟悉了,是創(chuàng)建的reactor.netty.resources.DefaultLoopResources 對(duì)象
todo~~
@B2 方法會(huì)調(diào)用到如下方法:
org.springframework.boot.web.embedded.netty.NettyReactiveWebServerFactory#getWebServer
public WebServer getWebServer(HttpHandler httpHandler) {
// @D1 創(chuàng)建 HttpServer
HttpServer httpServer = this.createHttpServer();
ReactorHttpHandlerAdapter handlerAdapter = new ReactorHttpHandlerAdapter(httpHandler);
NettyWebServer webServer = this.createNettyWebServer(httpServer, handlerAdapter, this.lifecycleTimeout, this.getShutdown());
webServer.setRouteProviders(this.routeProviders);
return webServer;
}@D1 方法會(huì)執(zhí)行到如下方法: org.springframework.boot.web.embedded.netty.NettyReactiveWebServerFactory#createHttpServer
private HttpServer createHttpServer() {
HttpServer server = HttpServer.create().bindAddress(this::getListenAddress);
if (Ssl.isEnabled(this.getSsl())) {
server = this.customizeSslConfiguration(server);
}
if (this.getCompression() != null && this.getCompression().getEnabled()) {
CompressionCustomizer compressionCustomizer = new CompressionCustomizer(this.getCompression());
server = compressionCustomizer.apply(server);
}
server = server.protocol(this.listProtocols()).forwarded(this.useForwardHeaders);
return this.applyCustomizers(server);
}繼續(xù)分析上面:@B3
這個(gè)地方太絕了,向容器中注入了一個(gè) WebServerStartStopLifecycle,這種類型的類會(huì)被框架中觸發(fā)start方法,觸發(fā)一些列的start方法,最終執(zhí)行的是 reactor.netty.http.server.HttpServerBind父類 ----> HttpServer 父類 -----> reactor.netty.transport.ServerTransport的 bindNow方法 ----> bind方法。
重要方法 reactor.netty.transport.ServerTransport#bind
public Mono<? extends DisposableServer> bind() {
CONF config = (CONF)(this.configuration());
Objects.requireNonNull(config.bindAddress(), "bindAddress");
Mono<? extends DisposableServer> mono = Mono.create((sink) -> {
SocketAddress local = (SocketAddress)Objects.requireNonNull((SocketAddress)config.bindAddress().get(), "Bind Address supplier returned null");
if (local instanceof InetSocketAddress) {
InetSocketAddress localInet = (InetSocketAddress)local;
if (localInet.isUnresolved()) {
local = AddressUtils.createResolved(localInet.getHostName(), localInet.getPort());
}
}
boolean isDomainSocket = false;
DisposableBind disposableServer;
if (local instanceof DomainSocketAddress) {
isDomainSocket = true;
disposableServer = new UdsDisposableBind(sink, config, local);
} else {
disposableServer = new InetDisposableBind(sink, config, local);
}
ConnectionObserver childObs = new ChildObserver(config.defaultChildObserver().then(config.childObserver()));
// @E1
Acceptor acceptor = new Acceptor(config.childEventLoopGroup(), config.channelInitializer(childObs, (SocketAddress)null, true), config.childOptions, config.childAttrs, isDomainSocket);
// @E2
TransportConnector.bind(config, new AcceptorInitializer(acceptor), local, isDomainSocket).subscribe(disposableServer);
});
if (config.doOnBind() != null) {
mono = mono.doOnSubscribe((s) -> config.doOnBind().accept(config));
}
return mono;
}@E1:最終調(diào)用 reactor.netty.resources.DefaultLoopResources#cacheNioServerLoops 獲得work線程池
@E2:最終調(diào)用 reactor.netty.resources.DefaultLoopResources#cacheNioSelectLoops 獲得boss線程池
問(wèn)題來(lái)了:看reactor.netty.resources.LoopResources源碼,如果系統(tǒng)參數(shù)里沒(méi)有配置reactor.netty.ioSelectCount,則boss線程會(huì)和work線程池的AtomicReference在cacheNioSelectLoops 方法中返回同一對(duì)象;這是不是會(huì)造成線程池共用?
可以參看 https://blog.csdn.net/qq_42651904/article/details/134561804 這篇文章理解;
那么你們生產(chǎn)環(huán)境會(huì)單獨(dú)設(shè)置 reactor.netty.ioSelectCount 參數(shù)嗎?
jvisualvm監(jiān)控驗(yàn)證
- 如果不設(shè)置reactor.netty.ioSelectCount 這個(gè)啟動(dòng)參數(shù),通過(guò)監(jiān)控網(wǎng)關(guān)啟動(dòng)后線程名稱是 reactor-http-nio-xx

- 如果在啟動(dòng)的時(shí)候設(shè)置這個(gè)參數(shù)為1,啟動(dòng)的線程不一樣 reactor-http-select-xx
public static void main(String[] args) {
System.setProperty("reactor.netty.ioSelectCount", "1");
SpringApplication.run(XXXGatewayApp.class, args);
log.info("XXX網(wǎng)關(guān)啟動(dòng)成功!");
}

設(shè)想一下,如果代碼寫得不好在GlobalFilter有阻塞寫法,比如數(shù)據(jù)查詢、redis查詢,加上高并發(fā)請(qǐng)求,那么是不是會(huì)影響boss線程“接客”?
后面再通過(guò)壓測(cè)的方式論證一下。
到此這篇關(guān)于Spring Cloud Gateway 啟動(dòng)流程源碼分析的文章就介紹到這了,更多相關(guān)Spring Cloud Gateway 啟動(dòng)流程內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
MyBatis 探秘之#{} 與 ${} 參傳差異解碼(數(shù)據(jù)庫(kù)連接池筑牢數(shù)據(jù)交互
本文詳細(xì)介紹了MyBatis中的`#{}`和`${}`的區(qū)別與使用場(chǎng)景,包括預(yù)編譯SQL和即時(shí)SQL的區(qū)別、安全性問(wèn)題,以及如何正確使用數(shù)據(jù)庫(kù)連接池來(lái)提高性能,感興趣的朋友一起看看吧2024-12-12
MyBatisPlus 主鍵策略的實(shí)現(xiàn)(4種)
MyBatis Plus 集成了多種主鍵策略,幫助用戶快速生成主鍵,本文主要介紹了MyBatisPlus主鍵策略的實(shí)現(xiàn),具有一定的參考價(jià)值,感興趣的可以了解一下2023-10-10
Java(Springboot)項(xiàng)目調(diào)用第三方WebService接口實(shí)現(xiàn)代碼
這篇文章主要介紹了如何使用Java調(diào)用WebService接口,傳遞XML參數(shù),獲取XML響應(yīng),并將其解析為JSON格式,文中詳細(xì)描述了WSDL文檔的使用、HttpClientBuilder和Apache?Axis兩種調(diào)用方式的具體實(shí)現(xiàn)步驟,需要的朋友可以參考下2025-02-02
Java的MyBatis框架中對(duì)數(shù)據(jù)庫(kù)進(jìn)行動(dòng)態(tài)SQL查詢的教程
這篇文章主要介紹了Java的MyBatis框架中對(duì)數(shù)據(jù)庫(kù)進(jìn)行動(dòng)態(tài)SQL查詢的教程,講解了MyBatis中一些控制查詢流程的常用語(yǔ)句,需要的朋友可以參考下2016-04-04
什么是Maven及如何配置國(guó)內(nèi)源實(shí)現(xiàn)自動(dòng)獲取jar包的操作
本文介紹了Maven的基本概念,包括項(xiàng)目構(gòu)建、依賴管理、倉(cāng)庫(kù)管理以及如何設(shè)置國(guó)內(nèi)源,通過(guò)Maven,開(kāi)發(fā)者可以自動(dòng)化管理項(xiàng)目的依賴和構(gòu)建流程,提高開(kāi)發(fā)效率,感興趣的朋友跟隨小編一起看看吧2024-11-11
java使用POI實(shí)現(xiàn)html和word相互轉(zhuǎn)換
這篇文章主要為大家詳細(xì)介紹了java使用POI實(shí)現(xiàn)html和word的相互轉(zhuǎn)換,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-12-12
Java?EventBus手把手帶你實(shí)現(xiàn)
EventBus是Guava的事件處理機(jī)制,是設(shè)計(jì)模式中觀察者模式(生產(chǎn)/消費(fèi)者編程模型)的優(yōu)雅實(shí)現(xiàn)。本文就來(lái)和大家聊聊EventBus的使用,需要的可以參考一下2023-01-01

