快手基于 Apache Flink 的实时数仓建设实践

时间:2020-12-08 01:28:23

来源:Apache Flink

摘要:本文整理自快手实时数据开发工程师冯立,快手实时数据开发工程师羊艺超,在 Flink Forward Asia 2022 实时湖仓专场的分享。本篇内容主要分为四个部分:


    1. 快手实时数仓的发展
    2. 实时数仓建设方法论
    3. 实时数仓场景化实战
    4. 未来规划



01

快手实时数仓的发展


快手基于 Apache Flink 的实时数仓建设实践

作为短视频领域的领头羊,快手 APP 一直致力于视频、直播技术的迭代,其背后对数据实时性、准确性的要求非常高,这对于数仓体系的构建也提出了新的挑战。


下面是快手实时数仓发展到现在经历的几个阶段:


  • 在第一个阶段,快手的实时数仓起始于春节、国庆、快手之夜等大型活动场景。在这些活动场景下,实时数据主要用于满足活动大屏、运营看板、活动效果监控等实时需求。在这个阶段我们基于多次活动场景的实时化建设,沉淀了活动流量、用户激励等活动通用的数据。

  • 在第二个阶段,实时数据被应用于公司核心指标的实时化场景。此时实时数据主要服务于公司的核心数据产品,方便公司领导层实时了解当前公司产品的用户规模等核心指标。在这个阶段我们基于流量的通用性,建设了用户域、设备域的实时化数据。

  • 在第三个阶段,我们专注于业务数据域的实时化建设。在此阶段实时数据开始基于快手各大业务形态如直播、视频、搜索等业务数据,构建各个 FT(FeatureTeam 简称) 的实时数仓建设。此阶段实时数据主要服务于各个业务 FT 的核心实时指标,应用于各个业务 FT 的核心看板。
  • 在当前阶段,随着各大业务 FT 实时数仓建设的完善和稳定,我们开始重点扩充实时数据的使用场景。目前,实时数仓开始直接服务于线上推荐场景、产出用户画像标签、产出实时指标等推荐场景。在这个阶段我们逐步完善了各个业务 FT 的底层数据,提升了各个 FT 数仓数据的覆盖范围,更好的来满足业务对实时数据的需求。


快手基于 Apache Flink 的实时数仓建设实践

在整个实时数据的发展阶段,我们的实时数据覆盖范围越来越广,数据使用场景越来越复杂。期间在每个阶段我们也面临了很多的挑战,沉淀了在一些特定场景下的解决方案。


首先是第一阶段的大型活动和公司级核心指标的计算场景,在这个场景典型的特点是数据流量大、业务对数据指标的质量要求高,我们面临的挑战概括的讲是数据质量的保障问题。为了保障实时指标的快、稳、准;


  • 指标的实现方案上会选择缩短指标产出链路从而保证指标及时产出;采用以窗口为核心的解决方案来实现指标,从而来支持数据的可回溯。

  • 在架构上会根据指标的不同等级构建多机房容灾、双链路,来保障数据的持续可用


第二个阶段,我们专注于提升数据实时化,提升服务迭代效率。该场景的特点是产品迭代快、实时需求多。这个阶段我们面临的挑战概括的讲是开发的高效性问题。为了保证实时需求的快速稳定交付;


  • 我们在指标的实现方案上注重沉淀经典场景的解决方案,从而保证在经典场景下组内的技术方案是一致的,这样可以极大的提升开发效率。

  • 架构上我们开始沉淀各个 FT 的数仓数据,注重模型管理、数仓分层、避免烟囱开发,从而来提升数据的复用性。

   

第三个阶段,我们主要服务于线上推荐场景。这个场景下任务出现断流、重启都会直接影响到线上模型训练的准确性。这个阶段我们面临的挑战概况的讲是数据高可用性问题。为了保障实时数据的高可用;


  • 引擎层面上我们自研了支持任务 slot 级本地快速恢复,当单 slot 任务异常时,单独重启当前异常 slot 以及其对应的上下游任务,避免整个实时任务的重启,从而避免出现断流。

  • 架构上我们采用了全链路多机房容灾、任务双链路部署、主任务部署高优队列等措施,来预防物理层面导致的任务断流。


