web编程(6) Tomcat vs Netty

1 引言

tomcat 与 netty 直接来对比,有些勉强。二者不管在服务定位,还是在技术架构中所处的位置,都不同。tomcat 是 HTTP 协议解决方案,而Netty是TCP(也支持UDP)协议解决方案。可以看出Netty更偏向上游,更基础一些,Tomcat更偏向应用,提供的功能更丰富,也更接近业务开发。

本文不侧重对比的全面性,只从“I/O”、“线程”两个视角分析,观察一下它们的设计理念、细节, 这也最能体现二者的特点。

2 I/O模型

从概念上,大致分4种(也有说5种的,这不是重点),分别是:阻塞模型、非阻塞模型、I/O复用模型、异步模型,都是一些帮助理解的概念,这些理论模型也不局限于网络I/O,个人理解,只要是“内核层“与“用户层”数据交互,都是适用的,这方资料很多,就不细述。

JDK早期只支持BIO阻塞模型,在JDK1.4 提供了对 NIO 非阻塞模型的支持,其后不断优化,在底层也由epoll 替换了select/poll,性能上有了大幅提升,在JDK1.7时提供了NIO2.0,新增了对异步套接字的支持,也就是AIO模型。

目前,JDK 中 NIO模型,不管是可靠性,还是性能,都很高,主流基础框架 spring boot 2.x 内嵌的tomcat 9,还是netty 4.x 都选择它作为 默认 的I/O处理方式,本文也只关注NIO相关内容。

3 tomcat

3.1 流程

以下是从外部视角,观察Tomcat 的NIO处理过程。

tomcat本身能承受的并发量,是由两个因素决定的 I/O链接数(也称为channel, 或 socket)、线程数。再就是请求平均耗时,正常应该都在100ms以内(但这个数字变化莫测,不同时间、场景,出入很大,只是个估摸值)。

QPS = 1000 / 请求平均耗时 * 线程数。

从公式看,好像跟“I/O链接数“无关啊,通常来说瓶颈在线程数,更确切是“请求平均耗时”。

当然,如果你的 最大I/O链接数 是10,就算你的线程池是10000,它也只能支持并发10的请求,也就是说 最大I/O链接数 是硬性指标通常该值比较大,默认值 8192。

举例,如果最大I/O链接数为10000,最大线程数100,单笔请求耗时20ms,那么它的QPS理论上就是5000,也就是5000客户同时并发,100个线程在1s内轻松搞定。

如果客户的忍耐度是5s的话,再把最大I/O链接数调到30000,可以满足25000客户同时并发,仍然是100线程,在5s内搞定全部请求,当然了,它的QPS仍然是5000,这是瞬时的并发量,不可持续,否则,客户端就会有大量请求被拒,但tomcat服务本身,没有被压垮,仍然稳定。

说这么多,其实我想表达,tomcat的性能、稳定性,在它的业务场景内,其实不差。

3.2 I/O链接

主要涉及2个参数:

server.tomcat.max-connections:最大IO链接数,默认值8192,细节可参考上面描述。

server.tomcat.accept-count:内核accept队列大小,也就是内核TCP三次握手过程中“半链接”队列 + "全连接"队列的合计值,也就是BACKLOG值。悬停在内核,还没有被I/O线程 acceptor 取走。

如果较真,也可以说,tomcat 支持的最大链接数是这两个参数的合计值,客户端并发链接请求如果超过该值,就会立马收到被拒异常:Connection refused: connect。

3.3 线程

涉及2个参数:

server.tomcat.threads.max: 最大线程数,默认值200.

server.tomcat.threads.min-space: 最小线程数,默认值10

这里指的是业务线程数,至于I/O线程,因采用NIO非阻塞模型,它的数量跟CPU内核数有关。

3.4 超时

这里指的超时,是指服务端跟I/O链接相关的内容,涉及3个参数

server.tomcat.connection-timeout:

I/O线程已accept链接,等待read数据的最长时间,默认值 60000 ms

server.tomcat.keep-alive-timeout:

http请求已处理完成,并返回响应结果,keep-alive 保持I/O链接的最长时间,默认值 60000 ms

这两个参数都是tomcat服务端,为I/O链接不被耗尽的保护参数,超过限值,服务端就会主动关闭socket连接。

另外还有一个参数:

server.tomcat.max-keep-alive-requests,默认值 100

4 netty

4. 1 流程

以下是 netty 的NIO处理过程。

