文章目录
- 概述
- 什么是流式处理
- 流式处理的一些概念
- 时间
- 状态
- 流和表的二元性
- 时间窗口
- 流式处理的设计模式
- 单个事件处理
- 使用本地状态
- 多阶段处理和重分区
- 使用外部查找——流和表的连接
- 流与流的连接
- 乱序的事件
- 重新处理
概述
Kafka被广泛认为是一种强大的消息总线,可以可靠地传递事件流,是流式处理系统的理想数据来源。流式处理系统通常是指一种处理实时数据流的计算系统,能够对数据进行实时的处理和分析,并根据需要进行相应的响应和操作。与传统的批处理系统不同,流式处理系统能够在数据到达时立即进行处理,这使得它们特别适合需要实时响应的应用程序,例如实时监控和警报、实时推荐、实时广告投放等。
Kafka的设计使其成为流式处理系统的理想数据源,因为它具有高吞吐量、低延迟和可靠性,并且能够轻松地扩展以处理大量数据。许多基于Kafka的流式处理系统,如Apache Storm、Apache Spark Streaming、Apache Flink和Apache Samza等,已经成功地应用于各种不同的场景中。
Kafka的流式处理类库提供了一种简单而强大的方式来处理实时数据流,并将其作为Kafka客户端库的一部分提供。这使得开发人员可以在应用程序中直接读取、处理和生成事件,而无需依赖外部的处理框架。Kafka的流式处理类库提供了许多有用的功能,如窗口化处理、状态存储和流处理拓扑构建等,使得开发人员能够轻松地构建强大的流式处理应用程序。
随着Kafka的流行和流式处理技术的发展,流式处理系统已经成为数据处理的一个重要领域,并且在越来越多的应用场景中得到广泛应用。Kafka的流式处理类库为开发人员提供了一种强大的工具来处理实时数据流,并从中提取有用的信息,是构建复杂的流式处理系统的理想选择。
什么是流式处理
流式处理是一种编程范式,用于实时处理一个或多个事件流。事件流是无边界数据集的抽象表示,它们是无限和持续增长的,随着时间的推移,新的记录会不断加入进来。
与批处理不同,流式处理可以对事件流进行实时处理,而不需要等待所有数据都可用之后再进行处理。这使得流式处理非常适用于需要实时响应的业务场景,如可疑交易警报、网络警报、实时价格调整和包裹跟踪等。
流式处理具有以下几个特征:
-
有序:事件流中的数据记录是按照它们发生的时间顺序排列的。这意味着流式处理可以按照事件发生的顺序进行处理,从而得出正确的结果。
-
不可变:事件流中的数据记录是不可变的,即一旦记录被创建,它就不能被修改。这使得流式处理更容易实现,因为它不需要考虑并发修改问题。
-
可重播:事件流中的数据记录可以被重复处理,从而使得流式处理具有容错性。如果处理过程中发生了错误,可以重新处理相同的数据记录,直到得到正确的结果。
-
低延迟:流式处理具有较低的延迟,即处理事件流的时间很短,通常在毫秒或微秒级别。这使得流式处理非常适用于需要实时响应的业务场景。
-
高吞吐量:流式处理具有较高的吞吐量,即能够处理大量的数据记录。这使得流式处理非常适用于处理大规模的数据集。
-
不依赖于具体框架或API:流的定义不依赖于任何特定的框架、API或特性,只要从一个无边界的数据集中读取数据并进行处理,就可以进行流式处理。这使得流式处理具有较大的灵活性和可扩展性。
流式处理是一种能够实时处理无边界数据集的编程范式,具有有序、不可变、可重播、低延迟、高吞吐量和灵活性等特点,非常适用于需要实时响应的业务场景。
流式处理的一些概念
时间
时间或许就是流式处理最为重要的概念,也是最让人感到困惑的。在讨论分布式系统时,该如何理解复杂的时间概念? 在流式处理里,时间是一个非常重要的概念,因为大部分流式应用的操作都是基于时间窗口的。事
- 事件时间(Event Time):事件实际发生的时间。这是最重要的时间概念,大部分流式应用都是基于事件时间来进行窗口操作和聚合的。
- 日志追加时间(Log Append Time):事件被写入Kafka的时间。这种时间主要是Kafka内部使用的,和流式应用无太大关系。
- 处理时间(Processing Time):应用程序收到事件并开始处理的时间。这种时间不可靠,可能会产生不同的值,所以流式应用很少使用它。
推荐读者阅读 Justin Sheehy 的论文《There is No Now》来深入理解这些时间概念,特别是在分布式系统环境下的复杂性。
在流式系统中,如果生产者出现网络问题导致离线几个小时,然后大量数据涌入,这会给系统带来很大困难。因为大部分数据的事件时间已经超出我们设定的窗口范围,无法进行正常的聚合计算。
为了解决这个问题,流式系统提供了几种机制:
- 丢弃超出窗口的数据:简单但会导致数据损失
- 调整窗口:扩大窗口以包含更多数据,但窗口范围变大会影响计算精度
- 重发数据:生产者将离线期间的数据重新发送,系统会进行补充计算以产生正确的结果
- 水印(Watermark):允许指定数据迟到的最大时间,系统会等待水印时间之内的数据到达后开始计算并输出结果。水印机制可以有效解决数据迟到的问题 while 保证结果的准确性。
所以,在设计流式应用时需要考虑这些时间概念,特别要考虑数据迟到和离线的情况,并选择合适的机制来处理,保证系统的准确性。
状态
- 单纯处理单个事件很简单,但涉及多个事件时需要跟踪更多信息,这些信息被称为“状态”。
- 状态通常存储在应用程序的本地变量中,如散列表。但本地状态存在丢失风险,重启后状态变化,需持久化最近状态并恢复。
- 本地状态或内部状态:只能被单个应用程序实例访问,使用内嵌数据库维护,速度快但受限于内存大小。许多设计将数据拆分到子流使用本地状态处理。
- 外部状态:使用外部数据存储维护,如NoSQL系统Cassandra。大小无限制,多个应用实例可访问,但增加延迟和复杂度。大部分流式处理应用避免外部存储,或缓存在本地减少交互以降低延迟,引入内外状态一致性问题
流和表的二元性
- 表是记录的集合,具有主键和schema定义的属性,记录可变,查询可得某时刻状态,如CUSTOMERS_CONTACTS表获取所有客户联系信息。但表无历史信息。
- 流是事件序列,每个事件是变更。表是多变更结果的当前状态。表和流是同一硬币两面:世界变化,关注变更事件或当前状态。支持两种方式的系统更强大。
- 将表转为流需捕获表变更事件(insert、update、delete),如CDC解决方案发送变更到Kafka流式处理。
- 将流转为表需应用流所有变更以改变状态,在内存、内部状态存储或外部数据库创建表,遍历流所有事件逐个改变状态,得到某时间点状态的表。
假设有一个鞋店,某零售活动可以使用一个事件流来表示:
“红色、蓝色和绿色鞋子到货”
“蓝色鞋子卖出”
“红色鞋子卖出”
“蓝色鞋子退货”
“绿色鞋子卖出”
如果想知道现在仓库里还有哪些库存,或者到目前为止赚了多少钱,需要对视图进行物化。
应用流中所有变更事件来改变状态并建立表,表转流需要捕获表上的变更事件并发送到流进行后续流式处理。表代表某时刻的状态,流代表变更,二者相互转化,支持两种方式的系统更强大
时间窗口
针对流的时间窗口操作主要有以下几种类型:
- 窗口大小:5分钟、15分钟、1天等,大小影响变更检测速度和平滑度。窗口越小,变更检测越快但噪声也越大;窗口越大,变更越平滑但延迟也越严重。
- 窗口移动频率(“移动间隔”):5分钟平均值每分钟变化一次或每秒变化一次或每新事件变化一次。移动间隔等于窗口大小为“滚动窗口”,随每记录移动为“滑动窗口”。
- 窗口可更新时间:计算00:00-00:05平均值,1小时后00:02事件,是否更新00:00-00:05窗口结果?可定义时间段内事件添加对应时间片段,如4小时内更新,否则忽略。
- 窗口与时间对齐或不对齐:5分钟窗口每分钟移动,第一个片00:00-00:05,第二个00:01-00:06;或应用任时启动,第一个片03:17-03:22。滑动窗口随新记录移动,永不与时间对齐。
窗口大小影响操作结果的灵敏度和平滑度,移动间隔决定结果更新频率,可更新时间决定迟到事件是否参与运算。窗口可与时间对齐或不对齐。
滑动窗口随每新事件移动,滚动窗口按预定间隔移动,但两者移动间隔都不超过窗口大小。滚动窗口移动间隔与窗口大小相等时,相邻窗口没有重叠;滑动窗口移动间隔小于窗口大小时,相邻窗口有重叠。
【滚动窗口和跳跃窗口的区别】
流式处理的设计模式
单个事件处理
处理单个事件是流式处理最基本的模式。这个模式也叫 map 或 filter 模式,因为它经常被用于过滤无用的事件或者用于转换事件
map 这个术语是从 Map-Reduce 模式中来的,map阶段转换事件,reduce 阶段聚合转换过的事件)。
读取流事件,修改并写到其他流。如读取日志流,ERROR级别消息写高优先级流,其他写低优先级流;或JSON转Avro格式。无需维护状态,易恢复错误或负载均衡。
【单事件处理拓扑】
这种模式可以使用一个生产者和一个消费者来实现.
使用本地状态
多数流处理应用聚合信息,如每天最高最低股票价和移动平均值。需维护流状态,如保存最小最大值和新值比较。可通过本地状态实现,每操作一组聚合,如下图。Kafka分区确保同代码事件同分区。每个应用实例获取分配分区事件,维护一组股票代码状态。
多阶段处理和重分区
本地状态适组内聚合,要全信息结果如每日前10股票需两阶段:第一阶段每个实例计算每股涨跌,写单分区新主题;第二阶段单应用实例读取新主题找前10股。新主题只股票摘要,流量小,单实例足以。更多步骤亦如MapReduce多reduce步骤,每个步骤应用隔离。流处理框架可多步骤一应用,框架调度每个步骤哪个应用实例运行。
【包含本地状态和重分区步骤的拓扑】
使用外部查找——流和表的连接
【使用外部数据源的流式处理】
外部查找会带来严重的延迟
为了获得更好的性能和更强的伸缩性,需要将数据库的信息缓存到流式处理应用程序里。不过,要管理好这个缓存也是一个挑战。
比如,如何保证缓存里的数据是最新的?如果刷新太频繁,那么仍然会对数据库造成压力,缓存也就失去了作用。如果刷新不及时,那么流式处理中所用的数据就会过时。
如果能够捕捉数据库的变更事件,并形成事件流,流式处理作业就可以监听事件流,并及时更新缓存。捕捉数据库的变更事件并形成事件流,这个过程被称为 CDC——变更数据捕捉(Change Data Capture)。如果使用了 Connect,就会发现,有一些连接器可以用于执行CDC 任务,把数据库表转成变更事件流。
这样就拥有了数据库表的私有副本,一旦数据库发生变更,用户会收到通知,并根据变更事件更新私有副本里的数据,如图
【连接流和表的拓扑,不需要外部数据源】
流与流的连接
在 Streams 中,上述的两个流都是通过相同的键来进行分区的,这个键也是用于连接两个流的键。这样一来,user_id:42 的点击事件就被保存在点击主题的分区 5 上,而所有 user_id:42 的搜索事件被保存在搜索主题的分区 5 上。Streams 可以确保这两个主题的分区 5 的事件被分配给同一个任务,这个任务就会得到所有与 user_id:42 相关的事件。
Streams 在内嵌的 RocksDB 里维护了两个主题的连接时间窗口,所以能够执行连接操作
乱序的事件
处理乱序和迟到事件的要点:
- 识别乱序事件:检查事件时间,与当前时间比较,超出时间窗口视为乱序或迟到。
- 规定时间窗口重排乱序事件:如3小时内事件重排,3周外事件丢弃。
- 重排时间窗口内乱序事件的能力:流处理与批处理不同,无“重新运行昨日作业”概念,须同时处理乱序与新事件。
- 更新结果的能力:如结果在数据库,用put或update更新;如邮件发送结果,更新方式需巧妙。
- 支持时间独立事件的框架:如Dataflow和Streams维护多个聚合时间窗口,更新事件,且可配置窗口大小。窗口越大,本地状态内存需求越高。
- Streams API聚合结果写入主题,常为压缩日志主题,每个键只保留最新值。如果聚合窗口结果需更新,直接为窗口写入新结果,覆盖前结果。
处理乱序和迟到事件需要:
- 识别时间窗口外的事件,丢弃或特殊处理
- 为时间窗口内的乱序事件定义重排窗口,在该窗口内重排乱序事件
- 具有在定义的时间窗口内重排乱序事件并更新结果的能力
- 选择支持时间独立事件和本地状态管理的流框架,如Dataflow或Streams
- 将更新后的聚合结果直接 overwrite,使用压缩日志主题避免结果主题无限增长
事件的乱序和迟到是流处理的常见场景,但又不太适合批处理的重新计算方式。定义多个时间窗口以管理历史状态,重排时间窗口内乱序事件,直接覆盖更新结果可以有效解决此类问题。
Streams提供的本地状态管理、时间窗口支持和压缩日志主题写入使其可以高效处理乱序和迟到事件。通过配置不同时间窗口,开发人员可以实现不同粒度的状态管理和事件重排。
事件乱序和迟到带来的挑战在于历史状态的管理和结果的更新,Streams等流框架的出现使开发人员无需过于关注这些底层问题,可以专注于流处理应用的业务逻辑。
重新处理
重处理事件的两种模式:
- 改进流处理应用,新版本应用处理同事件流,生成新结果,比较两版本结果,时间点切换客户端新结果流。
- 现有应用有缺陷,修复后重处理事件流重新计算结果。
第一种模式实现:
- 新版本应用作为新消费者群组
- 从输入主题第一个偏移量开始读取事件,获得自己输入流事件副本
- 检查结果流,新版本应用赶上进度,切换客户端应用新结果流
第二种模式挑战: - 重置应用到输入流起点重新处理,重置本地状态,避免混淆两版本结果
- 可能需清理前输出流
- 尽管Streams提供重置应用状态工具,有条件运行两个应用生成两个结果流更安全,可以比较不同版本结果,无数据丢失或清理引入错误风险
重处理事件模式需要:
- 事件流长期在可扩展数据存储,如Kafka
- 运行不同版本应用作为不同消费者群组,各自处理事件流并生成结果
- 新版本应用从头读取事件,建立自己的输入流副本和结果,避免影响当前版本
- 比较不同版本结果,确定切换时机,小心切换客户端到新结果流
- 可选清理现有结果和状态,使用重置工具小心操作,或采用并行模式避免清理
事件流的长期保留为重新处理事件和 AB 测试不同版本应用程序提供了可能。重置当前运行的应用程序存在一定风险,并行运行多个版本的应用程序可以最大限度减小风险。
无论采用何种模式,重新处理事件都需要小心谨慎的计划与执行。不同版本应用程序生成的结果流比较可以让我们清楚地知道新的版本是否达到了预期的改进,这为重新处理事件和发布提供了依据。
Streams 的消费者群组管理和工具支持使其在重新处理事件和 AB 测试场景下性能卓越。通过将不同版本应用加入不同消费者群组,各自处理事件流并生成独立结果,再小心migrate客户端,这是一种较为安全可靠的重新处理事件模式。
事件流的长期保留和可靠的状态管理是重新处理事件的基石。AB 测试不同版本应用程序也可借此机制实现,这为流式应用的持续优化和演化提供了可能。