02

实时数仓建设方法论


2.1 实时数仓技术架构


快手基于 Apache Flink 的实时数仓建设实践

上图是快手实时数仓的技术架构图。从图中可以看出,整体采用 Lambda 架构,实时链路主要使用 Flink+Kafka。


在维表的实践中,我们根据维表的数据大小、访问维表任务的 QPS 等来选择用 Redis 或其它 KV 存储来作为底层存储引擎。


在数据服务层,我们会根据不同的场景,选择不同的数据存储引擎服务数据产品。比如在比较灵活的分析场景,我们会把 DWD 层数据直接导入 Clickhouse,借助 Clickhouse 的物化视图、二次开发能力、高性能 OLAP 分析能力,提供数据查询服务;在实时指标需要传给 C 端等高查询 QPS 的场景下,会选择将 Flink 计算完的指标直接导入到 Redis,用服务化接口的形式提供给数据产品;在人群特征等需要多流拼接的场景下,我们借助 Hudi 来支持多数据源合并写入,最终用离线数据服务于业务。


在离线部分,实时数据会同步导出至离线数据。该数据主要用于加速离线链路,如加速离线小时指标的产出等。与此同时,同步的数据也会被用来进行实时数据的准确性校验,支持长周期实时指标的回溯等。从而保障实时数仓的数据和离线数仓的数据一致性。


快手基于 Apache Flink 的实时数仓建设实践

上图是快手实时数仓的整体架构图。ODS 数据主要来自于快手主 APP、快手极速版 APP、快手 PC 端等产品。数据最终以服务端日志、客户端日志、Binlog 日志等形式,进入实时数仓。


在 ODS 层,我们会对超大 QPS 日志进行拆分,对加密数据进行解密。在 DWD 层,我们根据快手的业务划分,从数据上划分出视频 FT、直播 FT、搜索 FT 等。基于各个 FT 的业务过程构建出各个 FT 在多业务过程对应的 DWD 表、DWD 扩展维表。通过灵活的 DWD 层建设来支持各主题域下丰富灵活的实时业务场景。


在 ADS 层,我们基于不同的应用场景,采用不同的技术方案,支持对应的实时需求。


  • 在核心指标场景,我们基于沉淀的典型场景技术方案,采用以 Window 为核心的解决方案;

  • 在 AB 实验多维度大流量场景下,同一份数据经过多实验后,流量会被放大 N 倍。此时首先会通过构建对应的 DWS 层来缩减对应的 QPS,并且会采用 Flink 1.13 以上的版本,借助引擎本身自带的本地聚合特性来提升任务整体的性能。

  • 在垂类业务个性化场景下,我们会采用更细粒度的划分业务过程,拆分出特定场景下的 DWD 数据、DWD 扩展维表数据,从而直接把对应 DWD 数据导入到 Clickhouse 或用 Flink SQL 计算对应的实时指标。


2.2 实时数仓 ODS 建设


快手基于 Apache Flink 的实时数仓建设实践

下面会针对实时数仓分层中各个分层的特点,详细讲一下对应分层中沉淀的一些思路。


ODS 层直接对接原始数据。该层的数据有流量大、多业务共用、日志格式嵌套深的特点。在该层的实践中,除了解密日志、日志格式化等操作,还会重点关注数据复用性和下游口径一致性的问题。快手的客户端日志是全站统一、业务共用的,针对这种超大 QPS 的 Topic 我们进行了流量拆分。根据各业务主题域不同的拆分逻辑,拆分出专属于当前主题域的 Kafka topic 数据,从而减轻下游处理单一业务主题域的数据量。这样不仅节省了资源,而且从源头保证了主题域上数据口径的一致性。


