TT Lab
开始
学习 学习路径 课程

Tomcat 与 nginx 运维

有依据地配置线程池、堆与 GC 日志

在 TT Lab 中继续学习

目标

亲手配置共享 Executor、acceptCount、Keep-Alive、堆和 GC 日志, 并能够用利特尔法则计算 maxThreads,留下依据文档。

为什么重要

出现性能问题时,总会有人说“把 maxThreads 调高吧”。但如果不知道这个值 作用在哪个环节,大多数情况下要么什么都不会改变,要么变得更糟。 maxThreads 是“同时处理中的请求”,maxConnections 是“持有的套接字”, acceptCount 是“连这个也满了之后的操作系统 backlog”,backlog 也满了,客户端收到的 不是 503,而是 Connection refused。懂得这个区别的人,能更快结束故障。

步骤

  1. 启动 Tomcat 后,在 server.xml 的 <Service> 内添加共享 Executor。 name="tomcatThreadPool"、namePrefix="labhub-exec-"、 maxThreads="150"、minSpareThreads="20"。
  2. 为 8080 连接器添加 executor="tomcatThreadPool" 属性,使其使用这个 Executor, 然后重启。发送几次请求后,导出线程转储并保存到 /root/tc/threads.txt。转储中必须能看到 labhub-exec- 线程。
  3. 为 8080 连接器设置 acceptCount="50"、maxConnections="2000"、 connectionTimeout="20000"。
  4. 为 8080 连接器设置 keepAliveTimeout="5000"、maxKeepAliveRequests="100", 重启后,用一次 curl 在同一个连接上发送两个请求, 并将其详细输出(-v)保存到 /root/tc/keepalive.txt。 文件中必须有 Re-using existing connection 或 Connection #0 to host 这类连接复用的痕迹。
  5. 在 /opt/tomcat/bin/setenv.sh 的 CATALINA_OPTS 中加入 -Xms512m -Xmx512m, 然后重启。正在运行的进程参数中必须能看到这两个值。
  6. 在同一个 CATALINA_OPTS 中追加 GC 日志选项,然后重启。 日志文件为 /root/tc/gc.log,文件轮转设置为 5 个、每个 10MB。 重启后 /root/tc/gc.log 必须已生成且不为空。
  7. 创建 /root/tc/load.sh。接收两个参数(URL 동시요청수,占位符依次为 URL 与并发请求数), 同时发送请求,并在最后一行输出 ok=<성공수> fail=<실패수>(占位符依次为成功数与失败数)。 HTTP 200 算成功,其他情况(包括 404)算失败。
  8. 编写 /root/tc/sizing.md。用下面的场景应用利特尔法则, 计算 maxThreads 的值,并留下计算过程和结果。
    • 目标吞吐量:300 TPS
    • 平均响应时间:200ms
    • 峰值余量率:30% 文档中必须恰好有一行形如 maxThreads=<계산값>(占位符为计算值)的内容, 并且 TPS、ms、여유율(韩文,意为“余量率”)这三个词都要出现。

参考

定义共享 Executor

启动 Tomcat 后,在 server.xml 的 <Service> 内添加共享 Executor。 name="tomcatThreadPool"、namePrefix="labhub-exec-"、 maxThreads="150"、minSpareThreads="20"。

Executor 是 Service 的子元素,必须先于 Connector 声明。给 namePrefix 起一个有意义的名字,之后在线程转储中就能立刻区分出来。

将连接器关联到 Executor

为 8080 连接器添加 executor="tomcatThreadPool" 属性,使其使用这个 Executor, 然后重启。发送几次请求后,导出线程转储并保存到 /root/tc/threads.txt。转储中必须能看到 labhub-exec- 线程。

给连接器指定 executor 属性后,该连接器的 maxThreads 会被忽略。是否真正生效,可以在线程转储中查看线程名称来确认。请使用 jcmd 或 jstack。

连接接纳参数

为 8080 连接器设置 acceptCount="50"、maxConnections="2000"、 connectionTimeout="20000"。

acceptCount 是 maxConnections 耗尽之后的操作系统 backlog。连这里也满了,客户端收到的不是 503,而是连接被拒绝。

设置并确认 Keep-Alive

为 8080 连接器设置 keepAliveTimeout="5000"、maxKeepAliveRequests="100", 重启后,用一次 curl 在同一个连接上发送两个请求, 并将其详细输出(-v)保存到 /root/tc/keepalive.txt。 文件中必须有 Re-using existing connection 或 Connection #0 to host 这类连接复用的痕迹。

用 curl -v 可以查看响应头中的 Connection 值以及连接是否被复用。用一次 curl 发送两个请求,就能观察到复用。

固定堆大小

在 /opt/tomcat/bin/setenv.sh 的 CATALINA_OPTS 中加入 -Xms512m -Xmx512m, 然后重启。正在运行的进程参数中必须能看到这两个值。

服务器应用程序的惯例是把最小堆和最大堆设为相同。目的是消除堆反复扩大缩小所引发的 GC。

启用 GC 日志

在同一个 CATALINA_OPTS 中追加 GC 日志选项,然后重启。 日志文件为 /root/tc/gc.log,文件轮转设置为 5 个、每个 10MB。 重启后 /root/tc/gc.log 必须已生成且不为空。

JDK 9 之后使用 -Xlog 统一日志。如果不同时指定文件轮转选项(个数和大小),GC 日志会把磁盘写满。

并发负载脚本

创建 /root/tc/load.sh。接收两个参数(URL 동시요청수,占位符依次为 URL 与并发请求数), 同时发送请求,并在最后一行输出 ok=<성공수> fail=<실패수>(占位符依次为成功数与失败数)。 HTTP 200 算成功,其他情况(包括 404)算失败。

在后台启动多个 curl,再用 wait 汇总,这种方式就足够了。每个请求是否成功,请用 HTTP 状态码来判定。

编写线程池容量估算依据

编写 /root/tc/sizing.md。用下面的场景应用利特尔法则, 计算 maxThreads 的值,并留下计算过程和结果。

利特尔法则:并发处理数 = 目标 TPS × 平均响应时间(秒)。乘以余量率之后向上取整。必须在文档中留下计算过程,6 个月后有人要改这个值时才有依据。