第 4 章 对时间的处理
4.1 采用批处理架构和 Lambda 架构计数
尽管看起来简单,但是大规模的计数任务在实践中出人意料地困难。当然,
计数无处不在。针对联机分析处理多维数据集的聚合或其他操作,都可以 简单地归结为计数。图4-1 展示了如何采用传统的批处理架构实现计数 任务。
在该架构中,持续摄取数据的管道每小时创建一次文件。这些文件通常被 存储在HDFS 或 MapR-FS 等分布式文件系统中。像 Apache Flume 这样的 工具可以用于完成上述工作。由调度程序安排批处理作业(如MapReduce 作业)分析最近生成的一个文件(将文件中的事件按key 分组,计算每个 key 对应的事件数),然后输出计数结果。对于每个使用 Hadoop 的公司来 说,其集群都有多个类似的管道。
36 | 第 4 章
调度程序
时间
服务和存储
文件 1
文件 2
文件 3
作业 1
作业 2
作业 3
图4-1:用定期运行的批处理作业来实现应用程序的持续性。数据被持续地分割为 文件(如以一小时为单位);然后,批处理作业将文件作为输入,以此达到持续处 理数据的效果
这种架构完全可行,但是存在以下问题。
• 太多独立的部分。为了计算数据中的事件数,这种架构动用了太多系统。
每一个系统都有学习成本和管理成本,还可能存在bug。
• 对时间的处理方法不明确。假设需要改为每 30 分钟计数一次。这个变动 涉及工作流调度逻辑(而不是应用程序代码逻辑),从而使DevOps 问题 与业务需求混淆。
• 预警。假设除了每小时计数一次外,还需要尽可能早地收到计数预警(比 如在事件数超过10 时预警)。为了做到这一点,可以在定期运行的批 处理作业之外,引入Storm 来采集消息流(Kafka 或者 MapR Streams)。
Storm 实时提供近似的计数,批处理作业每小时提供准确的计数。但是 这样一来,就向架构增加了一个系统,以及与之相关的新编程模型。上 述架构叫作Lambda 架构,如图 4-2 所示。第 1 章有过简要的介绍。
对时间的处理 | 37
调度程序
流处理作业 时间
服务和存储
文件 1
文件 2
文件 3
作业 1
作业 2
作业 3
图4-2:Lambda 架构用定期运行的批处理作业来实现应用程序的持续性,并通过流 处理器获得预警。流处理器实时提供近似结果;批处理层最终会对近似结果予以纠正
• 乱序事件流。在现实世界中,大多数事件流都是乱序的,即事件的实际 发生顺序(事件数据在生成时被附上时间戳,如智能手机记录下用户登 录应用程序的时间)和数据中心所记录的顺序不一样。这意味着本属于 前一批的事件可能被错误地归入当前一批。批处理架构很难解决这个问 题,大部分人则选择忽视它。
• 批处理作业的界限不清晰。在该架构中,“每小时”的定义含糊不清,分 割时间点实际上取决于不同系统之间的交互。充其量也只能做到大约每 小时分割一次,而在分割时间点前后的事件既可能被归入前一批,也可 能被归入当前一批。将数据流以小时为单位进行分割,实际上是最简单 的方法。假设需要根据产生数据的时间段(如从用户登录到退出)生成 聚合结果,而不是简单地以小时为单位分割数据,则用如图4-1 和图 4-2 所示的架构无法直接满足需求。
38 | 第 4 章
(Kafka 或 MapR Streams)。消息传输系统为负责处理所有数据的流处理器(在本 例中是Flink)提供流数据,产生的结果既是实时的,也是正确的
DataStream<LogEvent> stream = env // 通过Kafka生成数据流
.addSource(new FlinkKafkaConsumer(...)) // 分组
.keyBy("country") // 将时间窗口设为60分钟 .timeWindow(Time.minutes(60)) // 针对每个时间窗口进行操作
.apply(new CountPerWindowFunction());