从图可以看出,netty 与 tomcat 在 I/O 处理部分,是很相似的,但netty在I/O 处理方面,做了增强,它有ChannelHandler链式处理逻辑,且整个过程非阻塞。对“业务”处理部分,完全由用户自己发挥。

4.2 I/O链接

netty 最引人注目的特点,可以支持大量长链接,通常在10万以上,甚至百万,理论上有多少内存,就能支持多少链接,每一个Socket链接对应一个文件句柄。

linux系统,单个进程打开的句柄是有限的,一般是1024,需要调整该参数。

同样,提供参数ChannelOption.SO_BACKLOG,来调节内核accept队列大小。

4.3 线程

Netty 本身只有I/O线程,由于全部逻辑非阻塞,线程很少, 默认为内核的2倍。

业务线程,完全由用户根据自己实际情况维护、管理。

4.4 超时

netty 服务端,具有"长链接"特性,默认不会出现连接、读写超时,但它提供了更灵活的读写状态检查机制,通过配置IdleStateHandler 可以检测链接的读写情况,以下是其构造函数。

IdleStateHandler(long readerIdleTime, long writerIdleTime, long allIdleTime, TimeUnit unit);
readerIdleTime:  表示多长时间未读。
writerIdleTime: 表示多长时间未写。
allIdleTime: 表示多长时间未读写。
unit:时间单位。

在超时时,就会触发ChannelHandler状态事件:
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
    IdleStateEvent event = (IdleStateEvent)evt;    
  switch (event.state()) {
      case READER_IDLE:
        eventType = "读空闲"; break;
      case WRITER_IDLE:
          eventType = "写空闲"; break;
      case ALL_IDLE:
          eventType = "读写空闲"; break;
  }
}

通过重写该事件,可以做心跳、关闭连接等处理。

另外,需要注意参数,ChannelOption.SO_TIMEOUT,ChannelOption.CONNECT_TIMEOUT_MILLIS 都是针对客户端,对服务端无效。

5 总结

(1) Netty 适用于处理大量"长链接"场合,例如:IM、消息推送等。

(2) Netty 提供了便捷的编解码、自定义协议能力,在高性能通信方面应用广泛,例如:阿里的Dubbo,RocketMQ、Hadoop的Avro等。

(3) 通过对Tomcat分析,可以看出,在性能调优方面,除常见的JVM参数外,I/O链接数、线程数是很重要的指标,尤其是线程大小,合理的设置,可以使硬件性能得到发挥,又不至于使大量线程拖慢请求响应时间,需要对I/O链接数、线程数的监控分析,来优化配置参数。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
可以通过以下方式配置 ConfigurableServletWebServerFactory 来设置 TomcatNIO 线程池和堆栈大小: 1. 在应用程序的配置文件(例如 application.properties 或 application.yml)中添加以下属性: ``` server.tomcat.max-threads=<max-threads> server.tomcat.max-connections=<max-connections> server.tomcat.accept-count=<accept-count> server.tomcat.min-spare-threads=<min-spare-threads> server.tomcat.max-spare-threads=<max-spare-threads> server.tomcat.max-http-header-size=<max-http-header-size> ``` 其中: - `<max-threads>` 是 TomcatNIO 线程池中的最大线程数。 - `<max-connections>` 是 Tomcat 的最大连接数。 - `<accept-count>` 是 Tomcat 接受的最大连接数。 - `<min-spare-threads>` 是 Tomcat NIO 线程池中的最小空闲线程数。 - `<max-spare-threads>` 是 Tomcat NIO 线程池中的最大空闲线程数。 - `<max-http-header-size>` 是 Tomcat 支持的最大 HTTP 标头大小。 2. 创建一个 ConfigurableServletWebServerFactory bean,并设置上述属性: ``` @Bean public ConfigurableServletWebServerFactory webServerFactory() { TomcatServletWebServerFactory factory = new TomcatServletWebServerFactory(); factory.addConnectorCustomizers(connector -> { connector.setMaxThreads(<max-threads>); connector.setMaxConnections(<max-connections>); connector.setAcceptCount(<accept-count>); connector.setProperty("maxHttpHeaderSize", <max-http-header-size>); }); factory.addThreadPoolCustomizers(tp -> { tp.setMinSpareThreads(<min-spare-threads>); tp.setMaxSpareThreads(<max-spare-threads>); }); return factory; } ``` 3. 将创建的 ConfigurableServletWebServerFactory bean 注入到 Spring 应用程序中。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值