You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

9.1 KiB

40 | 谈谈Jetty性能调优的思路

关于Tomcat的性能调优前面我主要谈了工作经常会遇到的有关JVM GC、监控、I/O和线程池以及CPU的问题定位和调优今天我们来看看Jetty有哪些调优的思路。

关于Jetty的性能调优官网上给出了一些很好的建议分为操作系统层面和Jetty本身的调优我们将分别来看一看它们具体是怎么做的最后再通过一个实战案例来学习一下如何确定Jetty的最佳线程数。

操作系统层面调优

对于Linux操作系统调优来说我们需要加大一些默认的限制值这些参数主要可以在/etc/security/limits.conf中或通过sysctl命令进行配置其实这些配置对于Tomcat来说也是适用的下面我来详细介绍一下这些参数。

TCP缓冲区大小

TCP的发送和接收缓冲区最好加大到16MB可以通过下面的命令配置

 sysctl -w net.core.rmem_max = 16777216
 sysctl -w net.core.wmem_max = 16777216
 sysctl -w net.ipv4.tcp_rmem =“4096 87380 16777216”
 sysctl -w net.ipv4.tcp_wmem =“4096 16384 16777216”

TCP队列大小

net.core.somaxconn控制TCP连接队列的大小默认值为128在高并发情况下明显不够用会出现拒绝连接的错误。但是这个值也不能调得过高因为过多积压的TCP连接会消耗服务端的资源并且会造成请求处理的延迟给用户带来不好的体验。因此我建议适当调大推荐设置为4096。

 sysctl -w net.core.somaxconn = 4096

net.core.netdev_max_backlog用来控制Java程序传入数据包队列的大小可以适当调大。

sysctl -w net.core.netdev_max_backlog = 16384
sysctl -w net.ipv4.tcp_max_syn_backlog = 8192
sysctl -w net.ipv4.tcp_syncookies = 1

端口

如果Web应用程序作为客户端向远程服务器建立了很多TCP连接可能会出现TCP端口不足的情况。因此最好增加使用的端口范围并允许在TIME_WAIT中重用套接字

sysctl -w net.ipv4.ip_local_port_range =“1024 65535”
sysctl -w net.ipv4.tcp_tw_recycle = 1

文件句柄数

高负载服务器的文件句柄数很容易耗尽,这是因为系统默认值通常比较低,我们可以在/etc/security/limits.conf中为特定用户增加文件句柄数:

用户名 hard nofile 40000
用户名 soft nofile 40000

拥塞控制

Linux内核支持可插拔的拥塞控制算法如果要获取内核可用的拥塞控制算法列表可以通过下面的命令

sysctl net.ipv4.tcp_available_congestion_control

这里我推荐将拥塞控制算法设置为cubic

sysctl -w net.ipv4.tcp_congestion_control = cubic

Jetty本身的调优

Jetty本身的调优主要是设置不同类型的线程的数量包括Acceptor和Thread Pool。

Acceptors

Acceptor的个数accepts应该设置为大于等于1并且小于等于CPU核数。

Thread Pool

限制Jetty的任务队列非常重要。默认情况下队列是无限的因此如果在高负载下超过Web应用的处理能力Jetty将在队列上积压大量待处理的请求。并且即使负载高峰过去了Jetty也不能正常响应新的请求这是因为仍然有很多请求在队列等着被处理。

因此对于一个高可靠性的系统我们应该通过使用有界队列立即拒绝过多的请求也叫快速失败。那队列的长度设置成多大呢应该根据Web应用的处理速度而定。比如如果Web应用每秒可以处理100个请求当负载高峰到来我们允许一个请求可以在队列积压60秒那么我们就可以把队列长度设置为60 × 100 = 6000。如果设置得太低Jetty将很快拒绝请求无法处理正常的高峰负载以下是配置示例

<Configure id="Server" class="org.eclipse.jetty.server.Server">
    <Set name="ThreadPool">
      <New class="org.eclipse.jetty.util.thread.QueuedThreadPool">
        <!-- specify a bounded queue -->
        <Arg>
           <New class="java.util.concurrent.ArrayBlockingQueue">
              <Arg type="int">6000</Arg>
           </New>
      </Arg>
        <Set name="minThreads">10</Set>
        <Set name="maxThreads">200</Set>
        <Set name="detailedDump">false</Set>
      </New>
    </Set>
