zuul网关springboot版本升级导致的请求超时问题

1.现象

在zuul网关springboot版本由1.5.10升级到2.1.1版本后,网关调用某服务下的所有接口,均出现熔断超时现象,报GATEWAY_TIMEOUT。

在这里插入图片描述

2.分析

2.1 问题分析

1.从代码底层调用RibbonRoutingFilter的buildCommandContext方法构建请求上下文RibbonCommandContext。再到构建HystrixCommand,HttpClientRibbonCommand最终调用RetryableRibbonLoadBalancingHttpClient的execute方法发起http请求,整个源码链路跟了一遍,没发现明显异常。

2.启动老的springboot版本的zuul网关应用。发起调用,发现在网关连接发起,再到获取响应头的时候,有获取到响应头信息server:nginx。

在这里插入图片描述
联想到,在下游没有代理的情况下,网关是能正常调用的,所以初次断定是nginx的问题。

3.我们找来了下游服务对应的研发人员,让他配合调整nginx的一系列参数,发现仍然报超时的现象。最后让他把请求相关的参数打印出来:

在这里插入图片描述
我们发现nginx收到的参数,不是个完整的json。另外http状态码为400,有语义有误和参数有误的含义。另外有两个时间参数request_time和upstream_response_time,其值分别是12.827以及”-”。

两字段的含义
request_time: 指的就是从接受用户请求的第一个字节到发送完响应数据的时间,即包括接收请求数据时间、程序响应时间、输出响应数据时间。
upstream_response_time: 是指从Nginx向后端建立连接开始到接受完数据然后关闭连接为止的时间。

值大小说明:
$request_time值为12.827是因为在网关设置了ribbon的读取超时时间13秒
在这里插入图片描述
$upstream_response_time 为”-”的原因,很可能是nginx没有把请求参数发给下游服务。

延伸:request_time肯定比upstream_response_time值大,特别是使用POST方式传递参数时,因为Nginx会把request body缓存住,接受完毕后才会把数据一起发给后端。所以如果用户网络较差,或者传递数据较大时,request_time会比upstream_response_time大很多。

4.由于是参数的问题,所以顺着这个思路,把httpclient相关的jar包,由升级后的httpclient-4.5.6.jar 换回到 httpclient-4.5.5.jar。调用之后仍然报超时。既然最底层的httpclient包回滚都报错,联想到因为spring boot的版本升级,导致feign, hystrix, ribbon等一系列版本都升级。所以很可能在一开始构建请求参数的时候,就出现了问题。我们同时启动升级前后的两个zuul网关应用。我们在最初的构建请求上下文的地方:RibbonRoutingFilter的buildCommandContext方法打上断点,发起调用并进行详细的参数比对。在计算contentLength的时候,发现了不同点:

老版本:

在这里插入图片描述
新版本:

在这里插入图片描述

2.2 原因分析

1.项目启动的时候构建底层路由过滤器RibbonRoutingFilter。会判断HttpServletRequest子类是否有getContentLengthLong方法。(因为拉升了tomcat版本,升级前老版本没有,升级后有)。有的话,会将boolean变量useServlet31置为true,否则为false。

老版本:
在这里插入图片描述

在这里插入图片描述
新版本:

在这里插入图片描述
在这里插入图片描述
2.请求发起调用,RibbonRoutingFilter内部调用buildCommandContext方法,构建请求上下文RibbonCommandContext,里面包含参数contentLength。contentLength的构建逻辑:long contentLength = useServlet31 ? request.getContentLengthLong(): request.getContentLength(); 由于老版本useServlet31变量值为false,将调用request.getContentLength()方法,此方法在网关的自定义过滤器-加解密过滤器中,被重写过(因为解密后,请求体包的大小相比原来有变化,所以需要重写)。故而能得到准确的contentLength。但是升级后useServlet31为true,调用的是request.getContentLengthLong()。此方法,因为没被重写,获取的是最原始的请求体包大小。当时调用下游服务的请求,升级后最终算出来contentLength为1197,而实际上应该是1142。

老版本:
在这里插入图片描述
在这里插入图片描述
新版本:
在这里插入图片描述

在这里插入图片描述

  1. 请求打到下游的nginx后,nginx会再转发给他们的下游服务。但nginx因为contentlenth比实际大的缘故,导致nginx读取到消息结尾后,会等待下一个字节,会无响应直到超时。自然也无法把请求发给下游服务,这也是上文$upstream_response_time为”-”的原因。

  2. nginx迟迟不给网关返回响应,超过了熔断的时间,最终因为熔断报了GATEWAY_TIMEOUT。
    在这里插入图片描述

3.解决

3.1修改重写方法

加解密代码块中,重写方法由getContentLength() 改为getContentLengthLong(),再次发起调用后,一切正常。

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

the end~

  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 5
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值