近些年,企业对数据服务实时化服务的需求日益增多。本文整理了常见实时数据组件的性能特色和适用场景,介绍了美团如何经过 Flink 引擎构建实时数据仓库,从而提供高效、稳健的实时数据服务。此前咱们美团技术博客发布过一篇文章《流计算框架 Flink 与 Storm 的性能对比》,对 Flink 和 Storm 俩个引擎的计算性能进行了比较。本文主要阐述使用 Flink 在实际数据生产上的经验。html
在实时数据系统建设初期,因为对实时数据的需求较少,造成不了完整的数据体系。咱们采用的是“一路到底”的开发模式:经过在实时计算平台上部署 Storm 做业处理实时数据队列来提取数据指标,直接推送到实时应用服务中。算法
图1 初期实时数据架构缓存
可是,随着产品和业务人员对实时数据需求的不断增多,新的挑战也随之发生。性能优化
为解决以上问题,咱们根据生产离线数据的经验,选择使用分层设计方案来建设实时数据仓库,其分层架构以下图所示:数据结构
图2 实时数仓数据分层架构架构
该方案由如下四层构成:框架
经过多层设计咱们能够将处理数据的流程沉淀在各层完成。好比在数据明细层统一完成数据的过滤、清洗、规范、脱敏流程;在数据汇总层加工共性的多维指标汇总数据。提升了代码的复用率和总体生产效率。同时各层级处理的任务类型类似,能够采用统一的技术方案优化性能,使数仓技术架构更简洁。异步
实时数仓在设计中不一样于离线数仓在各层级使用同种储存方案,好比都存储在 Hive 、DB 中的策略。首先对中间过程的表,采用将结构化的数据经过消息队列存储和高速 KV 存储混合的方案。实时计算引擎能够经过监听消息消费消息队列内的数据,进行实时计算。而在高速 KV 存储上的数据则能够用于快速关联计算,好比维度数据。 其次在应用层上,针对数据使用特色配置存储方案直接写入。避免了离线数仓应用层同步数据流程带来的处理延迟。 为了解决不一样类型的实时数据需求,合理的设计各层级存储方案,咱们调研了美团内部使用比较普遍的几种存储方案。分布式
存储方案列表以下:ide
方案 | 优点 | 劣势 |
---|---|---|
MySQL | 1. 具备完备的事务功能,能够对数据进行更新。2. 支持 SQL,开发成本低。 | 1. 横向扩展成本大,存储容易成为瓶颈; 2. 实时数据的更新和查询频率都很高,线上单个实时应用请求就有 1000+ QPS;使用 MySQL 成本过高。 |
Elasticsearch | 1. 吞吐量大,单个机器能够支持 2500+ QPS,而且集群能够快速横向扩展。2. Term 查询时响应速度很快,单个机器在 2000+ QPS时,查询延迟在 20 ms之内。 | 1. 没有原生的 SQL 支持,查询 DSL 有必定的学习门槛;2. 进行聚合运算时性能降低明显。 |
Druid | 1. 支持超大数据量,经过 Kafka 获取实时数据时,单个做业可支持 6W+ QPS;2. 能够在数据导入时经过预计算对数据进行汇总,减小的数据存储。提升了实际处理数据的效率;3. 有不少开源 OLAP 分析框架。实现如 Superset。 | 1. 预聚合致使没法支持明细的查询;2. 没法支持 Join 操做;3. Append-only 不支持数据的修改。只能以 Segment 为单位进行替换。 |
Cellar | 1. 支持超大数据量,采用内存加分布式存储的架构,存储性价比很高;2. 吞吐性能好,经测试处理 3W+ QPS 读写请求时,平均延迟在 1ms左右;经过异步读写线上最高支持 10W+ QPS。 | 1. 接口仅支持 KV,Map,List 以及原子加减等;2. 单个 Key 值不得超过 1KB ,而 Value 的值超过 100KB 时则性能降低明显。 |
根据不一样业务场景,实时数仓各个模型层次使用的存储方案大体以下:
图3 实时数仓存储分层架构
在实时平台建设初期咱们使用 Storm 引擎来进行实时数据处理。Storm 引擎虽然在灵活性和性能上都表现不错。可是因为 API 过于底层,在数据开发过程当中须要对一些经常使用的数据操做进行功能实现。好比表关联、聚合等,产生了不少额外的开发工做,不只引入了不少外部依赖好比缓存,并且实际使用时性能也不是很理想。同时 Storm 内的数据对象 Tuple 支持的功能也很简单,一般须要将其转换为 Java 对象来处理。对于这种基于代码定义的数据模型,一般咱们只能经过文档来进行维护。不只须要额外的维护工做,同时在增改字段时也很麻烦。综合来看使用 Storm 引擎构建实时数仓难度较大。咱们须要一个新的实时处理方案,要可以实现:
咱们对主要的实时计算引擎进行了技术调研。总结了各种引擎特性以下表所示:
实时计算方案列表以下:
项目/引擎 | Storm | Flink | spark-treaming |
---|---|---|---|
API | 灵活的底层 API 和具备事务保证的 Trident API | 流 API 和更加适合数据开发的 Table API 和 Flink SQL 支持 | 流 API 和 Structured-Streaming API 同时也可使用更适合数据开发的 Spark SQL |
容错机制 | ACK 机制 | State 分布式快照保存点 | RDD 保存点 |
状态管理 | Trident State状态管理 | Key State 和 Operator State两种 State 可使用,支持多种持久化方案 | 有 UpdateStateByKey 等 API 进行带状态的变动,支持多种持久化方案 |
处理模式 | 单条流式处理 | 单条流式处理 | Mic batch处理 |
延迟 | 毫秒级 | 毫秒级 | 秒级 |
语义保障 | At Least Once,Exactly Once | Exactly Once,At Least Once | At Least Once |
从调研结果来看,Flink 和 Spark Streaming 的 API 、容错机制与状态持久化机制均可以解决一部分咱们目前使用 Storm 中遇到的问题。但 Flink 在数据延迟上和 Storm 更接近,对现有应用影响最小。并且在公司内部的测试中 Flink 的吞吐性能对比 Storm 有十倍左右提高。综合考量咱们选定 Flink 引擎做为实时数仓的开发引擎。
更加引发咱们注意的是,Flink 的 Table 抽象和 SQL 支持。虽然使用 Strom 引擎也能够处理结构化数据。但毕竟依旧是基于消息的处理 API ,在代码层层面上不能彻底享受操做结构化数据的便利。而 Flink 不只支持了大量经常使用的 SQL 语句,基本覆盖了咱们的开发场景。并且 Flink 的 Table 能够经过 TableSchema 进行管理,支持丰富的数据类型和数据结构以及数据源。能够很容易的和现有的元数据管理系统或配置管理系统结合。经过下图咱们能够清晰的看出 Storm 和 Flink 在开发统过程当中的区别。
图4 Flink - Storm 对比图
在使用 Storm 开发时处理逻辑与实现须要固化在 Bolt 的代码。Flink 则能够经过 SQL 进行开发,代码可读性更高,逻辑的实现由开源框架来保证可靠高效,对特定场景的优化只要修改 Flink SQL 优化器功能实现便可,而不影响逻辑代码。使咱们能够把更多的精力放到到数据开发中,而不是逻辑的实现。当须要离线数据和实时数据口径统一的场景时,咱们只需对离线口径的 SQL 脚本稍加改造便可,极大地提升了开发效率。同时对比图中 Flink 和 Storm 使用的数据模型,Storm 须要经过一个 Java 的 Class 去定义数据结构,Flink Table 则能够经过元数据来定义。能够很好的和数据开发中的元数据,数据治理等系统结合,提升开发效率。
在利用 Flink-Table 构建实时数据仓库过程当中。咱们针对一些构建数据仓库的经常使用操做,好比数据指标的维度扩充,数据按主题关联,以及数据的聚合运算经过 Flink 来实现总结了一些使用心得。
数据指标的维度扩充,咱们采用的是经过维度服务获取维度信息。虽然基于 Cellar 的维度服务一般的响应延迟能够在 1ms 如下。可是为了进一步优化 Flink 的吞吐,咱们对维度数据的关联所有采用了异步接口访问的方式,避免了使用 RPC 调用影响数据吞吐。 对于一些数据量很大的流,好比流量日志数据量在 10W 条/秒这个量级。在关联 UDF 的时候内置了缓存机制,能够根据命中率和时间对缓存进行淘汰,配合用关联的 Key 值进行分区,显著减小了对外部服务的请求次数,有效的减小了处理延迟和对外部系统的压力。
数据主题合并,本质上就是多个数据源的关联,简单的来讲就是 Join 操做。Flink 的 Table 是创建在无限流这个概念上的。在进行 Join 操做时并不能像离线数据同样对两个完整的表进行关联。采用的是在窗口时间内对数据进行关联的方案,至关于从两个数据流中各自截取一段时间的数据进行 Join 操做。有点相似于离线数据经过限制分区来进行关联。同时须要注意 Flink 关联表时必须有至少一个“等于”关联条件,由于等号两边的值会用来分组。
因为 Flink 会缓存窗口内的所有数据来进行关联,缓存的数据量和关联的窗口大小成正比。所以 Flink 的关联查询,更适合处理一些能够经过业务规则限制关联数据时间范围的场景。好比关联下单用户购买以前 30 分钟内的浏览日志。过大的窗口不只会消耗更多的内存,同时会产生更大的 Checkpoint ,致使吞吐降低或 Checkpoint 超时。在实际生产中可使用 RocksDB 和启用增量保存点模式,减小 Checkpoint 过程对吞吐产生影响。对于一些须要关联窗口期很长的场景,好比关联的数据多是几天之前的数据。对于这些历史数据,咱们能够将其理解为是一种已经固定不变的”维度”。能够将须要被关联的历史数据采用和维度数据一致的处理方法:”缓存 + 离线”数据方式存储,用接口的方式进行关联。另外须要注意 Flink 对多表关联是直接顺序连接的,所以须要注意先进行结果集小的关联。
使用聚合运算时,Flink 对常见的聚合运算如求和、极值、均值等都有支持。美中不足的是对于 Distinct 的支持,Flink-1.6 以前的采用的方案是经过先对去重字段进行分组再聚合实现。对于须要对多个字段去重聚合的场景,只能分别计算再进行关联处理效率很低。为此咱们开发了自定义的 UDAF,实现了 MapView 精确去重、BloomFilter 非精确去重、 HyperLogLog 超低内存去重方案应对各类实时去重场景。可是在使用自定义的 UDAF 时,须要注意 RocksDBStateBackend 模式对于较大的 Key 进行更新操做时序列化和反序列化耗时不少。能够考虑使用 FsStateBackend 模式替代。另外要注意的一点 Flink 框架在计算好比 Rank 这样的分析函数时,须要缓存每一个分组窗口下的所有数据才能进行排序,会消耗大量内存。建议在这种场景下优先转换为 TopN 的逻辑,看是否能够解决需求。
下图展现一个完整的使用 Flink 引擎生产一张实时数据表的过程:
图5 实时计算流程图
经过使用实时数仓代替原有流程,咱们将数据生产中的各个流程抽象到实时数仓的各层当中。实现了所有实时数据应用的数据源统一,保证了应用数据指标、维度的口径的一致。在几回数据口径发生修改的场景中,咱们经过对仓库明细和汇总进行改造,在彻底不用修改应用代码的状况下就完成所有应用的口径切换。在开发过程当中经过严格的把控数据分层、主题域划分、内容组织标准规范和命名规则。使数据开发的链路更为清晰,减小了代码的耦合。再配合上使用 Flink SQL 进行开发,代码加简洁。单个做业的代码量从平均 300+ 行的 JAVA 代码 ,缩减到几十行的 SQL 脚本。项目的开发时长也大幅减短,一人日开发多个实时数据指标状况也很多见。
除此之外咱们经过针对数仓各层级工做内容的不一样特色,能够进行针对性的性能优化和参数配置。好比 ODS 层主要进行数据的解析、过滤等操做,不须要 RPC 调用和聚合运算。 咱们针对数据解析过程进行优化,减小没必要要的 JSON 字段解析,并使用更高效的 JSON 包。在资源分配上,单个 CPU 只配置 1GB 的内存便可满需求。而汇总层主要则主要进行聚合与关联运算,能够经过优化聚合算法、内外存共同运算来提升性能、减小成本。资源配置上也会分配更多的内存,避免内存溢出。经过这些优化手段,虽然相比原有流程实时数仓的生产链路更长,但数据延迟并无明显增长。同时实时数据应用所使用的计算资源也有明显减小。
咱们的目标是将实时仓库建设成能够和离线仓库数据准确性,一致性媲美的数据系统。为商家,业务人员以及美团用户提供及时可靠的数据服务。同时做为到餐实时数据的统一出口,为集团其余业务部门助力。将来咱们将更加关注在数据可靠性和实时数据指标管理。创建完善的数据监控,数据血缘检测,交叉检查机制。及时对异常数据或数据延迟进行监控和预警。同时优化开发流程,下降开发实时数据学习成本。让更多有实时数据需求的人,能够本身动手解决问题。