知识卡片

Streaming数据积压问题:用"先初始化、后调度"解决Receiver和Executor的就绪时序不匹配

普通读书笔记卡

内容

在Receiver-Base架构的Spark Streaming里,Receiver负责从外部接收数据并缓存到本地内存,同时向Driver汇报接收进度,Driver则定期生成任务分发给Executor去消费这些数据——正常情况下这个”接收-汇报-消费”的节奏是协调的。但Hulu发现一个真实的时序问题:应用启动时,Driver会把Receiver处理程序调度到各个Executor上并初始化,Receiver一旦初始化完毕就会立刻开始源源不断地接收数据,但如果此时Executor处理端还没准备好(比如Hulu内部很多实时机器学习应用需要先加载和初始化算法模型,这个过程可能耗费数十分钟),Receiver端就会持续积压数据、长期占用内存,大部分数据甚至会撑过新生代回收年龄进入老年代,GC压力随之越来越大,最终可能把内存耗尽。这个问题的本质是Receiver(数据接收方)和Executor(数据处理方)两者的就绪时间点不同步——Receiver天然会比需要长时间初始化的Executor更早就绪,导致这段时间差里产生的数据无处安放。Hulu的解法是要求每个Executor在真正开始接收任何任务之前,先执行一个用户自定义的初始化任务(可以在这个任务里做模型加载等耗时准备工作),为此专门新增了一个接口让用户能配置这个自定义初始化逻辑,并修改了Spark的任务调度器:把每个Executor先标记为”未初始化”状态,调度器在这个状态下只会给它分配初始化任务、不会分配包括启动Receiver在内的任何其他类型任务;只有当Executor完成初始化任务、调度器把它的状态更新为”已初始化”之后,才会真正把启动Receiver这类正常任务分配给它——这样Receiver开始接收数据的时间点,被强制推迟到了Executor真正准备好之后,从根源上消除了”接收方先就绪、处理方后就绪”这个时序错配。这个案例给出了一条应对”生产者和消费者就绪时机不匹配导致数据积压”这类问题的通用思路:与其在数据已经开始堆积之后再想办法缓解(扩大缓冲区、加速消费),不如从源头上控制生产者的启动时机,让生产者的就绪状态显式依赖于消费者已经真正准备好这个前提条件,用调度层面的强制顺序,直接消灭”生产者抢跑”这个问题本身,而不是被动应对它造成的后果。

参考来源

- 位置:《高可用架构(第1卷)》第6章《大数据与数据库》"6.9 大数据盘点之Spark篇"节,"6.9.2 Spark在Hulu的实践"(源文件:_epub-src/OEBPS/Text/Chapter6_9_3.xhtml) - 结论依据:原文说明"在某些场景下,Executor处理端还并没有准备好,无法开始处理数据。此时Receiver端就会发生数据积压……Hulu采用的解决方法是要求每个Executor接收任何任务之前先执行一个用户定义的初始化任务……调度器不会给未初始化状态的配其他类型的任务。等Executor运行完初始化任务,调度器更新Executor的状态为已初始化后,这样的Driver就可将正常任务(包括初始化Receiver的任务)分配给Executor了",直接支撑本卡片结论。 - 原始内容:Hulu采用的解决方法是要求每个Executor接收任何任务之前先执行一个用户定义的初始化任务,在初始化任务中可以执行一些独立的用户代码……除了初始化任务外,调度器不会给未初始化状态的配其他类型的任务。