针对拆流任务,我们支持动态配置。通过动态配置,避免单一业务主题域的新增以及口径的修改,造成对整个任务进行重启的问题。如上图所示,我们把客户端的曝光日志进行流量拆分,从而拆分出视频曝光、直播曝光、活动曝光等单一主题域的曝光数据。


2.3 实时数仓 DWD/DWS 层建设


快手基于 Apache Flink 的实时数仓建设实践

DW 层是数仓建设的核心,其丰富性和稳定性直接关系到数仓的丰富性和稳定性。DW 层的建设思路整体遵循维度建模理论。


实时数仓的 DWD 层首先要确保涵盖所有需要服务的业务过程和分析维度。其次为了保证任务的稳定性,会存在同时建设多个有相同业务过程的 DWD 表的情况。我们会依据特定场景来决定具体使用的 DWD 表或 DWD 扩展维表等。


在 DWD 层的实战中,DWD 表需要进行维度扩展是非常常见的需求。在我们的实战中,维表扩展会基于维表的具体情况选择不同的关联方式。


  • 在大多数情况下维表变化比较稳定,我们会选择借助第三方 KV 存储,使用 UDF 直接访问 KV 存储来实现维表扩展。但在选择第三方 KV 存储时,当维表内容特别大时选择 kiwi、当 QPS 较高时选择 Kcatch。

  • 当维表变化频繁且对时效性要求较高时,选择 interval join。借助 interval 时间范围的特性来达到合理控制状态大小的目的。

  • 当维表关联逻辑比较复杂,为了任务的稳定性和扩展性,我们会通过自定义维表进行关联,手动维护状态管理的过程,实现 DWD 维表的扩展。


实时数仓的 DWS 层只有在数据量特别大且聚合后的数据量有明显减少的场景下才会构建。如果 DWD 层的 QPS 比较小,一般会直接省去 DWS 层的建设。这样的做法不仅可以保证数据的及时性,同时也缩短了指标产出的链路,进而保证了任务的稳定性。


2.4 实时数仓 ADS 层标准方案


快手基于 Apache Flink 的实时数仓建设实践

在 ADS 层的方案设计时我们需要依据具体的需求场景设计不同的实现方案。在上线指标时,我们不仅要思考满足当前的指标需求,而且要考虑指标的可回溯性和任务稳定性。比如在上线时需要考虑指标实现过程中是否访问了外部存储、上线后状态是否超大、指标异常后当前方案是否支持数据回溯等。


在快手的 ADS 实践中,经常会遇到绘制指标曲线图的需求。针对这种场景,我们基于需求本身以及支持指标可回溯会选择以窗口为核心的解决方案。


  • 在针对当日累计的场景,即要求每分钟实时产出从当天 0 点开始到当前统计时间分钟截止的总指标值的需求,我们会选择 cumulate window。

  • 针对活动累计场景,即活动一般会持续 n 天,则需求要求每分钟实时产出从活动开始到当前统计时刻为止的总指标值。我们会选择 infinity_cumulate window。

  • 在针对分布类的指标需求时,即需求指标会随着时间的推移出现波动。同一粒度下我们需先拿到最新的数据状态,再进行下一步汇总的统计。我们会选择 unbounded+infinity_cumulate window。

  • 在针对单直播间累计的场景下,我们会选择 dynamic_cumulate。


2.5 单直播间累计指标


快手基于 Apache Flink 的实时数仓建设实践

接下来以 dynamic_cumulate 为例,展示窗口在实际场景下的使用。需求的背景是基于直播流每分钟统计从直播开始到直播结束期间,各个直播间总的观看人数和观看次数。直播间的特点是每个直播间可能存在直播跨天的情况、不同直播间结束的时间点各不相同、直播间结束后直播间统计数据不会再更新。


