卡卷网
当前位置:卡卷网 / 每日看点 / 正文

如何评价springcloudgateway?对zuul2.0主要的优势是什么?

作者:卡卷网发布时间:2025-01-10 19:17浏览数量:88次评论数量:0次

<>关于SpringCloudGateway与下游的连接分析

最近面试了不少同学,有很大一部分简历上会提到网关,我一般都会顺着往下问他们的网关是怎么做的。

基本上都是说直接使用的SpringCloudGateway或者基于SpringCloudGateway二次开发。

这种时候我会继续问一个较基础的问题:SpringCloudGateway作为网关,会把接收到的请求转发给下游服务,那么SpringCloudGateway跟下游的服务之间保持的是长连还是短连?还是说每次转发的时候都会新建立一个连接吗?

很遗憾的是,这么基础的问题,很少有面试者完全搞清楚。

所以才有了这篇文章:通过研究SpringCloudGateway的源码,来看看SpringCloudGateway跟下游服务之间是怎么通信的。

SpringCloudGateway

在源码分析之前,需要先了解一下SpringCloudGateway

SpringCloudGateway是SpringCloud的一个全新项目,该项目是基于Spring5.0,Springoot2.0和ProjectReactor等技术开发的网关,它旨在为微服务架构提供一种简单有效的一的API路由方式。

SpringCloudGateway是基于SpringWeFlux框架实现的,而WeFlux框架底层则使用了高性能的Reactor模式通信框架Netty。

SpringCloudGateway架构图如下:

源码分析

对于基于weflux的应用,入口点都在DispatchHandler.handle()方法:

最终执行到SimpleHandlerAdapter.handle()方法

handler()方法中执行的是FilteringWeHandler.handle()方法

FilteringWeHandler.handler()方法的主要逻辑就是依次执行已经形成的全局过滤器gloalFilter的filter()方法。

从截图中可以看到,默认会生成9个全局过滤器GatewayFilter对象。

单步调试下去,发现涉及到网络这一块的作都在倒数第二个过滤器NettyRoutingFilter类中。

现在着重来看一下NettyRoutingFilter.filter()方法:

