我正在尝试在平均响应时间为5 ms的 Web 应用程序中实现并发限制器。
我的实现基于“负载下的性能”,分叉了项目并发限制,并使用了令人惊叹的技术演讲“停止速率限制!容量管理完成”中解释的概念。
我测量了我的应用程序在正常条件下的并发请求量(每秒正常请求量,正常响应时间),我得到以下结果:
1) 平均值为1,7 2) Perc 95 为3.2 3) 最大值达到45,50,60,具体取决于样品。
有了这些数字,我决定将并发请求的最大容量配置为45。在这一点上,我还没有问自己为什么最大值与平均值如此不同。
然后,我开始测试并发限制器,我发现由于达到我配置的阈值45,某些请求最终被拒绝。
值得一提的是,我正在使用AIMDLimit 实现来动态修改最大容量。但我也测量了每个值,最大容量的值永远不会低于 40。
因此,我对我的应用程序进行了一些研究,并发现了以下内容。每次我的应用程序执行 Minor GC 或 Major GC(使用 CMS)时,N 的值都会增加很多。测量值从 1、2 甚至 3 变为 10、11、12,当执行完整的 GC 时,测量值甚至上升到 40、50、60(这是 N 高于我的阈值和请求被拒绝)。
这种行为是有道理的,因为当执行 Minor 或 Major GC 时,由于我的应用程序位于 tomcat 容器后面,并且 tomcat 容器使用 SO 队列来轮询挂起的请求(请参阅“ Tuning Tomcat For A High Throughput, Fail Fast System ” ),所以没问题N的值也增加了。
例如,让我们分析以下情况。
1) 应用程序正在处理 3 个并发请求
2) 执行一次 GC,耗时 30ms
3) 还有 10 个请求到达并保留在 SO 队列中,等待 tomcat 轮询它们
4)GC完成
5)轮询10个请求,N(并发请求)的值现在上升到13
这里的问题是我还使用 jstat 测量了我的应用程序的 gc 时间,它们看起来并不那么糟糕:
+-----------+------+-------+-------+--------+-------+-------+------+---------+-----+--------+---------+
| Timestamp | S0 | S1 | E | O | M | CCS | YGC | YGCT | FGC | FGCT | GCT |
+-----------+------+-------+-------+--------+-------+-------+------+---------+-----+--------+---------+
| 91071.2 | 0.00 | 10.13 | 94.37 | 56.99 | 96.92 | 95.11 | 4399 | 368.077 | 64 | 22.428 | 390.505 |
| 91073.2 | 8.36 | 0.00 | 3.18 | 57.16 | 96.92 | 95.11 | 4400 | 368.178 | 64 | 22.428 | 390.606 |
| ******* | **** | **** | **** | ***** | ***** | ***** | **** | ******* | ** | ****** | ******* |
| ******* | **** | **** | **** | ***** | ***** | ***** | **** | ******* | ** | ****** | ******* |
| 91099.9 | 9.69 | 0.00 | 99.87 | 32.73 | 96.78 | 94.90 | 4386 | 318.084 | 66 | 19.694 | 337.778 |
| 91101.9 | 0.00 | 9.60 | 9.72 | 32.99 | 96.78 | 94.90 | 4387 | 318.135 | 66 | 19.694 | 337.830 |
| ******* | **** | **** | **** | ***** | ***** | ***** | **** | ******* | ** | ****** | ******* |
| ******* | **** | **** | **** | ***** | ***** | ***** | **** | ******* | ** | ****** | ******* |
+-----------+------+-------+-------+--------+-------+-------+------+---------+-----+--------+---------+
这些措施来自执行的年轻收集,可见年轻收集时间不会持续这么长时间。
368.077 -> 368.178 ( 101 毫秒) 318.084 -> 318.135 ( 51 毫秒)
我也测量了完整的 gc 时间
+-----------+------+------+-------+-------+--------+-------+-------+---------+-----+--------+---------+
| Timestamp | S0 | S1 | E | O | M | CCS | YGC | YGCT | FGC | FGCT | GCT |
+-----------+------+------+-------+-------+--------+-------+-------+---------+-----+--------+---------+
| ******* | **** | **** | **** | ***** | ***** | ***** | **** | ******* | ** | ****** | ******* |
| 91879.8 | 0.00 | 7.51 | 23.57 | 68.12 | 96.92 | 95.11 | 4437 | 372.348 | 65 | 22.432 | 394.780 |
| 91881.8 | 6.58 | 0.00 | 8.25 | 9.51 | 96.92 | 95.12 | 4438 | 372.465 | 66 | 23.066 | 395.531 |
+-----------+------+------+-------+-------+--------+-------+-------+---------+-----+--------+---------+
22.432 -> 23.066 ( 634 ms ) 我相信完整 gc 的测量并不意味着在整个持续时间内停止世界暂停
我所做的另一件事是让 Jstat 在一个选项卡中运行,并在另一个选项卡中跟踪 N(并发请求)的值的日志。正如我所预料的那样,每次触发年轻或完整的 gc 时,N 都会上升很多。
所以,在这个序言之后......我的问题是。
有没有什么好的方法可以限制 gc 暂停时间超过平均响应时间的应用程序的并发能力?
还值得一提的是,我们的 gc 暂停对于我们的往返请求时间来说不是问题。换句话说,对于客户来说没有问题,我也不打算开始讨论如何改进它们或是否不推荐使用 CMS 等。
提前致谢!