通过分析需求的实践发现,如果直接采用 Flink 本身的 session window、cumulate window 都无法满足需求,为此我们开发了 dynamic_cumulate window。通过该方案,不仅能分钟级产出所有直播间的统计指标,并且状态可控数据可回溯。dynamic_cumulate window 的用法如图所示,函数对应的三个参数分别是:time_attr 指定数据流的时间属性;step_interval 定义窗口触发计算的时间间隔;gap_interva 标识最新一条数据到达后,多长时间内没有数据到达就可以认为统计窗口结束。


当前函数本质是一个窗口函数。当直播间结束后,满足第三个参数设置的时长后,指标数据就不会更新就不需要统计当前直播间的指标值,此时可以从统计任务的状态中删除直播间对应的状态。最终达到了实时任务状态可控的要求。


2.6 实时数仓资源治理


快手基于 Apache Flink 的实时数仓建设实践

实时业务需求暴增、实时队列资源使用长期处于超过安全水位线运行、公司倡导降本增效、平台资源申请各种受限等,上述场景普遍发生在各个实时数仓的建设阶段。线上实时任务对列使用混乱,没有区分队列优先级。高优任务和一般任务混合部署,不同任务间资源抢占时有发生。


在面对上述实时资源的背景和现状,我们从存量任务、新增任务、集群队列三个方向总结了一些实时资源的治理方法。


  • 存量任务方面,依据任务血缘确定出无下游引用的实时任务,然后确定实时任务对应的数据集是否还在线上使用,从而对无用任务进行下线;其次通过梳理各个数据主题域的数据模型,确定出烟囱任务,对其进行合并下线;最后针对超大资源使用的任务进行方案评审,通过优化方案缩减大资源使用的资源量。

  • 针对新增任务,在上线任务时会组内评审任务的实现方案,确定方案最优后才能上线。其次每个上线的实时任务都需要进行压测,依据压测结果设置合理的资源方可上线。

  • 针对集群队列,我们对集群队列进行优先级的划分,按照不同任务的优先级部署到相应的队列中;在整个实时任务的监控方面,我们开发了实时任务的资源使用健康评分机制。通过定期的统计实时任务的资源使用情况,将结果发送给实时任务列表评分比较低的 owner。从而保障在线任务的资源使用处理在合理的水平;我们针对实时队列的资源使用率进行监控。当超过队列安全水位线后,系统会及时报警提醒管理员进行队列扩容。


通过上述方案,我们目前高优队列长期处于安全水位线以下,很好的解决了资源过度浪费的问题。


03

实时数仓场景化实战


3.1 业务实时应用场景的特点及挑战


快手基于 Apache Flink 的实时数仓建设实践

如上图所示,这两个场景分别为 S 级别活动大屏以及 AB 实验多维效果数据。S 级别活动大屏是快手在举办节日或盛典活动时,高管或产运同学必不可少的一种用于监控活动整体效果的重要工具,其中的指标通常都是大盘指标,而这类指标的加工链路的特点就在于上游的数据量是非常大的,通常为百万级 QPS,而在这么大的数据量下,业务又有 3 点强诉求,分别是算的准,不能因为数据乱序而丢数;算的快,要保证秒级别的数据更新速度;算的稳,如果出现故障,要在分钟级别的进行数据的恢复,所以对于 S 级别活动大屏来说,实时数仓的建设面临的挑战主要是核心场景的保障问题,而解决思路也很清晰,分别是以开发生命周期为基础的正向保障思路和模拟故障注入为基础的反向保障思路。


