一、先弄明白Jetty处理请求的“分工”

很多时候我们遇到Jetty连接超时,根本不知道问题出在哪,就像去餐厅吃饭突然叫不到服务员,得先搞懂餐厅的分工:Jetty处理请求有两个关键“岗位”,一个是负责接请求的,另一个是负责处理请求的,搞懂这俩,后面的调优就有方向了。

1.1 谁负责把请求“领进门”?

Jetty里有个叫acceptor的线程,就像餐厅的前台迎宾。每一个新的客户端连接过来,第一个碰到的就是这个迎宾,他要做的事很简单:把这个连接转到对应的“服务员线程”那里,不然客户端的连接就一直卡在门口。要是迎宾太少,每秒涌入100个连接,1个迎宾根本忙不过来,后面的连接就只能卡在门口,看起来就是连接超时了。

1.2 谁负责处理具体请求?

处理请求的是Jetty线程池里的线程,就像餐厅里真正干活的服务员。比如用户要查订单,就是服务员接了这个请求,去数据库查,再把结果返回。线程池的大小决定了同时能处理多少请求:要是服务员太少,高峰时100个请求只能等,等不到就超时;要是服务员太多,每个服务员只干10%的活,CPU不停切换不同服务员,反而耽误时间,请求处理得更慢。

二、为啥连接超时?从两个“岗位”找问题

生产环境里Jetty的连接超时,90%以上都和这两个岗位的配置不对有关,我们拆解最常见的坑:

2.1 迎宾太少(acceptors设置不够)

假设你的服务器是4核CPU,你只设了1个迎宾。正常低峰时没问题,但要是遇到双11这类高峰,每秒涌进100个新连接,1个迎宾要同时接过100个连接再分配给服务员,根本忙不过来,后面的连接直接被拒绝,显示连接超时。

2.2 服务员班子配置乱(线程池参数不对)

服务员的数量和排班全靠线程池的参数:

  • 要是最小线程数设得太小:平时只开5个服务员,高峰要招15个,临时招人需要时间,这几分钟里请求就只能等;
  • 要是最大线程数设得太大:比如设1000个,每个服务员占1MB栈内存,光栈就占1GB,再加上堆内存,服务器直接内存不够,反而会OOM;
  • 要是任务队列没设好:请求排队的地方满了,再进来的请求就会直接超时。

三、动手调优:从参数到验证,附完整示例

调优的核心是根据服务器的CPU、内存和业务并发量,调整两个“岗位”的数量,我们一步步来,所有示例都用Jetty 9.x版本(主流生产版本)。

3.1 先查当前Jetty的参数

在调优前,得先知道现在的参数是多少,用命令就能看:

# 进入Jetty的安装目录,比如/opt/jetty
cd /opt/jetty
# 查看当前Jetty运行状态,找到acceptor线程和线程池的基本参数
./bin/jetty.sh status

要是想更细节,可以用JDK自带的jvisualvm连接Jetty的JMX端口,看线程池的maxThreads、minThreads和acceptors的数量。

3.2 调整迎宾数量(acceptors参数)

正确的acceptors设置一般是CPU核心数的1倍,或者和selectors线程数对应。比如CPU是8核,就设8个acceptor,这个数量不会浪费CPU,也不会让迎宾忙不过来。我们修改jetty.xml里的连接器配置:

<!-- Jetty 9.x 连接器配置,核心是调整acceptors和selectors -->
<Configure id="Server" class="org.eclipse.jetty.server.Server">
  <Call name="addConnector">
    <Arg>
      <New class="org.eclipse.jetty.server.ServerConnector">
        <Arg name="server"><Ref refid="Server" /></Arg>
        <!-- 加载HTTP协议的连接工厂 -->
        <Arg name="factories">
          <Array type="org.eclipse.jetty.server.ConnectionFactory">
            <Item>
              <New class="org.eclipse.jetty.server.HttpConnectionFactory">
                <Arg name="config"><Ref refid="httpConfig" /></Arg>
              </New>
            </Item>
          </Array>
        </Arg>
        <!-- 设置acceptor线程数:和CPU核心数一致,8核就设8 -->
        <Set name="acceptors">8</Set>
        <!-- 设置selectors线程数:和acceptors一致,负责处理已分配的连接,不要太多 -->
        <Set name="selectors">8</Set>
        <!-- 服务端口,根据实际业务改,这里用8080 -->
        <Set name="port">8080</Set>
        <!-- 连接超时时间,默认是30秒,别设太短 -->
        <Set name="idleTimeout">30000</Set>
      </New>
    </Arg>
  </Call>
</Configure>

3.3 调整服务员班子(线程池参数)

线程池的核心是三个参数:最小线程数(平时留多少服务员)、最大线程数(高峰最多能招多少服务员)、队列大小(请求最多能等多少)。修改jetty.xml里的线程池配置:

<!-- Jetty线程池配置,适合大多数生产场景 -->
<Configure id="Server" class="org.eclipse.jetty.server.Server">
  <!-- 定义QueuedThreadPool,Jetty默认的线程池 -->
  <Set name="threadPool">
    <New class="org.eclipse.jetty.util.thread.QueuedThreadPool">
      <!-- 最小线程数:平时保留的线程数,低峰时不用反复创建销毁,设为20 -->
      <Set name="minThreads">20</Set>
      <!-- 最大线程数:根据服务器内存调整,1核2GB的服务器设200,1核4GB设400,每个线程占1MB栈,200个就是200MB,留够堆内存 -->
      <Set name="maxThreads">200</Set>
      <!-- 空闲线程存活时间:超过1分钟没用的线程会回收,节省资源 -->
      <Set name="idleTimeout">60000</Set>
      <!-- 任务队列大小:最多能排队多少请求,设为100,要是队列满了,会启动新线程到maxThreads,还满就拒绝 -->
      <Set name="maxQueued">100</Set>
    </New>
  </Set>
</Configure>

3.4 验证调优效果

改完配置后重启Jetty,用压测工具模拟并发,看超时率是不是降下来:

# 用ab工具压测,模拟1000并发,总请求10000,地址换成你自己的API
ab -n 10000 -c 1000 http://localhost:8080/your-business-api

看压测输出里的“Timeouts”是不是0,或者降到极低,再用jvisualvm看线程数,不会一直冲到maxThreads就卡住。

四、调优的注意事项和坑

4.1 别盲目设最大参数

很多人调优喜欢把maxThreads设得特别大,比如1000,但要算内存账:每个线程的栈内存默认是1MB,1000个线程就占1GB栈,再加上JVM堆内存,服务器很快就会内存溢出。acceptors也别设太多,超过CPU核心数的话,线程之间抢CPU,反而会让请求变慢。

4.2 要结合业务实际调

比如你的业务是电商,双11峰值是平时的5倍,maxThreads可以设成平时的5倍,但要测试峰值的表现;要是是后台管理系统,并发低,maxThreads设50就够,节省资源。

4.3 别忽略其他关联参数

除了acceptors和线程池,还有连接器的idleTimeout别设太短,比如设成10秒,要是网络波动,正常的请求也会被当成超时,设成30秒比较合理。

五、总结

生产环境Jetty的连接超时,大部分都不是突然出现的,而是参数配置和业务并发不匹配导致的。只要搞懂Jetty的“迎宾(acceptors)”和“服务员(线程池)”的分工,结合CPU、内存和业务峰值调整这两个参数,再用压测验证效果,就能解决绝大多数连接超时问题。调优不是一劳永逸的,要定期看监控,比如线程队列长度、acceptor线程的CPU使用率,根据业务变化慢慢调整,才能让Jetty服务稳定运行。