在
《RocketMQ 流存储解析:面向流场景的关键特性与典型案例》
一文中我们介绍了 RocketMQ 面向数据集成提供的流存储的能力,基于流存储和业界主流的分布式流计算引擎结合,比如 Flink、Spark,能为用户提供完整的流处理能力。然而,在某些场景下,我们有机会为用户提供更简化的流处理方案,不需要维护多套分布式系统,通过 RocketMQ 5.0,就能提供一体化的流处理。
本文第一部分,我们将从概念和宏观角度理解什么是流处理;第二部分,我们再回到 RocketMQ 5.0,介绍 RocketMQ 提供的轻量流处理引擎 RStreams,了解其特性和原理;第三部分,我们将介绍 RocketMQ 的流数据库 RSQLDB,如何通过流存储和流计算的深度结合,进一步降低流处理使用门槛。
让我们先来了解一下什么是流处理?
流处理过程包括流数据摄入、流数据存储和流计算。
第一个概念是
流数据
,它是与批量数据、离线数据相对的。其特点在于数据源源不断的产生,并且有一定的顺序,从而形成一个无边界数据流,类似于现实世界中的河流。比如信用卡交易、股票交易、IoT 设备传感数据,都可以认为是流数据。
第二个概念是
流存储
,在
《RocketMQ 流存储解析:面向流场景的关键特性与典型案例》
一文中重点介绍过,这里简要回顾一下。流存储就是面向流式数据进行深度优化的存储系统,类似于日志(Log),提供按照分区、位点进行读写操作的能力,数据是持续追加且不可变的。典型的流存储有 RocketMQ、KAFKA、AWS 的 Kinesis Data Streams。
第三个概念是
流计算
,面向流式数据的计算引擎,它主要的特征是实时计算、低延迟,能够实现有状态计算,比较典型的流计算引擎有 Flink、Spark Streaming、Kafka 的 KStreams 等等。
那么,通常什么时候会用到流处理场景呢?相对于批处理——通常是天级别的计算延迟,流处理更侧重于需要实时响应的场景,比如信用卡欺诈检测、股票实时投资、工厂设备维护,还有舆情实时监控等等。
流处理过程主要包括流数据的摄入、存储以及流式计算三个环节。虽然摄入和存储也是重要环节,但本文将聚焦于流计算所需的技术能力。
流计算的数据流可以概括为三个步骤:数据输入,数据转换,数据输出。
我们通过一个简单的案例 WordCount 来解释流计算的技术要点。看右边这个图,数据输入是实时产生的语句流,我们希望能够按照时间窗口统计每个单词出现的次数,按时间输出统计结果。我们基于流计算引擎,只需要写少量的代码,比如左下角的图,即可完成任务。
从这个案例,我们可以总结出流计算引擎需要具备的关键能力:
首先,需要支持丰富的可重用算子,采用函数式编程的方式,提升流计算的开发效率。
其次,需要具备容错能力,在计算过程中节点宕机时,能够通过重启或者其他计算节点接管恢复计算。
再来,流数据往往是大规模的,比如 IoT 设备产生的大规模传感数据,往往超出单机的计算能力。流计算引擎要具备大规模并行计算能力。
最后,流计算的结果往往用于关键业务决策,流计算引擎要能做到在大规模并行、容错切换、资源调度等场景下,保障计算结果的正确性。
面向流处理场景,RocketMQ 5.0 提供了原生的轻量流计算引擎 RStreams,它有三个特点:
首先,只依赖 RocketMQ 的原生技术栈,基于 RocketMQ 的不同类型 Topic 实现数据流处理,适合轻量输出、边缘计算场景。
其次,它的用法也很轻量,不用搭建流计算平台,用户没有额外的运维负担,直接使用 RStreams 的 SDK 编写流计算逻辑,并内嵌到业务应用(或者微服务中)即可。
最后,它覆盖了主流场景的所有算子,具备完整的流计算能力。包括无状态算子,比如过滤、map 等等,以及有状态算子,如聚合计算、窗口计算等等。
对于一个流计算引擎来说,最关键的是要了解整个数据流的情况。虽然从使用角度看,流计算是一次输入、转换和一次输出,而实际的实现过程中,流计算是由多个更加原子的算子多次输入、计算、输出组合在一起,涉及复杂的数据流图。
RStreams 完全是基于 RocketMQ 的流存储能力来实现数据流,面向用户的输入、输出分别对应 Source Topic 和 Sink Topic,而中间件的计算过程要基于 State Topic(即 CompactTopic)来维护流计算的中间状态,在计算过程可能还需要进行数据交换,比如按照单词统计词频中会用到 KeyBy 算子,RStreams 是基于 Shuffle topic 来实现的。
数据交换 - Shuffle Topic
关于 Shuffle Topic,我们再简单看一下。还是以 WordCount 为例,我们希望每个句子切割成单词后,要按照单词统计频率,这就需要把同一个单词的数据放到同一个计算实例上计数。RStreams 的实现就是把单词作为 Key hash 到同一个队列,基于 RocketMQ 的消费负载算法就可以保障同一个单词都在一个计算实例上统计。这就是 RStreams 的数据交换机制。
我们再来看 RStreams 的另一个关键技术点——状态管理。
状态管理有两种场景,一种是容错场景,这里只需要依赖 RocketMQ 队列位点重放能力实现 checkpoint 机制就可以恢复计算状态。
另一种场景是有状态计算的中间计算结果维护,RStreams 通过 RocksDB 作为本地状态管理器,提供高性能、低延迟的状态读写,同时也基于 RocketMQ 的 CompactTopic 维护远程状态,定期和本地状态同步。这样一来,当本地节点磁盘损坏或者计算节点重新调度后,还可以从一个统一的数据存储中心恢复状态,提高状态数据的可靠性。
有状态算子 - Windows 举例
我们以 WordCout 案例中的窗口计算为例,来了解 RStreams 的有状态算子状态维护。
这个案例里,首先通过 Shuffle Topic 完成单词的分组统计,单词词频统计是按照时间窗口刷新,所以这里状态维护用的 Key 是 Topic + Q + 窗口时间 + 单词,Value 是统计数量,定时刷新到 RStreams 的状态存储中。当出现宕机,进行容错恢复后,窗口中的数据不用从头重新计算,保障流计算的实时性。
RStreams 的大规模并行计算,直接复用 RocketMQ 的无限扩展能力和负载均衡机制。比如基于 RocketMQ 的数据分片,流存储可以实现无限扩展;基于 RocketMQ 的分片负载消费模式,流计算节点也可以实现无限扩展。
下面我们再详细了解一下 RStreams 弹性伸缩的过程。在
《RocketMQ 流存储解析:面向流场景的关键特性与典型案例》
一文中提到,对于 RStreams 计算调度主要依赖 RocketMQ 的消费者队列负载均衡机制,数据源的每个数据分片只会被一个 RStreams 的实例读取计算。发生扩缩容的时候,会按照负载均衡算法重新分配计算节点。
除此之外,涉及有状态计算的时候,RStreams 还需要依赖 Compact Topic 维护状态,Compact Topic 的队列分布需要和 SourceTopic 保持一致,这样一来,数据源和对应的状态存储就都会被同一个 RStreams 计算节点重新加载。比如下面这张图,在发生缩容的时候,SourceTopic 队列 2 的数据和状态都调度到 RStreams 实例 2,从 checkpoint 加载数据恢复计算。