第二个业务应用场景是 AB 实验多维效果数据,相信大家对于 AB 实验并不陌生,AB 实验是推荐策略同学用于来验证策略是否有效的重要工具,而要评估 AB 实验的效果自然离不开 AB 实验效果数据,但是传统的离线链路加工的方式产出时延达会达到 t - 1,导致推荐策略同学调整实验策略的周期很长,实验迭代效率低,因此实时产出分钟级别的 AB 实验效果数据目前正在成为实时数据的一个重要价值场景,推荐策略同学依赖实时的 AB 实验效果数据能够极大的提升策略调整的效率。接下来我们看看 AB 实验指标的特点,它和 S 级大屏有类似的地方,AB 实验关注的往往也是大盘数据,因此计算指标的 Flink 任务的入口流量通常也是百万级别 QPS 的大流量,除此之外,在近百个实验同时在线的情况下,会进一步造成计算数据量的膨胀,关于数据量的膨胀原因我们将在后续详细分析。除此之外,AB 实验指标还有另一个重要特点,由于业务迭代速度快,因此业务需要对 AB 指标进行分析的维度也是非常丰富的,不止如此,维度也会经常变化和更新。而结合上面这两个特点,在 AB 实验效果实时数据的落地过程中,我们面临的挑战主要就是大数据量下的 Flink 任务性能问题以及快速业务迭代中 AB 维度扩展的灵活性问题。针对这两个问题,我们给出的解决思路是通过建设用于 AB 维度扩展 DWD 层提升维度扩展灵活性并通过建设多维 DWS 层压缩数据量的方式来提升任务的性能。


在了解了这两种场景的各自的业务特点以及我们的解决思路之后,接下来我们详细分析每种场景下的建设方案以及保障方案的细节。


3.2 S 级大屏的保障思路


快手基于 Apache Flink 的实时数仓建设实践

首先是 S 级别活动大屏。如上图所示,这类场景中的指标通常都是同时在线数据和当日累计数据,也就是 tumble 和 cumulate 两类窗口的指标。指标本身的处理逻辑并不复杂,重要的是保障。保障的诉求是算的准、算的快、算的稳,针对这三点,我们提出了横向和纵向的切分的保障方案,在横向切分中,将算的准和算的快归类到以开发生命周期为正向的保障范畴,将算的稳归类到了模拟故障注入为基础的反向保障范围,在纵向切分中,我们是以大屏指标的整体生命周期为思路展开的,主要分为开发、测试、服务 3 个阶段,在这每个阶段针对每种保障诉求分别提供了对应的解决方案。


对于算的准来说,在开发阶段,会使用快手内部沉淀的标准化解决方案,并且由于快手对于数据曲线可回溯的诉求特别强烈,所以 allowLateness 机制可以说是大屏场景必用的一项配置,在测试阶段,我们会通过多轮数据内测来保障数据的准确性,在服务阶段,我们会分别运用同环比的实时波动率 DQC、实时时序算法 DQC 以及实时离线对比的准确性 DQC 来及时监控数据质量。


对于算的快来说,为了保障数据产出尽可能的低时延,我们通常会将窗口计算的频次提频到 10s,并且尽量缩短指标的产出链路来降低指标产出的时延,在测试阶段,通过压测检测任务是否有数据倾斜问题以及其他的性能瓶颈点,并在测试阶段全部解决,在服务阶段,通过配置标准的性能监控,比如数据处理延迟,单节点处理延迟,输入输出 QPS 等监控项来监控任务是否处于正常的数据处理状态。


对于算的稳来说,在开发阶段,针对这种核心高优指标,我们会进行多机房部署,并针对一些可能出现的异常情况做故障恢复的预案,在测试阶段,会通过数据回溯的性能测试来保障任务在满足 SLA 的前提下快速将数据回溯完成,同时会通过 Flink 引擎侧提供的限流以及 watermark 对齐等能力来保障任务在回溯过程中不会由于回溯压力过大而导致任务失败。


接下来,详细介绍算的快、算的准以及算的稳中的具有快手特色的解决方案。


快手基于 Apache Flink 的实时数仓建设实践

首先是算的准,在 S 级活动大屏的应用场景中,当日累计类的指标几乎占据了一半的*,而我们知道在 cumulate 窗口应用中,只要在整个大窗口内,乱序的数据都不会被丢弃,这看起来虽然好,但是面对严重数据乱序的场景时,cumulate 只会将乱序数据记录到最新处,而这就会导致出现图中红框中圈起来的问题,其中绿色的线是在没有数据乱序时正确的趋势图,而当发生数据乱序后,cumulate 实际计算得到的结果是下面蓝色的曲线。


