Envoy代理Jetty9出现的503问题的分析
概述
最近公司在进行压力测试摸底每个服务的最大并发能力,在压力测试的过程中发现出现了大量的503现象,大概每次压测中会有3%-8%的503错误,经过调查分析,发现这是由于后端的Java服务主动关闭了connection,导致前端的代理Envoy拿着一个已经被关闭的connection发送请求,从而导致产生了503的现象。要解决这个问题的需要将java端的服务的idle timeout值设置的要大于前端Envoy代理服务器的idle timeout的值。下面是这个问题的解决的详细过程的描述。
详细过程
测试部门的同事在作压测的时候发现服务返回了大量的503错误,服务的架构是:后端的服务是Java的,servlet容器是Jetty 9,前端代理服务器是Envoy,当我接手这个问题的时候,压测的同事说,这个Java服务只有被Envoy代理才会返回503,如果直接压测Java服务,不会出现503,因此我把重点放在了Envoy代理上面。
首先503的意思是服务不可用,就是说Envoy当转发请求到服务端的时候,发现服务不可用。因此猜测这是由于网络原因导致的错误,因此怀疑是不是Envoy代理和Java服务之间是不是有网络的丢包,导致出现503的现象。Java服务是部署在Kubernetes中的,Envoy代理和Java服务都是部署在同一个Pod中的,是两个独立的container,但是他们都共享一个网络命名空间,因为在一个网络命令空间就意味这他们之间的通信是进程间的IPC通信,都不会走网络,因此不会出现网络丢包。
排除了网络丢包的可能性,那么就思考他们是如何通信的,考虑到Envoy是Java服务的代理,一般代理的服务都会提前创建好到后端服务的连接,这样可以比较高效的响应请求,如果每次请求到来的时候再创建连接就会很低效。而服务端接受客户端的连接创建请求后,为了保护有限的资源不被消耗光,必须要有一个回收资源的机制。在服务端中有idle timeout来保证空闲的连接可以被及时的关闭,同样的道理,在客户端中,如果因为请求量突然增加,会额外创建了很多的连接到服务端,如果随着请求量的下降,需要及时关闭这些不再使用的连接,因为对于客户端来讲这些连接的维持也是需要消耗资源的。客户端回收资源的机制也是idle timeout , 查看Jetty 9的默认的idle timeou为30秒,Envoy的idle timeou为15秒,目前这里发现的问题是客户端的idle timeout 时间大于服务端的idle timeout,所以就导致了服务端已经关闭的连接被客户端误以为是有效的,所以为了验证这个假设,我做了一个测试,将服务端Jetty的idle timeou时间调整到6分钟,然后再进行压测,结果没有发现503,说明我的假设是对的。 Jetty的idle timeout调整之后,再进行压测,不再出现这个503现象了。
总结
在所有的网络调用,客户端的idle timeout 的设置一定是要小于等于服务端的idle timeout,否者就会出现类似503的错误,这个在jdbc的也有类似的问题,参考我的另一篇文章:kingshard的连接问题调查(二) ,另外在调研Envoy的资料的时候,发现Envoy对于代理有keep alive的参数配置,就是说在tcp层面可以通过发送网络包来保持连接的有效性,如果设置了这个参数是不是可以不用修改jetty的idle timeout这个参数? 因为在调大了这个参数之后,发现jetty的吞吐量下降了20%-30%左右。我们在Java的jdbc pool的设置里,经常会有一个参数idleCheck = ‘select 1’ ,目的就是保证我们维持一些可用的连接存在。所以后面有机会可以验证下利用keep alive的机制来避免该问题。