什么是流式计算
流式计算一般被用来和批量计算做比较。批量计算往往有一个固定的数据集作为输入并计算结果。而流式计算的输入往往是“无界”的(Unbounded Data),持续输入的,即永远拿不到全量数据去做计算;同时,计算结果也是持续输出的,只能拿到某一个时刻的结果,而不是最终的结果。(批量计算是全量的:拿到一批数据,计算一个结果;流式计算是增量的:数据持续输入,持续计算最新的结果)
流式计算框架
Storm | Trident | Spark Streaming | Flink | Samza | Kafka streams | |
---|---|---|---|---|---|---|
数据流模型 | 原生 | 微批 | 微批 | 原生 | 原生 | 原生 |
状态存储 | 不支持状态管理 | 本地存储,外部数据库 | 多种状态存储方式 | 多种状态存储方式 | 本地存储,Kafka主题 | 本地存储,日志变更主题 |
时延 | 低 | 高 | 高 | 低 | 低 | 低 |
吞吐量 | 低 | 高 | 高 | 高 | 高 | 高 |
保障机制 | at-least-once | exactly-once | exactly-once | exactly-once | at-least-once | exactly-once |
容错机制 | record ack | record ack | RDD based,checkpoint | checkpoint | Kafka log-base | Kafka log |
成熟度 | 较多不足,但实际应用比较广泛 | Storm基础上改进 | 流行的框架之一,Spark大环境 | 较新的流处理框架,性能非常优秀 | 基于Kafka作为数据源 | 完全基于Kafka集群实现 |
定位 | 框架 | 框架 | 框架 | 框架 | 框架 | 类库 |
Kafka Streams
Kafka Streams is a client library for building applications and microservices, where the input and output data are stored in Kafka clusters. It combines the simplicity of writing and deploying standard Java and Scala applications on the client side with the benefits of Kafka’s server-side cluster technology.
Kafka Streams是一个构建应用程序和微服务的客户端库,并且输入数据个输出数据均是保存在Kafka集群上的。Kafka Streams主要有如下特点:
- 非常简单的客户端库,可以非常容易的嵌入到任何java应用程序与任何应用程序进行封装集成。
- 使用Kafka集群作为消息层,没有外部依赖。
- 支持本地状态存储。
- 提供了快速故障切换分布式处理和容错能力。
- 提供了非常方便的API。
- 支持
exactly-once
语义 - 支持纪录级的处理,实现毫秒级的延迟
- 提供High-Level的Stream DSL和Low-Level的Processor API
采用one-record-at-a-time
的消息处理方式,实现消息处理的低延迟。
但是Kafka Streams的设计目标是足够轻量,所以很难满足对大体量的复杂计算需求,并且数据的输入和输出均是依靠Kafka集群,对于其他的数据源需要借助Kafka connect将数据输入到Kafka主题中,然后在通过Kafka Streams程序进行处理,并通过Kafka connect将主题中的数据转存到其他数据源。
所以Kafka Streams更适合计算复杂度较小,数据流动过程是Kafka->Kafka
的场景。
Storm
在Storm中,需要先设计一个实时计算结构,我们称之为拓扑(topology)。之后,这个拓扑结构会被提交给集群,其中主节点(master node)负责给工作节点(worker node)分配代码,工作节点负责执行代码。在一个拓扑结构中,包含spout和bolt两种角色。数据在spouts之间传递,这些spouts将数据流以tuple元组的形式发送;而bolt则负责转换数据流。
- 状态管理:无状态,需用户自行进行状态管理
- 窗口支持:对事件窗口支持较弱,缓存整个窗口的所有数据,窗口结束时一起计算
- 消息投递:At Most Once/At Least Once
- 容错方式:对每个消息进行全链路跟踪,失败或超时进行重发。
Spark Streaming
采用微批的方式,提高了吞吐性能。Spark streaming批量读取数据源中的数据,然后把每个batch转化成内部的RDD。Spark streaming以batch为单位进行计算,而不是以record为单位,大大减少了ack所需的开销,显著满足了高吞吐、低延迟的要求,同时也提供exactly once功能。但也因为处理数据的粒度变大,导致Spark streaming的数据延时不如Storm,Spark streaming是秒级返回结果(与设置的batch间隔有关),Storm则是毫秒级。
但是Spark Streaming的优点是可以与Spark大环境进行有效的结合。
Flink
Flink 是一种可以处理批处理任务的流处理框架。Flink 流处理为先的方法可提供低延迟,高吞吐率,近乎逐项处理的能力,并且提供了复杂计算的能力。
Flink 完全支持流处理,也就是说作为流处理看待时,输入数据流是无界的;批处理被作为一种特殊的流处理,只是它的输入数据流被定义为有界的。这与 Spark streaming 不同,Spark streaming 是将流处理视为无限个有界的批处理(microbatch)。
- 有状态计算的 Exactly-once 语义。状态是指 flink 能够维护数据在时序上的聚类和聚合,同时它的 checkpoint 机制可以方便快速的做出失败重试;
- 支持带有事件时间(event time)语义的流处理和窗口处理。事件时间的语义使流计算的结果更加精确,尤其在事件到达无序或者延迟的情况下;
- 支持高度灵活的窗口(window)操作。支持基于 time、count、session,以及 data-driven 的窗口操作,能很好的对现实环境中的创建的数据进行建模;
- 轻量的容错处理(fault tolerance)。它使得系统既能保持高的吞吐率又能保证 exactly-once 的一致性。通过轻量的 state snapshots 实现;
- 支持高吞吐、低延迟、高性能的流处理;
- 支持 savepoints 机制(一般手动触发),可以将应用的运行状态保存下来;在升级应用或者处理历史数据上,能够做到无状态丢失和最小停机时间;
- 支持大规模的集群模式,支持 yarn、Mesos。可运行在成千上万的节点上;
- 支持具有 Backpressure 功能的持续流模型;
- Flink 在 JVM 内部实现了自己的内存管理,包括完善的内存架构和 OOM error prevention;
- 支持迭代计算;
- 支持程序自动优化:避免特定情况下 Shuffle、排序等昂贵操作,中间结果进行缓存。
说明一
通常来说,对于单独的消息系统而言,语义分为如下三种:
至多一次(At most once):不管 Writer 在等待 ACK 时是否发生超时或者得到错误异常,Writer 都不会重新发送 Event,因此会有数据丢失的风险。在具体的实现过程中,这一种语义无需做任何额外的控制,实现起来最为简单,因此也通常有着最优的性能。在某些特定的场景中,我们只希望追求极致的性能而不关心数据的丢失,可能会选用此方案。
至少一次(At least once):如果 Writer 在等待 ACK 时发生超时或者得到错误异常,Writer 将会重新发送消息,这样能保证每个 Event 至少被处理一次,保证了数据不会丢失,从而提高了系统的可靠性,但同时会带来数据重复的问题,例如,当 Writer 往 Stream 中成功写入一个 Event,但是当系统尝试给 Writer 返回 ACK 的时候出现网络异常,Writer 因没有收到 ACK 而判断为写入 Event 失败,因此 Writer 还是会重新发送此 Event,导致数据重复。
仅一次(Exactly once):在系统发生异常时,Writer 可以尝试多次重新发送 Event,同时能保证最终每个 Event 只被写入一次。一些对数据准确性要求非常高的系统需要保证 exactly-once 语义,譬如支付系统,当用户在移动端付款时,很有可能会因为网络原因导致延时较长甚至超时,用户可能会手动进行刷新操作,如果没有 exactly-once 的语义支持,很有可能会发生两次扣费,我们绝对不希望此类错误发生。
说明二
测试结论
推荐使用 Flink 的场景
- 实时计算场景建议考虑使用 Flink 框架进行计算:
- 要求消息投递语义为 Exactly Once 的场景;
- 数据量较大,要求高吞吐低延迟的场景;
- 需要进行状态管理或窗口统计的场景。
说明三 - 语言支持
Spark和flink都是JVM语言开发,在API层对java和Scala语言支持较好,python语言API支持,但是效率不高,其它语言几乎不支持。