而针对这个问题,我们自然会想到 tumble 窗口中提供的 allowLateness 机制,但是目前的 cmulate 窗口并没有这种机制,因此我们针对 cumulate 的场景开发了 allowLateness 机制来实现相同的效果。首先来看 cumulate 的执行机制,cumulate 窗口在执行时会包含两部分状态数据,分别是 merged state 和 slice state,当窗口大小为 1 天,步长为 10s,最大乱序时间为 30s,当前的 watermark 为 9 分 10 秒时,merged state 中包含的数据范围是 0 分到 9 分 10 秒的数据,而剩下会有 3 个 slice state,其中状态中的数据分别为 9 分 10 秒到 9 分 20 秒,9 分 20 秒到 9 分 30 秒,9 分 30 秒到 9 分 40 秒,随着 watermark 的推进,slice state 会一一合并到 merged state 中,然后将 merged state 中的结果输出,这时如果有一条 5 分 23 秒的数据来了之后,就只能记录到最新的 slice state,这就出现了我们刚刚提到的问题。而为 cumulate 实现 allowlateness 的思路并不复杂,依然是上面这个案例,当我们设置了 5min 的 allowLateness 后,从 4 分 10 秒一直到 9 分 40 秒之间中所有的数据都要保存到 slice state 中,而 merged state 中只包含 0 分到 4 分 10 秒之间的累计数据,如果这时 5 分 23 秒的数据来了之后,就会将这条数据放入到 5 分 20 秒到 5 分 30 秒的 slice state 中,然后在输出数据时,可以将 4 分 10 秒到 9 分 10 秒之间的数据重新输出一遍,通过这种方式就可以将严重乱序场景中的不符合预期的曲线给自动修复。这个功能在快手实际的应用场景中可以做到在大流量的任务中设置 30min 的 allowLateness 来解决最近 30min 内的数据乱序问题,在小流量任务中,会设置 1 天的 allowLateness 来解决最近 1 天以内的数据乱序问题。


快手基于 Apache Flink 的实时数仓建设实践

接下来是算的快的中的数据倾斜问题处理的优化方案。数据倾斜对于 Flink 任务的危害是非常大的,通常我们会使用图中的 SQL 来作为常用的数据倾斜解决方案,在 SQL 的内层,通过对 user_id 取模将数据打散,然后通过 SQL 的外层将打散的数据进行合并。但是这种常见的解决方案依然会存在问题,问题在于由于 Flink 引擎在计算每一个 key 所属的 key_group 时,依然会有一层 hash 策略,而这就使得每一个 key_group 中处理的 key 的个数不同,依然导致存在一定的数据倾斜。举例来说,SQL 内层聚合算子的 key 总共有 3 个,分别为 0,1,2,接下来假设这个 SQL 对应的 Flink 任务的中聚合算子的并行度以及最大并行度都为 3,那么 key_group 也就有 3 个,我们也分别记为下标为 0,1,2 的 key_group,但是由于 key 和 key_group 之间存在 hash 策略,则会导致出现 key 为 0 和 1 的数据只会被发送到下标为 0 的 key_group 中,key 为 2 的数据只会被发送到下标为 2 的 key_group 中,其中下标为 1 的 key_group 一条数据都不会接收到,最终就出现了数据倾斜的问题。而我们期望的效果为 key 和 key_group 最好能够一一对应,key 为 0 的数据只会被发到下标为 0 的 key_group 中,key 为 1 的数据只会被发到下标为 1 的 key_group 中,key 为 2 的数据只会被发到下标为 2 的 key_group 中。


