导
Lead
语
异步系统中为什么会出现OOM问题?如何避免?揭秘时刻到啦!今天小编带大家了解异步系统中的OOM问题。
在 Java 编程中,我们经常使用 ExecutorService 线程池服务类来处理请求异步执行的问题。在这种方案中,请求的具体逻辑实际上是交给了各个步骤的执行器(executor)进行处理。在这整个过程中没有任何阻塞的地方,各个步骤待处理的任务都被隐式地存放在了各个执行器的任务队列中。如果各执行器处理得足够快,那么它们的任务队列都能被及时消费,这样不会存在问题。但是,一旦有某个步骤的处理速度比不上请求接收线程接收新请求的速度,那么必定有部分执行器任务队列中的任务会不停增长。由于执行器任务队列默认是非阻塞且不限容量的,这样当任务队列里积压的任务越来越多时,终有一刻, JVM 的内存会被耗尽,抛出 OOM 系统错误后程序异常退出。
图 1 : 任务在各个 Executor 队列中积压
实际上,这也是所有异步系统都普遍存在,而且必须引起我们重视的问题。在纤程里,可以通过指定最大纤程数量来限制内存的使用量,非常自然地控制了内存和流量。但是在一般的异步系统里,如果不对执行的各个环节做流量控制,就很容易出现前面所说的OOM问题。因为当每个环节都不管其下游环节处理速度是否跟得上,不停将其输出塞给下游的任务队列时,只要上游输出速度超过下游处理速度的状况持续一段时间,必然会导致内存不断被占用,直至最终耗尽,抛出 OOM 灾难性系统错误。
为了避免OOM问题,我们必须对上游输出给下游的速度做流量控制。一种方式是严格控制上游的发送速度,比如每秒控制其只能发 1000 条消息。但是这种粗糙的处理方案会非常低效。比如如果实际下游能够每秒处理 2000 条消息,那上游每秒 1000 条消息的速度就使得下游一半的性能没发挥出来。再比如如果下游因为某种原因性能降级为每秒只能处理 500 条,那在一段时间后同样会发生 OOM 问题。
更优雅的一种解决方法是被称为反向压力的方案,即上游能够根据下游的处理能力动态调整输出速度。当下游处理不过来时,上游就减慢发送速度;当下游处理能力提高时,上游就加快发送速度。反向压力的思想,实际上正逐渐成为流计算领域的共识,比如与反向压力相关的标准Reactive Streams正在形成过程中。图 2 演示了 Reactive Streams 的工作原理,下游的消息订阅从上游的消息发布者接收消息前,会先通知消息发布者自己能够接收多少消息,消息发布者之后就按照这个数量向下游的消息订阅者发送消息。这样,整个消息传递的过程都是量力而行的,就不存在上下游处理能力不匹配造成的 OOM 问题了。
图2: Reactive Stream s 工作原理
那在 Java 中具体该怎样实现反向压力功能呢?我们继续用 ExecutorService 线程池来进行说明。由于请求接收线程接收的新请求及其触发的各项任务是被隐式地存放在各步骤的执行器任务队列中,并且执行器默认使用的任务队列是非阻塞和不限容量的。因此要加上反向压力的功能,只需要从两个方面来控制:
第一,执行器任务队列容量必须有限。
第二,当执行器任务队列中的任务已满时,就阻塞上游继续向其提交新的任务,直到任务队列重新有空间可用为止。
图3 : 使用容量有限的阻塞队列实现反向压力
按照上面这种思路,我们可以很容易地实现反向压力。图 3 展示了使用容量有限的阻塞队列实现反向压力的过程,当 process 这个步骤比 decode 步骤慢时,位于 process 前的容量有限的阻塞队列会被塞满。当 decode 继续要往其写入消息时,就会被阻塞,直到 process 从队列中取走消息为止。
作者简介: 周爽 ,本硕毕业于华中科技大学,先后在华为2012实验室高斯部门和上海行邑信息科技有限公司工作。开发过实时分析型内存数据库RTANA、华为公有云RDS服务、移动反欺诈MoFA等产品。目前担任公司技术部架构师一职。著有 《实时流计算系统设计与实现》 一书。
扫码了解详情并购买
↓↓↓点击“阅读原文”直达开学季促销专场
以上所述就是小编给大家介绍的《反向压力:异步系统中的OOM问题》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:- 没有压力的“压力测试”:LSTM神经网络是如何预测焦虑的?
- [原]压力测试
- jmeter 分布式压力测试
- 使用ab压力命令测试网站性能
- 漫谈数据治理(一):计算与存储压力
- multi-mechanize负载压力
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。