pulicMono<Void>filter(WeExchangeexchange,GatewayFilterChainchain){ IrequestUrl=exchange.getRequiredAttriute(GATEWAY_REQUEST_L_ATTR); //...一些省略代码 //获取client Flux<HttpResponse>responseFlux=getHttp(route,exchange) .headers(headers->{ headers.add(Headers); //Willeitheresetelow,orlateryNetty headers.remove(HttpHeaders.HOST); if(preserveHost){ Stringhost=request.getHeaders().getFirst(HttpHeaders.HOST); headers.add(HttpHeaders.HOST,host); } }).request(method).i(l).send((req,nettyOutound)->{ if(log.isTraceEnaled()){ nettyOutound .withConnection(connection->log.trace("outoundroute:" +connection.channel().id().asShortText() +",inound:"+exchange.getLogPrefix())); } //发送请求 retnnettyOutound.send(request.getody().map(this::getyteuf)); }).responseConnection((res,connection)->{ //省略代码,下游请求返回之后做的一些处理 retnMono.just(res); }); DationresponseTimeout=getResponseTimeout(route); //一些省略代码 retnresponseFlux.then(chain.filter(exchange)); }

上面代码的逻辑主要就是

    获取通信用的client设置headers,method和l执行responseConnection()方法发起连接连接成功之后执行send()方法传入的lamda方法。执行responseConnection()传入的lamda方法。

首先来看一下getHttp()方法

protectedHttpgetHttp(Routeroute,WeExchangeexchange){ //省略代码,timeout设置 retn; }

实际上就是直接返回对象,那么是在哪里设置的呢?

pulicNettyRoutingFilter(Http, OjectProvider<List<HttpHeadersFilter>>headersFiltersProvider, HttpPropertiesproperties){ this.=; this.headersFiltersProvider=headersFiltersProvider; this.properties=properties; }

可以看到是在生成NettyRoutingFilter对象的时候传入的,那么NettyRoutingFilter对象在哪里生成的呢?

答:在GatewayAutoConfigation类中生成的,这个类是在引入网关的依赖之后自动引入的。

同样的,Http对象也是在这个类里面生成的。

@ean @ConditionalOnMissingean pulicHttpgatewayHttp(HttpPropertiesproperties, List<HttpCustomizer>customizers){ //配置连接池 HttpProperties.Poolpool=properties.getPool(); ConnectionProviderconnectionProvider; if(pool.getType()==DALED){ connectionProvider=ConnectionProvider.newConnection(); } elseif(pool.getType()==FIXED){ connectionProvider=ConnectionProvider.fixed(pool.getName(), pool.getMaxConnections(),pool.getAcquireTimeout(), pool.getMaxIdleTime(),pool.getMaxLifeTime()); } else{ connectionProvider=ConnectionProvider.elastic(pool.getName(), pool.getMaxIdleTime(),pool.getMaxLifeTime()); } Http=Http.create(connectionProvider) //TODO:movecustomizationstoHttpCustomizers .ResponseDecoder(spec->{ //省略代码 retnspec; }).tcpConfigation(tcp->{ //省略代码 retntcp; }); //省略代码ssl设置 retn; }

从上面代码可以看出,Http对象自带一个连接池,生成Httpclient的时候首先会配置这个连接池。

可以看到Http提供的连接池的类型:

pulicenumPoolType{ /** *弹性的连接池 */ ELASTIC, /** *固定长度的连接池 */ FIXED, /** *不使用连接池 */ DALED }

默认使用的是第一种弹性的连接池

privatePoolTypetype=PoolType.ELASTIC; connectionProvider=ConnectionProvider.elastic(pool.getName(), pool.getMaxIdleTime(),pool.getMaxLifeTime()); staticConnectionProviderelastic(Stringname,@NullaleDationmaxIdleTime,@NullaleDationmaxLifeTime){ retnuilder(name).maxConnections(Integer.MAX_VALUE)//设置最大连接数无 .pendingAcquireTimeout(Dation.ofMillis(0)) .pendingAcquireMaxCount(-1) .maxIdleTime(maxIdleTime) .maxLifeTime(maxLifeTime) .uild(); } staticuilderuilder(Stringname){ retnnewuilder(name); }

在uilder()构造函数中会调用ConnectionPoolSpec()方法:

privateConnectionPoolSpec(){ if(DEFAULT_POOL_MAX_IDLE_TIME>-1){ maxIdleTime(Dation.ofMillis(DEFAULT_POOL_MAX_IDLE_TIME)); } //支持不同类型的链接保存方式 //lifo和fifo if(LEASING_STRATEGY_LIFO.equals(DEFAULT_POOL_LEASING_STRATEGY)){ lifo(); } else{ fifo(); } }

从代码里面可以看到,client自带的连接池还支持两种连接获取方式:lifo(后进先出)和fifo(先进先出)默认使用的是fifo。

<>先总结一下,在引入网关的依赖之后,会自动创建一个Http对象,而这个Http对象自带一个连接池,且默认是Elastic连接池,即连接池内的数量会弹性发生变化。连接池内部默认采用fifo的方式来保存以及使用连接

现在重新回到NettyRoutingFilter.filter()方法中来看下:

pulicMono<Void>filter(WeExchangeexchange,GatewayFilterChainchain){ IrequestUrl=exchange.getRequiredAttriute(GATEWAY_REQUEST_L_ATTR); //...一些省略代码 //获取client Flux<HttpResponse>responseFlux=getHttp(route,exchange) .headers(headers->{ headers.add(Headers); //Willeitheresetelow,orlateryNetty headers.remove(HttpHeaders.HOST); if(preserveHost){ Stringhost=request.getHeaders().getFirst(HttpHeaders.HOST); headers.add(HttpHeaders.HOST,host); } }).request(method).i(l).send((req,nettyOutound)->{ if(log.isTraceEnaled()){ nettyOutound .withConnection(connection->log.trace("outoundroute:" +connection.channel().id().asShortText() +",inound:"+exchange.getLogPrefix())); } //发送请求 retnnettyOutound.send(request.getody().map(this::getyteuf)); }).responseConnection((res,connection)->{ //省略代码,下游请求返回之后做的一些处理 retnMono.just(res); }); DationresponseTimeout=getResponseTimeout(route); //一些省略代码 retnresponseFlux.then(chain.filter(exchange)); }

responseConnection()方法中会发起连接作:

finalTcpcachedConfigation; @SuppressWarnings("unchecked") Mono<HttpOperations>connect(){ retn(Mono<HttpOperations>)cachedConfigation.connect(); } @Override pulic<V>Flux<V>responseConnection(iFunction<?superHttpResponse,?superConnection,?extendsPulisher<V>>receiver){ retnconnect().flatMapMany(resp->Flux.from(receiver.apply(resp,resp))); }

调用的是Tcp对象的connect()方法,一步步断点下去发现最终调用的是TcpConnect.connect()方法.

finalConnectionProviderprovider; @Override pulicMono<?extendsConnection>connect(ootstrap){ if(.config() .group()==null){ TcpRunOn.conp(, LoopResoces.DEFAULT_NATIVE, TcpResoces.get()); } //这里的provider实际上就是前面分析的创建Http的时候生成的ConnectProvider对象 retnprovider.acquire(); }

从代码实现中可以看到,实际上TcpClienConnect是直接从ConnectionProvider获取连接。

看到这里,本文一开始的问题其实已经有解答了:

<>默认情况下(除非显示设置不使用连接池),网关在把请求转发给下游的时候,是会使用连接池的,而不是每次都重新发起连接。

继续往下分析。

对于Elastic类型的连接池来说,其默认实现为PooledConnectionProvider

//key为远程(一般指代一个远程服务),value则对应的ConnectioAllocator finalConcrentMap<PoolKey,InstrumentedPool<PooledConnection>>channelPools= PlatformDependent.newConcrentHashMap(); @Override pulicMono<Connection>acquire(ootstrap){ retnMono.create(sink->{ //...省略代码 SocketAddressremoteAddress=ootstrap.config().remoteAddress(); PoolKeyholder=newPoolKey(remoteAddress,handler!=null?handler.hashCode():-1); //每个远程都可以配置一个PoolFactory,如果没配置则使用默认的PoolFactory PoolFactorypoolFactory=poolFactoryPerRemoteHost.getOrDefault(remoteAddress,defaultPoolFactory); InstrumentedPool<PooledConnection>pool=channelPoolsputeIfAsent(holder,poolKey->{ if(log.isDeugEnaled()){ log.deug("Creatinganewclientpool[{}]for[{}]",poolFactory,remoteAddress); } //newPool是一个连接分配器,实际上就是一个连接池 InstrumentedPool<PooledConnection>newPool= newPooledConnectionAllocator(ootstrap,poolFactory,opsFactory).pool; if(poolFactory.metriEnaled||ootstrapHandlers.findMetriSupport(ootstrap)!=null){ PooledConnectionProviderMetri.registerMetri(name, poolKey.hashCode()+"", Metri.formatSocketAddress(remoteAddress), newPool.metri()); } retnnewPool; }); // disposaleAcquire(newDisposaleAcquire(sink,pool,os,opsFactory,poolFactory.pendingAcquireTimeout,false)); }); } staticvoiddisposaleAcquire(DisposaleAcquiredisposaleAcquire){ //accquire一个连接,如果无可用了解则创建,则调用 Mono<PooledRef<PooledConnection>>mono= disposaleAcquire.pool.acquire(Dation.ofMillis(disposaleAcquire.pendingAcquireTimeout)); mono.suscrie(disposaleAcquire); } Pulisher<PooledConnection>connectChannel(){ retnMono.create(sink->{ ootstrap=ootstrap.clone(); PooledConnectionInitializerinitializer=newPooledConnectionInitializer(sink); .handler(initializer); //创建连接 ChannelFutef=.connect(); if(f.isDone()){ initializer.operationComplete(f); }else{ f.addListener(initializer); } }); }

从代码里面可以看出,ConnectionProvider对每一个远程(即下游服的某一个)都缓存了一个连接分配器(ConnectionAllocator),而这个ConnectionAllocator才是正的连接池,是ProjectReactor项目内部实现的一个连接池,就不从源码角度分析,简单来说,就是请求方获取连接的时候,如果池子里面有空闲连接,则直接用现成连接,如果没有的话,则调用PoolFactory创建新的链接。

总结一下:

网关内部维持了一个缓存映射,缓存着下游每一个服务(ip:port)对应的连接分配器(ConnectionAllocator),而ConnectionAllocator是一个连接池,内部会保存复用已经生成的连接。

当网关转发请求时确认下游目标服务的,即可直接从对应的连接池中取出连接复用。

END

免责声明:本文由卡卷网编辑并发布,但不代表本站的观点和立场,只提供分享给大家。

卡卷网

卡卷网 主页 联系他吧

请记住:卡卷网 Www.Kajuan.Net

欢迎 发表评论:

请填写验证码