当前位置:首页 > 每日看点 > 正文内容

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

卡卷网1年前 (2025-01-10)每日看点217

<>关于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是一个连接池,内部会保存复用已经生成的连接。

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

扫描二维码推送至手机访问。

版权声明:本文由卡卷网发布,如需转载请注明出处。

本文链接:https://www.kajuan.net/ttnews/2025/01/8370.html

分享给朋友:

相关文章

如何在自己家里建立一套私有云系统?需要哪些设备?

如何在自己家里建立一套私有云系统?需要哪些设备?

我敢保证,这绝对是目前为止最简单的搭建家用私有云的方法:“一台主机+至少一块硬盘”足矣!不需要任何专业知识,也没有复杂繁琐的步骤,十分钟不到就能搭建好,帮你成功打开文件云储存新世界的大门!还在单纯依靠网盘进行文件存储的朋友,不是我吐槽哈,它...

抖音和快手最大的区别是什么?

1、目标人群定位不同抖音:一二线城市,占比52%,大专学历以上,女性偏多。抖音以年轻群体居多。快手:三四线城市,占比64%,高中学历以下,男女更均衡。快手所覆盖的年龄段范围更广。2、内容创作的形式和深度不同抖音:偏深层,内容的装饰及表达更高...

为什么我感受不到 1500 元的手机比四五千的差?

我长期使用两千元左右的安卓机,一天接近8小时的重度手机使用者。某天我突然想试试看传说中非常赛艇的苹果。狠了心,砸了钱。七千大样买了爱疯。就这?什么辣鸡玩意。而且因为我一直更新软件,用了两年爱疯就卡了。并没有传说中的用四五年不卡。用了这么一次...

自媒体如何快速起步?

自媒体如何快速起步?

有两种经验,可供参考。第一种是:现象级的爆火、爆款,这种情况捞到钱,实现财务自由的人并不多,但确实存在。只不过非要说清楚为什么这个账号可以火、可以短时间内赚到普通人一辈子赚不到的钱,他们自己也不一定能说清楚,因为赶上了风口(内外部情况)。举...

为什么扫码支付在中国流行,在发达国家被排斥?

因为这是一种落后的技术。卖菜的大爷花5毛钱就可以打印出一张二维码来接受付款。你觉着这种先进么?跟先进完全不沾边的。正是因为不先进,所以才能流行。卖菜大爷用不起一台先进的、具有NFC感应功能的、还能刷各种银行卡的收款机。这就是现实。发达国家,...

中国芯片产量达1399亿颗,这意味着什么?

美国并不是没有明白人,只是特朗普不懂芯片产业的情况,冒冒失失在ZZ正确下,开启了对中国芯片的掐脖子,结果没掐死,反而让人练出了铁肺。芯片是所有科技产业的上游,美国原本在上游呆得很舒服,靠英伟达、AMD、德州仪器、高通等这些几十年霸主地位的公...

发表评论

访客

看不清,换一张

◎欢迎参与讨论,请在这里发表您的看法和观点。