接下来是解决方案,解决方案其实是一种通过 key_group 的下标去找该 key_group 的 key 的思路。其中主要的步骤有两个,第一步,需要保证 key 的个数和 key_group 的个数相同,举例来说,如果 key 为 0,1,2,那么 key_group 也必须为 3 个,第二步,使用 key_group 的下标通过 key 和 key_group 的 hash 策略去主动的寻找这个下标的 key_group 对应的 key 的值,并维护出一个 key_group 和 key 的 map,举例来说,假设下标为  0,1,2 的 key_group 找到的 key 分别为 15,18,19。接下来,当任务中实际的 key 为 0 时,我们就会通过维护的这个 map 将其映射为 15,然后 Flink 引擎拿到 15 之后经过 hash 策略计算后就能得到这个 key 要发往下标为 0 的 key_group,这就实现了 key 和 key_group 之间的一一映射,避免因为 Flink 引擎的 key 和 key_group 之间的 hash 策略导致的数据倾斜问题。最后来看看这种优化方案在我们实际应用中的效果,这种优化方案非常适合在 DWD 访问维表的应用场景,只要 key 本身没有倾斜,Flink 任务就不会出现数据倾斜的问题。


快手基于 Apache Flink 的实时数仓建设实践

最后是指标算的稳的保障思路,最有效的方法莫过于指标产出全链路的双机房热备部署,如图所示,从输入的 Kafka Topic 到 Flink 计算任务、依赖的维表存储的 Redis 引擎,一直到产出的 Kafka Topic 以及最终的 OLAP 服务引擎都部署了双机房。当 Kafka 或者 OLAP、Redis 引擎出现故障时,依赖于快手基础架构的自动容错能力,在开发人员无需任何干预的情况下,就能实现自动的机房切换。当 Flink 引擎单机房出现故障时,首先我们会判断 Flink 任务是否能够在 SLA 的时间内快速恢复,如果无法快速恢复,在验证了热备机房产出数据正确性的前提下,我们会切换为热备机房产出的数据集。当然了,主备链路的切换是一个需要上下游联动才能做出决策的高成本操作,所以我们依然会对每一条处理链路做压力测试并预留 buffer,保证在没有出现重大故障问题的情况下,单个机房的任务也能快速恢复,继续提供服务。


3.3 AB 实验多维数据建设思路


接下来分析第二个应用场景,AB 实验多维数据整体建设过程,AB 实验多维数据的指标和第一个场景中类似,指标本身并不复杂,以直播曝光为例,那么最终的 Flink 任务就是图中的 SQL 展示的 tumble 窗口,其中多维体现在 SQL 中 group by 的维度多,比如会使用直播间、主播等多个维度交叉进行分析。


快手基于 Apache Flink 的实时数仓建设实践

如图所示,AB 实验多维数据的核心诉求和建设难点分为两部分。


第一部分的核心问题是业务迭代的灵活性问题。业务侧迭代速度很快,通常观察的都是直播间、主播的一些个性化维度,平均 1~3 个月就新增或者下线 2 个维度,而这些维度又是来源于多个不同的维表,比如 author_dim1 来自表 A,author_dim2 来自表 B,最后会通过 hive2redis 导入到 redis 中以便 Flink 通过 lookup join 将维度数据关联到,但是如果每一张表导入一遍 redis,将会导致 redis 资源的浪费,并且 Flink 任务也得需要多次 lookup join,会导致一定的性能瓶颈,除此之外越来越多的维度也会导致 Flink 任务计算性能的急剧下降。


针对这个问题,我们从开发以及治理两个角度给出了对应的解决方案,首先是开发方案,我们会首先将多张维表合并,统一构建一个 AB 专用的 Hive 维表以及一个 AB 专用的的 DWD 维度扩展任务,通过一次访问就能将同一个粒度下的所有维度数据访问到,即使维度有新增,只要粒度不变,依然可以添加到原来的维表中,除此之外,由于近百个实验中,不同的实验关注的维度组合是不同的,所以我们也会将实验按照维度进行分组归类,然后分别构建不同维度组合的 ADS 层任务产出数据,避免出现一个 Flink 任务计算过多的维度组合。除此之外,由于维度不能无限扩展,所以我们会通过定期监控 OLAP 数据服务引擎中维度字段的访问频次来判断维度是否已经没有在使用,从而下线无效的维度。


