RocksDB是Flink中的一个状态后端,它允许作业的状态大于可用内存量,因为状态后端可以将状态溢出到本地磁盘。这意味着磁盘性能可能会影响使用RocksDB的Flink作业的性能。通过一个案例研究,本文说明了使用RocksDB的Flink作业的吞吐量下降问题,并演示了我们如何将底层磁盘的性能确定为根本原因。
作业和执行环境
我们处理的是一个典型的物联网(IoT)工作,它处理数百万台设备发出的事件流。每个事件都包含设备标识符(ID)、事件类型和生成事件时的时间戳。作业基于设备ID对流进行分区,并以状态存储从每个事件类型到接收到该类型事件时的最新时间戳的映射。可以有数百种事件类型。对于每个传入事件,作业需要从接收到的事件类型的状态读取时间戳,并将其与传入事件进行比较。如果传入的时间戳较新,它会更新状态中存储的时间戳。
作业运行在Amazon Elastic Kubernetes服务(EKS)集群上,该集群是使用官方AWS命令行工具eksctl创建的,具有所有默认设置。Flink TaskManager分配了1.5个CPU内核和4 GB内存。作业使用RocksDB状态后端,该后端配置为使用Flink的托管内存。state.backend.rocksdb.localdir配置选项没有显式设置,因此默认情况下,底层EC2实例根卷上的/tmp目录用于rocksdb。
症状
这项工作最初在EKS上运行良好。但是,经过一段时间(数小时或数天,取决于传入的事件),作业吞吐量突然显著下降。下面的吞吐量度量图显示,在给定的一天中,在23:50之后不久,吞吐量从每秒10k以上的事件下降到每秒几百个事件。

此外,使用保存点停止作业,然后从中恢复也没有帮助:重新启动后,作业吞吐量仍然很低。虽然当作业从空状态重新启动时恢复了高吞吐量,但这不是一个选项,因为
(1)作业状态将丢失,(2)作业吞吐量将在较短时间后再次下降。
分析
通过检查CPU指标,我们注意到当吞吐量下降时,TaskManager容器的CPU利用率也降低了。由于TaskManager容器可能会使用更多的CPU资源(与吞吐量下降之前相同),因此CPU使用量的减少在这里是一个症状。

TaskManager容器的内存使用率在吞吐量下降发生之前很长一段时间就达到了分配限制,在23:50左右没有明显变化。

为了调查是什么减慢了作业,我们通过设置以下TaskManager JVM选项启用了TaskManager JMX:
env.java.opts.taskmanager: >-
-Dcom.sun.management.jmxremote
-Dcom.sun.management.jmxremote.authenticate=false
-Dcom.sun.management.jmxremote.ssl=false
-Dcom.sun.management.jmxremote.local.only=false
-Dcom.sun.management.jmxremote.port=1099
-Dcom.sun.management.jmxremote.rmi.port=1099
-Djava.rmi.server.hostname=127.0.0.1
然后我们将一个本地运行的VisualVM连接到TaskManager上,并进行了CPU采样。如下面的CPU采样结果所示,线程UpdateState消耗了93%的CPU时间。这是运行操作符UpdateState的线程,它在RocksDB中读取和更新状态。

在UpdateState线程内部,如下面的屏幕截图所示,几乎所有的CPU时间都由本机方法org.rocksdb.rocksdb.get()占用。这告诉我们,在从RocksDB读取state时,该作业遇到了瓶颈。

为了进一步调查RocksDB在哪里花费时间,我们启用了以下Flink RocksDB指标:
state.backend.rocksdb.metrics.block-cache-capacity: true
state.backend.rocksdb.metrics.block-cache-pinned-usage: true
state.backend.rocksdb.metrics.block-cache-usage: true
state.backend.rocksdb.metrics.estimate-table-readers-mem: true
块缓存是RocksDB在内存中缓存数据以进行读取的地方。如下图所示,在作业启动的最初几分钟内,块缓存很快就被填满了,主要是由状态条目填充的。这仍然不能解释23:50左右吞吐量突然下降的原因。

默认情况下,RocksDB本机度量被禁用,因为它们可能会对您的工作产生负面的性能影响。在生产中使用时应小心。
当状态项不在RocksDB块缓存中时,从RocksDB读取它将涉及磁盘IO操作。我们继续检查根卷的磁盘度量。如下面两个图所示,当Flink作业吞吐量下降时,读取吞吐量下降到大约每秒230个操作。同样的情况也发生在写吞吐量上,它下降到10左右。检查每秒磁盘输入/输出操作(IOPS)容量,我们发现默认情况下,使用eksctl创建的EKS群集中的每个EC2实例都是一个m5.large实例,附带一个通用(gp2)弹性块存储(EBS)根卷。根卷的大小为80GB,提供240 IOPS的基准速率。这证实了磁盘已饱和,Flink作业在磁盘IO上遇到瓶颈。

当状态项不在RocksDB块缓存中时,从RocksDB读取它将涉及磁盘IO操作。我们继续检查根卷的磁盘度量。如下面两个图所示,当Flink作业吞吐量下降时,读取吞吐量下降到大约每秒230个操作。同样的情况也发生在写吞吐量上,它下降到10左右。检查每秒磁盘输入/输出操作(IOPS)容量,我们发现默认情况下,使用eksctl创建的EKS群集中的每个EC2实例都是一个m5.large实例,附带一个通用(gp2)弹性块存储(EBS)根卷。根卷的大小为80GB,提供240 IOPS的基准速率。这证实了磁盘已饱和,Flink作业在磁盘IO上遇到瓶颈。


我们在一开始就可以获得更高的IOPS的原因是AWS为维持突发IO请求而向每个gp2卷提供初始I/O学分。下面的突发余额指标证实了这一点:初始I/O信用已用尽,在问题发生时,突发余额降至0。这也解释了为什么重新启动作业没有帮助。

在确定了根本原因之后,克服此问题的解决方案是附加一个具有高IOPS速率的专用卷,例如gp3或io1/io2卷,然后将Flink configuration state.backend.rocksdb.localdir设置为该卷上的目录。
结论
这篇博文描述了Flink的一个工作吞吐量下降问题,以及我们为找到根本原因所做的调查。如我们所见,磁盘性能对Flink中RocksDB状态后端的性能有很大影响。这里的关键要点是:当使用状态大的RocksDB state后端时,访问状态预计会不断命中磁盘(例如,随机从RocksDB读取状态),您应该将Flink configuration state.backend.RocksDB.localdir设置为具有高IOPS速率的卷上的目录。请记住,突发性磁盘适合具有突发IO的作业。一个burstable磁盘最初可能会给您带来足够的性能,但随着时间的推移,当突发I/O信用耗尽时,它的性能可能会下降到基线。
猜您喜欢往期精选▼
Spark Shuffle调优之调节map端内存缓冲与reduce端内存占比
Flink中Checkpoint和Savepoint 的 3 个不同点
本文通过一个案例详细分析了磁盘性能如何影响使用RocksDB作为状态后端的Flink作业吞吐量。在物联网工作负载中,由于RocksDB状态溢出到磁盘,当磁盘IOPS达到极限,作业吞吐量显著下降。解决方案是将Flink的RocksDB本地目录设置为高IOPS卷。

209

被折叠的 条评论
为什么被折叠?



