压测让人爆炸的事,就算把你写的代码都注释掉了这个问题依旧存在,这个时候你开始怀疑网络,怀疑TCP链接,怀疑系统框架,这篇文章就线上遇到的问题好好梳理了一下一个请求进来究竟会“遭遇”什么
一、单机系统 #
首先我们先来思考在没有Tomcat这些网络框架的时候,一个单机系统是如何处理请求
首先一个请求进来我们分成五步:
-
建立TCP连接
-
读取请求数据(先读取TCP请求头,然后根据TCP请求头读取报文)
-
处理请求
-
发送响应
-
关闭TCP连接
假如我们客户只有一个,那么只要它网速好,这个模型速度很快
假如用户一多,其他用户就得排队,他们都堵在第一步建立TCP连接上,我们经常看到连接超时就是这种情况
假如你处理请求很慢,用户发完请求之后就在那里等,这个时候就是浏览器转圈
接下来我们看Tomcat是如何实现一个高并发的网络框架
二、Tomcat原理 #
首先Tomcat是采用线程池来实现一个并发框架,它把上面步骤都放到2、3、4都放在一个线程中进行,你只需要写步骤3处理请求的代码,其他的步骤它来帮你完成
其中步骤1和步骤5由主线程实现,主线程的工作就是创建TCP连接,这个数量是由 server.tomcat.max-connections 控制的,当超过这个数量的时候,Tomcat会把这个请求放到请求队列中server.tomcat.accept-count ,当超过这个数量的时候,Tomcat会丢弃这个请求,客户端会收到503错误
由于我们内存CPU网卡的速度是有限的,所以Tomcat规定了,我们同时能处理多少请求,这个是通过server.tomcat.threads.max 来规定的,默认是200,就是同时能处理200个请求,其他的都在连接等待过程中
这样讲非常的枯燥,接下来我们来看看在Cat监控中,每个步骤处的位置
三、Cat例子 #
首先简单介绍一下Cat的原理,就是AOP,使用Spring的AOP能够在你想要监控的方法前后添加自己的代码
上面是我限制客户端网速伪造的一个超长请求,我们来看看第一个 17:10:11.750 URL 这个是Cat在方法
org.jboss.resteasy.plugins.server.servlet.HttpServletDispatcher.service 进行的一个打点
你可以看到后面接着一大串同时的Service.client 这些,都是在调用这个方法之前打的,然后最后那个URL是执行完这个函数之后打的点
我们可以看到在HTTP.UserAgent 和 Service 之间存在近3秒的等待,这么长的时间是在干什么呢?

@RequestMapping(value="/process", method = RequestMethod.POST)
public void process(@RequestBody QueryRequest payload) throws Exception {
System.out.println(payload);
}
我们来看到代码,我们在定义这个这个函数的时候,指定了我们请求的Body是QueryRequest 类型,Tomcat提供了在提供了我们运行线程的同时,会给传给我们一个SocketWrapperBase socketWrapper 对象,我们可以读取这个获取到请求报文,当然你也可以选择不读取(无视客户端请求哈哈),Spring通过反射获取到这个方法的时候,会根据你的类型使用你想序列化的方式将它序列化成对象给你
所以那么耗时将近3秒的时间都是Spring框架在读取Socket报文,我们通过Arthas可以监控到,耗时99%都在 javax.ws.rs.ext.ReaderInterceptorContext.proceed 上面
这个是直接请求我们单机的ip地址才能监控到耗时这么长的请求,但是假如我们把请求打到网关上去,由网关路由到我们服务,惊奇的发现Cat上面没有长耗时的请求了
接下来我们看看为什么接入网关之后读取就没有了
四、网关的影响 #
其实网关和Tomcat一样,只不过它把处理请求下发到服务去了,所以就算客户端网速很卡,发的很慢,但是服务是无感知的,它是和网关交互,打个比方就是,网关就像一个超市,有很多收银台,就算有一个顾客走的很慢,堵住后面的顾客,但是只要它交给售货员,售货员打完了所有的单子,计算机一下子就给它算完了
所以对服务来说,他从网关那边读取请求很快,他处理也很快,所以没有耗时
五、接收的影响 #
接下来我们思考一下,假如客户端不仅发的慢,接收也很慢,在Cat上面会有什么影响吗?
答案是完全没有影响,Tomcat通过org.apache.tomcat.util.net.SocketWrapperBase.write 来实现发送响应体,无论客户端网络多卡,只要服务端不卡,耗时都非常短
其实这个和网关一样,对于服务端来说,只要它和外网的交换机不卡,不管客户端网速有多炸,它啪的一下就发完了,客户端读的慢就慢慢读,对于服务端来说,它已经发完了,这个TCP连接的控制权已经交给Tomcat的
六、总结 #
假如一个服务在网关层后面,如果监控上他有卡顿,如果网关没问题的话,99%的可能就是你程序哪个地方有死锁或者耗时的操作,基本上排除了网络接收发送这些耗时的可能
我们压测最终发现的也是因为日志压缩打印的锁,导致所有请求全部都卡顿了20S,而且一般都是在压测30分钟之后才会有一次,因为日志量达到1个G了