作者:夏军@小米
小米业务线众多,从信息流,电商,广告到金融等覆盖了众多领域,小米流式平台为小米集团各业务提供一体化的流式数据解决方案,主要包括数据采集,数据集成和流式计算三个模块。目前每天数据量达到 1.2 万亿条,实时同步任务 1.5 万,实时计算的数据 1 万亿条。
伴随着小米业务的发展,流式平台也经历三次大升级改造,满足了众多业务的各种需求。最新的一次迭代基于 Apache Flink,对于流式平台内部模块进行了彻底的重构,同时小米各业务也在由 Spark Streaming 逐步切换到 Flink。
背景介绍
小米流式平台的愿景是为小米所有的业务线提供流式数据的一体化、平台化解决方案。具体来讲包括以下三个方面:
- 流式数据存储:流式数据存储指的是消息队列,小米开发了一套自己的消息队列,其类似于 Apache kafka,但它有自己的特点,小米流式平台提供消息队列的存储功能;
- 流式数据接入和转储:有了消息队列来做流式数据的缓存区之后,继而需要提供流式数据接入和转储的功能;
- 流式数据处理:指的是平台基于 Flink、Spark Streaming 和 Storm 等计算引擎对流式数据进行处理的过程。
下图展示了流式平台的整体架构。从左到右第一列橙色部分是数据源,包含两部分,即 User 和 Database。
- User 指的是用户各种各样的埋点数据,如用户 APP 和 WebServer 的日志,其次是 Database 数据,如 MySQL、HBase 和其他的 RDS 数据。
- 中间蓝色部分是流式平台的具体内容,其中 Talos 是小米实现的消息队列,其上层包含 Consumer SDK 和 Producer SDK。
- 此外小米还实现了一套完整的 Talos Source,主要用于收集刚才提到的用户和数据库的全场景的数据。
Talos Sink 和 Source 共同组合成一个数据流服务,主要负责将 Talos 的数据以极低的延迟转储到其他系统中;Sink 是一套标准化的服务,但其不够定制化,后续会基于 Flink SQL 重构 Talos Sink 模块。
下图展示了小米的业务规模。在存储层面小米每天大概有 1.2 万亿条消息,峰值流量可以达到 4300 万条每秒。转储模块仅 Talos Sink 每天转储的数据量就高达 1.6 PB,转储作业目前将近有 1.5 万个。每天的流式计算作业超过 800 个,Flink 作业超过 200 个,Flink 每天处理的消息量可以达到 7000 亿条,数据量在 1 PB 以上。
小米流式平台发展历史
小米流式平台发展历史分为如下三个阶段:
- Streaming Platform 1.0:小米流式平台的 1.0 版本构建于 2010 年,其最初使用的是 Scribe、Kafka 和 Storm,其中 Scribe 是一套解决数据收集和数据转储的服务。
- Streaming Platform 2.0:由于 1.0 版本存在的种种问题,我们自研了小米自己的消息队列 Talos,还包括 Talos Source、Talos Sink,并接入了 Spark Streaming。
- Streaming Platform 3.0:该版本在上一个版本的基础上增加了 Schema 的支持,还引入了 Flink 和 Stream SQL。
Streaming Platform 1.0 整体是一个级联的服务,前面包括 Scribe Agent 和 Scribe Server 的多级级联,主要用于收集数据,然后满足离线计算和实时计算的场景。离线计算使用的是 HDFS 和 Hive,实时计算使用的是 Kafka 和 Storm。虽然这种离线加实时的方式可以基本满足小米当时的业务需求,但也存在一系列的问题。
- 首先是 Scribe Agent 过多,而配置和包管理机制缺乏,导致维护成本非常高;
- Scribe 采用的 Push 架构,异常情况下无法有效缓存数据,同时 HDFS / Kafka 数据相互影响;
- 最后数据链级联比较长的时候,整个全链路数据黑盒,缺乏监控和数据检验机制。
为了解决 Streaming Platform 1.0 的问题,小米推出了 Streaming Platform 2.0 版本。该版本引入了 Talos,将其作为数据缓存区来进行流式数据的存储,左侧是多种多样的数据源,右侧是多种多样的 Sink,即将原本的级联架构转换成星型架构,优点是方便地扩展。
- 由于 Agent 自身数量及管理的流较多(具体数据均在万级别),为此该版本实现了一套配置管理和包管理系统,可以支持 Agent 一次配置之后的自动更新和重启等。
- 此外,小米还实现了去中心化的配置服务,配置文件设定好后可以自动地分发到分布式结点上去。
- 最后,该版本还实现了数据的端到端监控,通过埋点来监控数据在整个链路上的数据丢失情况和数据传输延迟情况等。
Streaming Platform 2.0 的优势主要有:
- 引入了 Multi Source & Multi Sink,之前两个系统之间导数据需要直接连接,现在的架构将系统集成复杂度由原来的 O(M*N) 降低为 O(M+N);
- 引入配置管理和包管理机制,彻底解决系统升级、修改和上线等一系列问题,降低运维的压力;
- 引入端到端数据监控机制,实现全链路数据监控,量化全链路数据质量;
- 产品化解决方案,避免重复建设,解决业务运维问题。
下图详细介绍一下 MySQL 同步的案例,场景是将 MySQL 的一个表通过上述的机制同步到消息队列 Talos。具体流程是 Binlog 服务伪装成 MySQL 的 Slave,向 MySQL 发送 Dump binlog 请求;MySQL 收到 Dump 请求后,开始推动 Binlog 给 Binlog 服务;Binlog 服务将 binlog 以严格有序的形式转储到 Talos。之后会接入 Spark Streaming 作业,对 binlog 进行解析,解析结果写入到 Kudu 表中。目前平台支持写入到 Kudu 中的表的数量级超过 3000 个。
Agent Source 的功能模块如下图所示。其支持 RPC、Http 协议,并可以通过 File 来监听本地文件,实现内存和文件双缓存,保证数据的高可靠。平台基于 RPC 协议实现了 Logger Appender 和 RPC 协议的 SDK;对于 Http 协议实现了 HttpClient;对于文件实现了 File Watcher 来对本地文件进行自动地发现和扫描,Offset Manager 自动记录 offset;Agent 机制与 K8S 环境深度整合,可以很容易地和后端的流式计算等相结合。
下图是 Talos Sink 的逻辑流程图,其基于 Spark Streaming 来实现一系列流程。最左侧是一系列 Talos Topic 的 Partition 分片,基于每个 batch 抽象公共逻辑,如 startProcessBatch() 和 stopProcessBatch(),不同 Sink 只需要实现 Write 逻辑;不同的 Sink 独立为不同的作业,避免相互影响;Sink 在 Spark Streaming 基础上进行了优化,实现了根据 Topic 流量进行动态资源调度,保证系统延迟的前提下最大限度节省资源。
下图是平台实现的端到端数据监控机制。具体实现是为每个消息都有一个时间戳 EventTime,表示这个消息真正生成的时间,根据 EventTime 来划分时间窗口,窗口大小为一分钟,数据传输的每一跳统计当前时间窗口内接受到的消息数量,最后统计出消息的完整度。延迟是计算某一跳 ProcessTime 和 EventTime 之间的差值。
Streaming Platform 2.0 目前的问题主要有三点:
- Talos 数据缺乏 Schema 管理,Talos 对于传入的数据是不理解的,这种情况下无法使用 SQL 来消费 Talos 的数据;
- Talos Sink 模块不支持定制化需求,例如从 Talos 将数据传输到 Kudu 中,Talos 中有十个字段,但 Kudu 中只需要 5 个字段,该功能目前无法很好地支持;
- Spark Streaming 自身问题,不支持 Event Time,端到端 Exactly Once 语义。
基于 Flink 的实时数仓
为了解决 Streaming Platform 2.0 的上述问题,小米进行了大量调研,也和阿里的实时计算团队做了一系列沟通和交流,最终决定将使用 Flink 来改造平台当前的流程,下面具体介绍小米流式计算平台基于Flink的实践。
使用 Flink 对平台进行改造的设计理念如下:
- 全链路 Schema 支持,这里的全链路不仅包含 Talos 到 Flink 的阶段,而是从最开始的数据收集阶段一直到后端的计算处理。需要实现数据校验机制,避免数据污染;字段变更和兼容性检查机制,在大数据场景下,Schema 变更频繁,兼容性检查很有必要,借鉴 Kafka 的经验,在 Schema 引入向前、向后或全兼容检查机制。
- 借助 Flink 社区的力量全面推进 Flink 在小米的落地,一方面 Streaming 实时计算的作业逐渐从 Spark、Storm 迁移到 Flink,保证原本的延迟和资源节省,目前小米已经运行了超过 200 个 Flink 作业;另一方面期望用 Flink 改造 Sink 的流程,提升运行效率的同时,支持 ETL,在此基础上大力推进 Streaming SQL;
- 实现 Streaming 产品化,引入 Streaming Job 和 Streaming SQL 的平台化管理;
- 基于 Flink SQL 改造 Talos Sink,支持业务逻辑定制化
下图是 Streaming Platform 3.0 版本的架构图,与 2.0 版本的架构设计类似,只是表达的角度不同。具体包含以下几个模块:
- 抽象 Table:该版本中各种存储系统如 MySQL 和 Hive 等都会抽象成 Table,为 SQL 化做准备。
- Job 管理:提供 Streaming 作业的管理支持,包括多版本支持、配置与Jar分离、编译部署和作业状态管理等常见的功能。
- SQL 管理:SQL 最终要转换为一个 Data Stream 作业,该部分功能主要有 Web IDE 支持、Schema 探查、UDF/维表 Join、SQL 编译、自动构建 DDL 和 SQL 存储等。
- Talos Sink:该模块基于 SQL 管理对 2.0 版本的 Sink 重构,包含的功能主要有一键建表、Sink 格式自动更新、字段映射、作业合并、简单 SQL 和配置管理等。前面提到的场景中,基于 Spark Streaming 将 Message 从 Talos 读取出来,并原封不动地转到 HDFS 中做离线数仓的分析,此时可以直接用 SQL 表达很方便地实现。未来希望实现该模块与小米内部的其他系统如 ElasticSearch 和 Kudu 等进行深度整合,具体的场景是假设已有 Talos Schema,基于 Talos Topic Schema 自动帮助用户创建 Kudu 表。
- 平台化:为用户提供一体化、平台化的解决方案,包括调试开发、监控报警和运维等。
Job 管理
Job 管理提供 Job 全生命周期管理、Job 权限管理和 Job 标签管理等功能;支持Job 运行历史展示,方便用户追溯;支持 Job 状态与延迟监控,可以实现失败作业自动拉起。
SQL 管理
主要包括以下四个环节:
- 将外部表转换为 SQL DDL,对应 Flink 1.9 中标准的 DDL 语句,主要包含 Table Schema、Table Format 和 Connector Properities。
- 基于完整定义的外部 SQL 表,增加 SQL 语句,既可以得到完成的表达用户的需求。即 SQL Config 表示完整的用户预计表达,由 Source Table DDL、Sink Table DDL 和 SQL DML语句组成。
- 将 SQL Config 转换成 Job Config,即转换为 Stream Job 的表现形式。
- 将 Job Config 转换为 JobGraph,用于提交 Flink Job。
外部表转换成 SQL DDL 的流程如下图所示。
- 首先根据外部表获取 Table Schema 和 Table Format 信息,后者用于反解数据,如对于 Hive 数据反序列化;
- 然后再后端生成默认的 Connector 配置,该配置主要分为三部分,即不可修改的、带默认值的用户可修改的、不带默认值的用户必须配置的。
不可修改的配置情况是假设消费的是 Talos 组件,那么 connector.type 一定是 talos,则该配置不需要改;而默认值是从 Topic 头部开始消费,但用户可以设置从尾部开始消费,这种情况属于带默认值但是用户可修改的配置;而一些权限信息是用户必须配置的。
之所以做三层配置管理,是为了尽可能减少用户配置的复杂度。Table Schema、Table Format 和 Connector 1 其他配置信息,组成了SQL DDL。将 SQL Config 返回给用户之后,对于可修改的需要用户填写,这样便可以完成从外部表到 SQL DDL 的转换,红色字体表示的是用户修改的信息。
SQL 管理引入了一个 External Table 的特性。假设用户在平台上选择消费某个 Topic 的时候,该特性会自动地获取上面提到的 Table 的 Schema 和 Format 信息,并且显示去掉了注册 Flink Table 的逻辑;获取 Schema 时,该特性会将外部表字段类型自动转换为 Flink Table 字段类型,并自动注册为 Flink Tab 了。同时将 Connector Properties 分成三类,参数带默认值,只有必须项要求用户填写;所有参数均采用 Map 的形式表达,非常便于后续转化为 Flink 内部的 TableDescriptor。
上面介绍了 SQL DDL 的创建过程,在已经创建的 SQL DDL 的基础上,如 Source SQL DDL 和 Sink SQL DDL,要求用户填写 SQL query 并返回给后端,后端会对 SQL 进行验证,然后会生成一个 SQL Config,即一个 SQL 语句的完整表达。
SQL Config 转换为 Job Config 的流程如下图所示。
- 首先在 SQL Config 的基础上增加作业所需要的资源、Job 的相关配置(Flink 的 state 参数等);
- 然后将 SQLConfig 编译成一个 Job Descriptor,即 Job Config 的描述,如 Job 的 Jar 包地址、MainClass 和 MainArgs 等。
下图展示了 Job Config 转换为 Job Graph 的过程。对于 DDL 中的 Schema、Format 和 Property 是和 Flink 中的 Table Descriptor 是一一对应的,这种情况下只需要调用 Flink 的相关内置接口就可以很方便地将信息转换为 Table Descriptor,如 CreateTableSource()、RegistorTableSource() 等。通过上述过程,DDL 便可以注册到 Flink 系统中直接使用。对于 SQL 语句,可以直接使用 TableEnv 的 sqlUpdate() 可以完成转换。
SQL Config 转换为一个 Template Job 的流程如下所示。前面填写的 Jar 包地址即该 Template 的 Jar 地址,MainClass 是该 Template Job。假设已经有了 SQL DDL,可以直接转换成 Table Descriptor,然后通过 TableFactorUtil 的 findAndCreateTableSource() 方法得到一个 Table Source,Table Sink 的转换过程类似。完成前两步操作后,最后进行 sqlUpdate() 操作。这样便可以将一个 SQL Job 转换为最后可执行的 Job Graph 提交到集群上运行。
Talos Sink 采用了下图所示的三种模式:
- Row:Talos 的数据原封不动地灌到目标系统中,这种模式的好处是数据读取和写入的时候无需进行序列化和反序列化,效率较高;
- ID mapping:即左右两边字段进行 mapping,name 对应 field_name,timestamp 对应 timestamp,其中 Region 的字段丢掉;
- SQL:通过 SQL 表达来表示逻辑上的处理。
未来规划
小米流式平台未来的计划主要有以下几点:
- 在 Flink 落地的时候持续推进 Streaming Job 和平台化建设;
- 使用 Flink SQL 统一离线数仓和实时数仓;
- 在 Schema 的基础上数据血缘分析和展示,包括数据治理方面的内容;
- 持续参与 Flink 社区的建设。
作者介绍:
夏军,小米流式平台负责人,主要负责流式计算,消息队列,大数据集成等系统的研发工作,主要包括 Flink,Spark Streaming,Storm,Kafka 等开源系统和一系列小米自研的相关系统。