bingoohuang / blog

write blogs with issues
MIT License
178 stars 24 forks source link

SpringBoot 填坑记 #207

Open bingoohuang opened 3 years ago

bingoohuang commented 3 years ago

SpringBoot 内嵌 Tomcat 会话保持的坑

问题

wecom-temp-a908312733e9b95963e3fd237cdda0af

以下脚本,证实springboot会话保持有问题

$ gobench -l :10902/mssm/v3/auth/api/customerVerify -c1 -t1
Dispatching 1 goroutines at 2021-08-03 16:44:42.211
10 / 10 [---------------------------------] 100.00% 1 p/s

Total requests:     26777 hits
OK    requests:     26777 hits
Network failed(NF): 0 hits
Bad requests(!2xx)(BF): 0 hits
OK requests rate:   2677.678 hits/sec
Read throughput:    510.4KiB/sec
Write throughput:   472.2KiB/sec
Test time:      10s(2021-08-03 16:44:42.211-16:44:52.212)
Max X-Gobench-Seq:  26777
Real Connections:   268
$ gobench -l :5003/health -c1 -t1
Dispatching 1 goroutines at 2021-08-03 16:47:40.658
10 / 10 [-----------------------------------] 100.00% 1 p/s

Total requests:     79607 hits
OK    requests:     79607 hits
Network failed(NF): 0 hits
Bad requests(!2xx)(BF): 0 hits
OK requests rate:   7960.749 hits/sec
Read throughput:    1.5MiB/sec
Write throughput:   1.2MiB/sec
Test time:      10s(2021-08-03 16:47:40.658-16:47:50.658)
Max X-Gobench-Seq:  79607
Real Connections:   1

分析

抓包:GOLOG_STDOUT=true sudo -E httpdump -i any -port 10902 -verbose all -resp > a.txt

image

发现每一个连接,都是使用了100次(202中,200是包括100个请求100个响应,以及2个双向连接关闭),证实了是 关闭连接是tomcat主动发起的,也就是说,gobench发起了长连接,但是Tomcat却把它关闭了。

谷歌搜索 springboot tomcat keepalivespringboot maxKeepAliveRequests,找到解决方案,Tomcat关闭长连接的条件有2个:

  1. keepAliveTimeOut:多少毫秒后不响应的断开keepalive, 默认60秒
  2. maxKeepAliveRequests:多少次请求后keepalive断开失效,默认100个

解决

Spring Boot Tomcat embed 中設置 Keep-Alive 此版本是基於 springboot 2.0.2.release,其他版本請自行測試

@Bean
public TomcatServletWebServerFactory tomcatServletWebServerFactory(){
TomcatServletWebServerFactory tomcatServletWebServerFactory = new TomcatServletWebServerFactory();
tomcatServletWebServerFactory.addConnectorCustomizers((connector)->{
ProtocolHandler protocolHandler = connector.getProtocolHandler();
if(protocolHandler instanceof Http11NioProtocol){
Http11NioProtocol http11NioProtocol = (Http11NioProtocol)protocolHandler;
http11NioProtocol.setKeepAliveTimeout(60000);//millisecond
http11NioProtocol.setMaxKeepAliveRequests(60000);
}
});
return tomcatServletWebServerFactory;
}

此版本是基於 springboot 1.5.6.release,其他版本請自行測試

@Bean
public TomcatEmbeddedServletContainerFactory tomcatEmbeddedServletContainerFactory(){
TomcatEmbeddedServletContainerFactory tomcatServletWebServerFactory = new TomcatEmbeddedServletContainerFactory();
tomcatServletWebServerFactory.addConnectorCustomizers((connector)->{
ProtocolHandler protocolHandler = connector.getProtocolHandler();
if(protocolHandler instanceof Http11NioProtocol){
Http11NioProtocol http11NioProtocol = (Http11NioProtocol)protocolHandler;
http11NioProtocol.setKeepAliveTimeout(60000);//millisecond
http11NioProtocol.setMaxKeepAliveRequests(60000);
}
});
return tomcatServletWebServerFactory;
}

资源

  1. SpringBoot内嵌Tomcat的坑
  2. springboot内嵌tomcat优化
  3. 細說Http中的KeepAlive和JavaHttp中的KeepAlive機制
bingoohuang commented 3 years ago

Http 中的Keep-Alive

HTTP 持久连接(HTTP persistent connection,也称作HTTP keep-alive 或HTTP connection reuse,翻译过来可以是保持连接或者连接复用)是使用同一个TCP 连接来发送和接收多个HTTP 请求/ 应答,而不是为每一个新的请求/ 应答打开新的连接的方式。

HTTP 协议采用“请求– 应答” 模式,当使用普通模式,即非KeepAlive 模式时,每个请求/ 应答客户和服务器都要新建一个连接,完成之后立即断开连接(HTTP 协议为无连接的协议),每次请求都会经过三次握手四次挥手过程,效率较低;当使用Keep-Alive

下图是每次新建连接和连接复用在通信模型上的区别:

image

在Http 1.0 中,Keep-Alive

Http1.1 以后,客户端(包括但不限于浏览器)发送请求时会在Header 中增加一个请求头 这样一来,客户端和服务器之间的HTTP 连接就会被保持,不会断开(断开方式下面介绍),当客户端发送另外一个请求时,就可以复用已建立的连接。Keep-AliveConnection: Keep-AliveConnection: Keep-Alive

现在的Http 协议基本都是Http 1.1 版本了,不太需要考虑1.0 的兼容问题

Keep-Alive 真的就这么完美吗

当然不是,Keep-Alive 也有自己的优缺点,并不是所有场景下都适用

优点

  1. 节省了服务端CPU 和内存适用量
  2. 降低拥塞控制(TCP 连接减少)
  3. 减少了后续请求的延迟(无需再进行握手)

缺点

  1. 对于某些低频访问的资源/ 服务,比如一个冷门的图片服务器,一年下不了几次,每下一次连接还保持就比较浪费了(这个场景举的不是很恰当)。Keep-Alive 可能会非常影响性能,因为它在文件被请求之后还保持了不必要的连接很长时间,额外占用了服务端的连接数。