4

我正在尝试在平均响应时间为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 等。

提前致谢!

4

1 回答 1

1

我的第一个想法是看看其他垃圾收集器,因为自 CMS 以来已经有了很多改进,但你说你不打算探索那个...... ;-)

CMS 行为的各个方面都是可调的,可能会对您的情况有所帮助。一种是增量模式,您可以通过以下方式启用:  -XX:+CMSIncrementalMode.

并发标记扫描 (CMS) 收集器

通常,CMS 收集器在整个并发跟踪阶段使用一个或多个处理器,而不会自愿放弃它们。类似地,一个处理器用于整个并发扫描阶段,同样不会放弃它。对于具有响应时间限制的应用程序来说,这种开销可能会造成太大的破坏,否则这些应用程序可能会使用处理内核,尤其是在只有一个或两个处理器的系统上运行时。增量模式通过将并发阶段分解为短暂的活动突发来解决此问题,这些活动计划在小暂停之间发生。

还有一些旋钮可以扭转“工作周期”(在上面链接的同一个文档中),这可能会有所帮助——例如:-XX:+CMSIncrementalPacing -XX:CMSIncrementalDutyCycleMin=0 -XX:CMSIncrementalDutyCycle=10——但它们不那么简单,你需要花一些时间进行测试和观察。

于 2019-12-14T03:40:37.750 回答