目录
1、前言
本文用于探讨默认配置下tomcat处理长连接的问题。
何为长连接:即以此tcp连接中发生多次http交互。
2、环境准备
使用到的框架为spring-boot(内嵌tomcat),netty(用于比较)
2.1、服务端代码
每次接受数据时打印出当前处理线程的id。
@RestController
public class HelloController {
@PostMapping("/hello")
public String hello(@RequestBody JSONObject jsonObject) throws InterruptedException {
System.out.println("接收到客户端的hello消息:" + jsonObject.getString("hello"));
System.out.println("线程id"+Thread.currentThread().getId());
return "hello world";
}
}
2.2、客户端代码
模拟长连接中连续发送100次http报文
@Test
void contextLoads() throws IOException, InterruptedException {
SocketChannel socketChannel = SocketChannel.open(new InetSocketAddress("localhost", 8080));
ByteBuffer write = ByteBuffer.wrap(("POST /hello HTTP/1.1\r\n" +
"Content-Type: application/json\r\n" +
"User-Agent: PostmanRuntime/7.29.0\r\n" +
"Accept: */*\r\n" +
"Postman-Token: fc10ee5d-a7b7-441f-a7e8-156cadd7778b\r\n" +
"Host: localhost:6666\r\n" +
"Accept-Encoding: gzip, deflate, br\r\n" +
"Connection: keep-alive\r\n" +
"Content-Length: 17\r\n" +
"\r\n" +
"{\"hello\":\"world\"}").getBytes(StandardCharsets.UTF_8));
for (int i = 0; i < 100; i++) {
socketChannel.write(write);
write.position(0);
}
}
2.3、结果
可以看到tomcat在长连接中数据处理共用一个线程,此时就会存在一个问题,如果生产环境下有qps需求,那么使用默认tomcat配置下进行编程很有可能会达不到压测的理想效果。
而我们通常面临的qps需求往往是短连接需求,即对不同用户的访问在预期时间内回包。比如,我们使用tomcat对服务发起两次访问,tomcat会使用两个不同的线程对数据进行处理,截图如下:
在短连接的情况下,我们可以很方便的对其进行性能的提升,如调大tomcat的并发请求数、使用nginx对服务进行负载均衡等。
3、环境改善
让我们使用netty来对长连接进行性能优化,预期效果是netty能够针对长连接中不同的数据包进行分线程处理。
3.1、服务端handler代码
public class NettyHttpHandler extends SimpleChannelInboundHandler<FullHttpRequest> {
private static final ExecutorService executorService = Executors.newFixedThreadPool(10);
@Override
protected void channelRead0(ChannelHandlerContext channelHandlerContext, FullHttpRequest fullHttpRequest) throws Exception {
executorService.execute(() -> {
dataHandle(fullHttpRequest);
});
// 构造返回数据
ByteBuf byteBuf = Unpooled.copiedBuffer("hello world", StandardCharsets.UTF_8);
DefaultFullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK, byteBuf);
response.headers().set(HttpHeaderNames.CONTENT_TYPE, "text/plain");
response.headers().set(HttpHeaderNames.CONTENT_LENGTH, byteBuf.readableBytes());
channelHandlerContext.writeAndFlush(response);
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
ctx.close();
}
private void dataHandle(FullHttpRequest fullHttpRequest) {
System.out.println(String.format("接收到客户端的消息:%s,使用的线程id为:%s", fullHttpRequest.content().toString(StandardCharsets.UTF_8), Thread.currentThread().getId()));
}
}
3.2、客户端代码
客户端代码还是沿用tomcat测试中的测试代码。
3.3、结果
因为我这里线程池数只存放10个线程,因此总共能并发处理的报文只能为10。但可以看出与tomcat测试中明显不同的是长连接的数据报文处理由单线程变为了多线程,这意味着在压测上肯定能得到更好的效果。
4、进一步验证
为了进一步证实我们的想法——多线程处理长连接报文状态下处理时间要优于单线程,我们来做抓包验证。
4.1、测试环境准备
- 我们将在服务端分别部署一套上文编写的tomcat服务端代码和netty服务端代码,端口分别为8080和6666。
- 测试仍使用我们的客户端测试代码。
4.2结果
4.2.1、netty
可以看到回送100个报文大概花的时间为:0.034250 - 0.005034 = 0.029216
4.2.2、tomcat
可以看到回送100个报文大概花的时间为:0.069127 - 0.002375 = 0.066752
综上可以得出,多线程下处理长连接速度确实要比单线程下快,并且速度差距将随着线程数、报文数增大而差距愈发明显。