一次因reactor-netty bug导致springcloud gateway请求积压问题处理
项目中再使用springcloud gateway做微服务网关,在线上的环境忽然发现有时候接口会出现卡顿,更多集中在登陆的时候,而且卡顿呈现不确定性再检查springcloud gateway的日志时候发现出现了Connection链接的异常reactor.netty.channel.AbortedException: Connection has been closed,期间还发现一个问题,因为
项目中再使用springcloud gateway做微服务网关,在线上的环境忽然发现有时候接口会出现卡顿,更多集中在登陆的时候,而且卡顿呈现不确定性再检查springcloud gateway的日志时候发现出现了Connection链接的异常reactor.netty.channel.AbortedException: Connection has been closed,期间还发现一个问题,因为服务部署在k8s上有时候即使出现这个异常后,也不影响服务使用但是时间长了之后,就会导致请求卡顿严重时候还会出现请求处理失败,在网上搜索包括查看github的issue之后发现偏幅都太宽泛,并不聚焦没有太多实质性的制导,最终只能硬着头皮完整走一遍,并且讲结果记录下来方便以后查看。
场景复现
gateway日志中的异常信息
2021-12-10 19:18:27.776 ERROR 22734 --- [or-http-epoll-1] com.ops.config.AppConfig : Web exception handing: Connection has been closed
reactor.netty.channel.AbortedException: Connection has been closed
at reactor.netty.channel.ChannelOperationsHandler.discard(ChannelOperationsHandler.java:356)
at reactor.netty.channel.ChannelOperationsHandler.drain(ChannelOperationsHandler.java:366)
at reactor.netty.channel.ChannelOperationsHandler.handlerRemoved(ChannelOperationsHandler.java:210)
at io.netty.channel.AbstractChannelHandlerContext.callHandlerRemoved(AbstractChannelHandlerContext.java:961)
at io.netty.channel.DefaultChannelPipeline.callHandlerRemoved0(DefaultChannelPipeline.java:638)
at io.netty.channel.DefaultChannelPipeline.destroyDown(DefaultChannelPipeline.java:887)
at io.netty.channel.DefaultChannelPipeline.destroyUp(DefaultChannelPipeline.java:853)
at io.netty.channel.DefaultChannelPipeline.destroy(DefaultChannelPipeline.java:845)
at io.netty.channel.DefaultChannelPipeline.access$700(DefaultChannelPipeline.java:46)
at io.netty.channel.DefaultChannelPipeline$HeadContext.channelUnregistered(DefaultChannelPipeline.java:1390)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelUnregistered(AbstractChannelHandlerContext.java:178)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelUnregistered(AbstractChannelHandlerContext.java:164)
at io.netty.channel.DefaultChannelPipeline.fireChannelUnregistered(DefaultChannelPipeline.java:830)
at io.netty.channel.AbstractChannel$AbstractUnsafe$8.run(AbstractChannel.java:835)
at io.netty.util.concurrent.AbstractEventExecutor.safeExecute(AbstractEventExecutor.java:163)
at io.netty.util.concurrent.SingleThreadEventExecutor.runAllTasks(SingleThreadEventExecutor.java:404)
at io.netty.channel.epoll.EpollEventLoop.run(EpollEventLoop.java:333)
at io.netty.util.concurrent.SingleThreadEventExecutor$5.run(SingleThreadEventExecutor.java:905)
at java.lang.Thread.run(Thread.java:748)
2021-12-10 19:18:27.779 ERROR 22734 --- [or-http-epoll-1] o.s.w.s.adapter.HttpWebHandlerAdapter : [26191cc8] Error [java.lang.UnsupportedOperationException] for HTTP GET "/auth/oauth/authorize?response_type=code&scope=read&client_id=00&redirect_uri=http%3A%2F%2F192.168.101.64%2Fapi%2Foauth2OAuth&state=%7B%22redirectUrl%22:%22http://192.168.101.64/1000/team/role", but ServerHttpResponse already committed (302 FOUND)
2021-12-11 13:05:57.278 ERROR 27785 --- [or-http-epoll-5] com.ops.config.AppConfig : Web exception handing: Connection has been closed
reactor.netty.channel.AbortedException: Connection has been closed
at reactor.netty.channel.ChannelOperationsHandler.discard(ChannelOperationsHandler.java:356)
at reactor.netty.channel.ChannelOperationsHandler.drain(ChannelOperationsHandler.java:366)
at reactor.netty.channel.ChannelOperationsHandler.handlerRemoved(ChannelOperationsHandler.java:210)
at io.netty.channel.AbstractChannelHandlerContext.callHandlerRemoved(AbstractChannelHandlerContext.java:961)
at io.netty.channel.DefaultChannelPipeline.callHandlerRemoved0(DefaultChannelPipeline.java:638)
at io.netty.channel.DefaultChannelPipeline.destroyDown(DefaultChannelPipeline.java:887)
at io.netty.channel.DefaultChannelPipeline.destroyUp(DefaultChannelPipeline.java:853)
at io.netty.channel.DefaultChannelPipeline.destroy(DefaultChannelPipeline.java:845)
at io.netty.channel.DefaultChannelPipeline.access$700(DefaultChannelPipeline.java:46)
at io.netty.channel.DefaultChannelPipeline$HeadContext.channelUnregistered(DefaultChannelPipeline.java:1390)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelUnregistered(AbstractChannelHandlerContext.java:178)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelUnregistered(AbstractChannelHandlerContext.java:164)
at io.netty.channel.DefaultChannelPipeline.fireChannelUnregistered(DefaultChannelPipeline.java:830)
at io.netty.channel.AbstractChannel$AbstractUnsafe$8.run(AbstractChannel.java:835)
at io.netty.util.concurrent.AbstractEventExecutor.safeExecute(AbstractEventExecutor.java:163)
at io.netty.util.concurrent.SingleThreadEventExecutor.runAllTasks(SingleThreadEventExecutor.java:404)
at io.netty.channel.epoll.EpollEventLoop.run(EpollEventLoop.java:333)
at io.netty.util.concurrent.SingleThreadEventExecutor$5.run(SingleThreadEventExecutor.java:905)
at java.lang.Thread.run(Thread.java:748)
2021-12-11 13:05:57.281 ERROR 27785 --- [or-http-epoll-5] o.s.w.s.adapter.HttpWebHandlerAdapter : [8d36ff6a] Error [java.lang.UnsupportedOperationException] for HTTP POST "/sws/webhook/hws/alert", but ServerHttpResponse already committed (200 OK)
抛析问题
问题分析比较简单了,第一次碰到这个异常我也不认识他,他也不认识我话不多少直接上google查看一番,经过baidu和google之后,基本上就能确定了是因为netty本身的bug引起的,那么解决问题就简单了升级netty版本到修复了该问题的新版本就ok了。
官方bug链接:http://github.com/reactor/rea…
解决问题
既然问题确定了后那么就直接开始动手升级
1.升级netty到0.9.7RELEASE版本
通过查看官方在github中提供的信息,说到在netty的0.8.5版本中就进行了修复(但是实际结果中bug还是存在)。项目中实际使用的是springboot2.1.4.RELEASE + springcloud Greenwich.SR1自动引入的io.projectreactor.netty:reactor-netty:0.8.6.RELEASE版本。结合搜到的一些文章提到0.9.4版本修复了该问题,但是又因为0.9.4还有问题因此计划一次性升级到0.9.7修复缺陷。
方法1:单独升级reactor-netty到0.9.7版本 io.projectreactor.netty:reactor-netty:0.9.7.RELEASE
编译通过启动服务时候失败,因为版本差异太大导致其他依赖所需方法和类启动报错,不兼容低版本的springboot,因此决定通过整体升级springboot的方式来进行升级,但是springboot和springcloud的版本关联也比较大,因此需要做同步升级;
方法2:升级springboot到2.2.7.RELEASE版本,同时升级springcloud到Hoxton.SR12
通过查看springboot中的依赖然后找到对应的版本,具体步骤如下:
springboot第三方依赖版本查看
- spring-boot/spring-boot-project/spring-boot-dependencies/pom.xml查看引用的依赖版本: http://github.com/spring-proj…;
- release note 查看依赖版本变化:http://github.com/spring-proj…;
- netty版本查看:http://github.com/reactor/rea…
- 升级springcloud到对应的版本 http://start.spring.io/actuator/in… (浏览器打不开可以通过curl查看);
相关链接:
springboot start工作原理:
http://blog.csdn.net/weixin_3956…
http://www.jb51.net/article/188…
2.解决版本升级带来的其他问题
问题1:缺少tomcat依赖
升级springboot到2.2.7.RELEASE 和 springcloud Hoxton.SR12之后,启动服务提示缺少servlet.
处理方式:添加tomcat依赖 implementation 'org.springframework.boot:spring-boot-starter-tomcat'
相关链接: http://stackoverflow.com/questions/3…
问题2:0.9.7版本reactor-netty找不到方法,服务启动失败
***************************
APPLICATION FAILED TO START
***************************
Description:
An attempt was made to call a method that does not exist. The attempt was made from the following location:
org.springframework.cloud.gateway.config.GatewayAutoConfiguration$NettyConfiguration.buildConnectionProvider(GatewayAutoConfiguration.java:798)
The following method did not exist:
reactor.netty.resources.ConnectionProvider$Builder.evictInBackground(Ljava/time/Duration;)Lreactor/netty/resources/ConnectionProvider$ConnectionPoolSpec;
The method's class, reactor.netty.resources.ConnectionProvider$Builder, is available from the following locations:
jar:file:/Users/qu/.gradle/caches/modules-2/files-2.1/io.projectreactor.netty/reactor-netty/0.9.7.RELEASE/3a63889c056a7da153d695726daa0614bc02bcf5/reactor-netty-0.9.7.RELEASE.jar!/reactor/netty/resources/ConnectionProvider$Builder.class
It was loaded from the following location:
file:/Users/qu/.gradle/caches/modules-2/files-2.1/io.projectreactor.netty/reactor-netty/0.9.7.RELEASE/3a63889c056a7da153d695726daa0614bc02bcf5/reactor-netty-0.9.7.RELEASE.jar
Action:
Correct the classpath of your application so that it contains a single, compatible version of reactor.netty.resources.ConnectionProvider$Builder
Process finished with exit code 1
处理方式:升级reactor-netty版本到0.9.13.RELEASE
相关链接:
http://stackoom.com/question/4a…
http://www.codenong.com/cs107047281…
问题3. 升级到0.9.13之后ES health Bean找不到服务启动失败
Failed to instantiate [org.springframework.boot.actuate.health.HealthContributor]: Factory method 'elasticsearchRestHealthContributor' threw exception; nested exception is java.lang.IllegalArgumentException: Beans must not be empty 找不到
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'healthContributorRegistry' defined in class path resource [org/springframework/boot/actuate/autoconfigure/health/HealthEndpointConfiguration.class]: Bean instantiation via factory method failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [org.springframework.boot.actuate.health.HealthContributorRegistry]: Factory method 'healthContributorRegistry' threw exception; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'elasticsearchRestHealthContributor' defined in class path resource [org/springframework/boot/actuate/autoconfigure/elasticsearch/ElasticSearchRestHealthContributorAutoConfiguration.class]: Bean instantiation via factory method failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [org.springframework.boot.actuate.health.HealthContributor]: Factory method 'elasticsearchRestHealthContributor' threw exception; nested exception is java.lang.IllegalArgumentException: Beans must not be empty
at org.springframework.beans.factory.support.ConstructorResolver.instantiate(ConstructorResolver.java:656)
at org.springframework.beans.factory.support.ConstructorResolver.instantiateUsingFactoryMethod(ConstructorResolver.java:636)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateUsingFactoryMethod(AbstractAutowireCapableBeanFactory.java:1338)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1177)
at org.springframework.beans.factory.config.DependencyDescriptor.resolveCandidate(DependencyDescriptor.java:276)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1306)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1226)
at org.springframework.beans.factory.support.ConstructorResolver.resolveAutowiredArgument(ConstructorResolver.java:885)
at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:789)
... 55 common frames omitted
Caused by: org.springframework.beans.BeanInstantiationException: Failed to instantiate [org.springframework.boot.actuate.health.HealthContributorRegistry]: Factory method 'healthContributorRegistry' threw exception; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'elasticsearchRestHealthContributor' defined in class path resource [org/springframework/boot/actuate/autoconfigure/elasticsearch/ElasticSearchRestHealthContributorAutoConfiguration.class]: Bean instantiation via factory method failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [org.springframework.boot.actuate.health.HealthContributor]: Factory method 'elasticsearchRestHealthContributor' threw exception; nested exception is java.lang.IllegalArgumentException: Beans must not be empty
at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:185)
at org.springframework.beans.factory.support.ConstructorResolver.instantiate(ConstructorResolver.java:651)
... 69 common frames omitted
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'elasticsearchRestHealthContributor' defined in class path resource [org/springframework/boot/actuate/autoconfigure/elasticsearch/ElasticSearchRestHealthContributorAutoConfiguration.class]: Bean instantiation via factory method failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [org.springframework.boot.actuate.health.HealthContributor]: Factory method 'elasticsearchRestHealthContributor' threw exception; nested exception is java.lang.IllegalArgumentException: Beans must not be empty
at org.springframework.beans.factory.support.ConstructorResolver.instantiate(ConstructorResolver.java:656)
at org.springframework.beans.factory.support.ConstructorResolver.instantiateUsingFactoryMethod(ConstructorResolver.java:636)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateUsingFactoryMethod(AbstractAutowireCapableBeanFactory.java:1338)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1177)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBeansOfType(DefaultListableBeanFactory.java:623)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBeansOfType(DefaultListableBeanFactory.java:611)
at org.springframework.context.support.AbstractApplicationContext.getBeansOfType(AbstractApplicationContext.java:1242)
at org.springframework.boot.actuate.autoconfigure.health.HealthEndpointConfiguration.healthContributorRegistry(HealthEndpointConfiguration.java:78)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:154)
... 70 common frames omitted
Caused by: org.springframework.beans.BeanInstantiationException: Failed to instantiate [org.springframework.boot.actuate.health.HealthContributor]: Factory method 'elasticsearchRestHealthContributor' threw exception; nested exception is java.lang.IllegalArgumentException: Beans must not be empty
at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:185)
at org.springframework.beans.factory.support.ConstructorResolver.instantiate(ConstructorResolver.java:651)
... 88 common frames omitted
Caused by: java.lang.IllegalArgumentException: Beans must not be empty
at org.springframework.util.Assert.notEmpty(Assert.java:549)
at org.springframework.boot.actuate.autoconfigure.health.AbstractCompositeHealthContributorConfiguration.createContributor(AbstractCompositeHealthContributorConfiguration.java:52)
at org.springframework.boot.actuate.autoconfigure.elasticsearch.ElasticSearchRestHealthContributorAutoConfiguration.elasticsearchRestHealthContributor(ElasticSearchRestHealthContributorAutoConfiguration.java:55)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:154)
... 89 common frames omitted
处理方式1: 直接关闭设置management.health.elasticsearch.enabled=false,问题解决但是过于暴力不推荐使用。
相关链接: http://github.com/spring-proj…
处理方式2: 配置Elasticsearch high level客户端Bean并且配置management.health.elasticsearch.response-timeout,配置完成后问题解决。
@Bean
public RestHighLevelClient client() {
//todo 生成host的逻辑根据当前es host配置方式转换,线上就不需要修改ES配置
List hostList = Arrays.stream(hosts.split(Consts.COMMA)).map(h -> new HttpHost(h, port, httpType)).collect(Collectors.toList());
HttpHost[] hosts = new HttpHost[hostList.size()];
hostList.toArray(hosts);
RestClientBuilder builder = RestClient.builder(hosts);
// 异步httpclient连接延时配置
builder.setRequestConfigCallback(new RestClientBuilder.RequestConfigCallback() {
@Override
public RequestConfig.Builder customizeRequestConfig(RequestConfig.Builder requestConfigBuilder) {
requestConfigBuilder.setConnectTimeout(RestClientBuilder.DEFAULT_CONNECT_TIMEOUT_MILLIS);
requestConfigBuilder.setSocketTimeout(RestClientBuilder.DEFAULT_SOCKET_TIMEOUT_MILLIS);
requestConfigBuilder.setConnectionRequestTimeout(RestClientBuilder.DEFAULT_CONNECT_TIMEOUT_MILLIS);
return requestConfigBuilder;
}
});
// 异步httpclient连接数配置
builder.setHttpClientConfigCallback(new RestClientBuilder.HttpClientConfigCallback() {
@Override
public HttpAsyncClientBuilder customizeHttpClient(HttpAsyncClientBuilder httpClientBuilder) {
httpClientBuilder.setMaxConnTotal(RestClientBuilder.DEFAULT_MAX_CONN_TOTAL);
httpClientBuilder.setMaxConnPerRoute(RestClientBuilder.DEFAULT_MAX_CONN_PER_ROUTE);
return httpClientBuilder;
}
});
RestHighLevelClient client = new RestHighLevelClient(builder);
return client;
}
bootstrap.yml
management:
health:
elasticsearch:
response-timeout: 1000ms # 增加超时时间,避免不必要的检查失败
启动服务发现服务启动成功,并且服务调用成功。(如果服务启动成功遇到redis反序列化问题,参考链接)
总结
1.在spring cloud gateway中使用reactor-netty如果遇到reactor.netty.channel.AbortedException: Connection has been closed需要考虑升级netty版本;
2.对于依赖版本的修改会引起很多其他新问题,但是无论是新旧问题那种问题修改,都需要结合官方的文档和资料作为更新依据;
作者:troyqu
链接: https://juejin.cn/post/7043621474405449764
更多推荐
所有评论(0)