接下来是 AB 实验多维数据的第二个建设难点。这个难点的核心问题任务的性能问题,用于计算 AB 实验的 Flink 任务的入口流量是百万级别的 QPS,而且同时在线的实验个数也是近百个,所以这里就会出现数据的膨胀问题,如图所示,一个 user_id 同时在 30 个实验中,那么一条包含 user_id 的直播曝光原始数据就会被膨胀为 30 条数据,那么百万级别的 QPS 经过数据膨胀之后就会变为千万级别的 QPS,这对 Flink 任务的性能是一个极大的挑战,而针对这个问题我们也从开发以及治理两个角度提出了对应的解决方案。开发方案的核心思路就是删减数据和压缩数据,从删减数据的角度出发,由于不是每一个实验都需要观看实时数据,所以我们会对计算实时数据的实验通过配置中心进行管控,只计算需要的实验的实时数据,除此之外,从压缩数据的角度出发,在加工链路上,我们构建了 uid 粒度的多维 DWS 层对数据进行压缩,在 ADS 层还利用了 tumble 窗口两阶段优化对数据进行了有效的压缩优化。除此之外,当一个任务达到性能瓶颈时,我们还会对计算任务进行横向扩展,按照实验拆分为多个任务进行处理。在治理方案上,主要是对实验的上线的审核和实验下线的治理监控。


快手基于 Apache Flink 的实时数仓建设实践

最后,用一张整体的 AB 实验多维数据架构图来将上述介绍到的解决方案进行说明,其中整体可以分为四部分。


  • 第一部分为左上,将所有的维度合并到同一张 AB 专用维表中;
  • 第二部分为左下,构建 DWD 任务关联 AB 的个性化维度,并构建 DWS 任务按照 user_id 对数据进行压缩;
  • 第三部分为右下,通过配置中心管控计算 AB 实时数据的实验,并通过任务横向扩展和维度纵向切分将单任务计算的压力分摊到多个任务上;
  • 第四部分是右上,在 ADS 任务中,通过 Tumble 窗口的两阶段优化有效的压缩上下游算子传输数据量。


04

未来规划


快手基于 Apache Flink 的实时数仓建设实践

快手实时数仓的未来规划分为夯实基建、降本提效和价值场景三部分。


夯实基建包含三点:


  • 实时资产的统一管理。目前实时数仓资产的服务出口并没有统一,而是分散在每一个开发人员的手中。实时资产的查询和使用的成本相对比较高,未来我们会将实时资产的出口通过平台进行统一的管理和收口。

  • Flink SQL 的精细化配置。比如对算子并行度进行独立设置,避免资源浪费。除此之外,Flink SQL 升级后的状态兼容是一个难题,后续计划对 Flink SQL 算子的 ID 实现配置化,让 Flink SQL 任务能够更加轻松的进行升级。

  • 实时任务的异常阻断。主要指实时维表任务出现问题时,关联的 DWD 层和 ADS 层任务进行及时阻断,避免产生错误的结果。


降本增效包含两点:


  • Flink 任务的动态扩缩容,实时任务和离线任务的波峰波谷正好相反。在波谷时,我们计划降低 Flink 任务的并发度,将这部分资源预留给离线加工任务,从而达到较高的资源使用率。

  • Flink 任务的问题的智能诊断。接下来,我们会将常见的问题进行归类,并结合对应问题发生时的指标异常进行结合,实现自动化。智能且高效地判断出问题的可能原因,从而降低运维成本。


针对价值场景,我们会探索湖仓一体化。目前,Flink 结合 Hudi 的增量计算场景,在快手内部已经有落地。接下来,我们会深化增量计算场景的拓展。除此之外 Table Store 也是我们非常感兴趣的一个方向,接下来会尝试探索应用,让实时计算和增量计算在业务场景中扮演更加有价值的角色。