</Configure>

那如何配置Jetty的线程池中的线程数呢跟Tomcat一样你可以根据实际压测如果I/O越密集线程阻塞越严重那么线程数就可以配置多一些。通常情况增加线程数需要更多的内存因此内存的最大值也要跟着调整所以一般来说Jetty的最大线程数应该在50到500之间。

Jetty性能测试

接下来我们通过一个实验来测试一下Jetty的性能。我们可以在这里下载Jetty的JAR包。

第二步我们创建一个Handler这个Handler用来向客户端返回“Hello World”并实现一个main方法根据传入的参数创建相应数量的线程池。

public class HelloWorld extends AbstractHandler {

    @Override
    public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
        response.setContentType("text/html; charset=utf-8");
        response.setStatus(HttpServletResponse.SC_OK);
        response.getWriter().println("<h1>Hello World</h1>");
        baseRequest.setHandled(true);
    }

    public static void main(String[] args) throws Exception {
        //根据传入的参数控制线程池中最大线程数的大小
        int maxThreads = Integer.parseInt(args[0]);
        System.out.println("maxThreads:" + maxThreads);

        //创建线程池
        QueuedThreadPool threadPool = new QueuedThreadPool();
        threadPool.setMaxThreads(maxThreads);
        Server server = new Server(threadPool);

        ServerConnector http = new ServerConnector(server,
                new HttpConnectionFactory(new HttpConfiguration()));
        http.setPort(8000);
        server.addConnector(http);

        server.start();
        server.join();
    }
}

第三步我们编译这个Handler得到HelloWorld.class。

javac -cp jetty.jar HelloWorld.java

第四步启动Jetty server并且指定最大线程数为4。

java -cp .:jetty.jar HelloWorld 4

第五步启动压测工具Apache Bench。关于Apache Bench的使用你可以参考这里

ab -n 200000 -c 100 http://localhost:8000/

上面命令的意思是向Jetty server发出20万个请求开启100个线程同时发送。

经过多次压测测试结果稳定以后在Linux 4核机器上得到的结果是这样的

从上面的测试结果我们可以看到20万个请求在9.99秒内处理完成RPS达到了20020。 不知道你是否好奇为什么我把最大线程数设置为4呢是不是有点小

别着急接下来我们就试着逐步加大最大线程数直到找到最佳值。下面这个表格显示了在其他条件不变的情况下只调整线程数对RPS的影响。

我们发现一个有意思的现象线程数从4增加到6RPS确实增加了。但是线程数从6开始继续增加RPS不但没有跟着上升反而下降了而且线程数越多RPS越低。

发生这个现象的原因是测试机器的CPU只有4核而我们测试的程序做得事情比较简单没有I/O阻塞属于CPU密集型程序。对于这种程序最大线程数可以设置为比CPU核心稍微大一点点。那具体设置成多少是最佳值呢我们需要根据实验里的步骤反复测试。你可以看到在我们这个实验中当最大线程数为6也就CPU核数的1.5倍时,性能达到最佳。

本期精华

今天我们首先学习了Jetty调优的基本思路主要分为操作系统级别的调优和Jetty本身的调优其中操作系统级别也适用于Tomcat。接着我们通过一个实例来寻找Jetty的最佳线程数在测试中我们发现对于CPU密集型应用将最大线程数设置CPU核数的1.5倍是最佳的。因此,在我们的实际工作中,切勿将线程池直接设置得很大,因为程序所需要的线程数可能会比我们想象的要小。

课后思考

我在今天文章前面提到Jetty的最大线程数应该在50到500之间。但是我们的实验中测试发现最大线程数为6时最佳这是不是矛盾了

不知道今天的内容你消化得如何?如果还有疑问,请大胆的在留言区提问,也欢迎你把你的课后思考和心得记录下来,与我和其他同学一起讨论。如果你觉得今天有所收获,欢迎你把它分享给你的朋友。