摘要:本文整理自快手实时计算数据团队技术专家李天朔在 Flink Forward Asia 2021 实时数仓专场的演讲。主要内容包括:
- 业务特点及实时数仓保障痛点
- 快手实时数仓保障体系架构
- 春节活动实时保障实践
- 未来规划
01业务特点及实时数仓保障痛点
快手最大的业务特点就是数据量大。每天入口流量为万亿级别。对于这么大的流量入口,需要做合理的模型设计,防止重复读取的过度消耗。另外还要在数据源读取和标准化过程中,极致压榨性能保障入口流量的稳定执行。
第二个特点是诉求多样化。快手业务的需求包括活动大屏的场景、2B 和 2C 的业务应用、内部核心看板以及搜索实时的支撑,不同的场景对于保障的要求都不一样。如果不做链路分级,会存在高低优先级混乱应用的现象,对于链路的稳定性会产生很大的影响。此外,由于快手业务场景的核心是做内容和创作者的 IP,这就要求我们构建通用维度和通用模型,防止重复烟囱建设,并且通过通用模型快速支撑应用场景。
第三个特点是活动场景频繁,且活动本身有很高的诉求。核心诉求主要为三个方面:能够体现对公司大盘指标的牵引能力、能够对实时参与度进行分析以及活动开始之后进行玩法策略的调整,比如通过对红包成本的实时监控快速感知活动效果。活动一般都会有上百个指标,但只有 2-3 周的开发时间,这对于稳定性的要求就很高。
最后一个特点是快手的核心场景。一个是提供给高管的核心实时指标,另外一个是提供给 C 端的实时数据应用,比如快手小店、创作者中心等。这对数据精度的要求极其高,出现问题需要第一时间感知并介入处理。
以上要素构成了快手实时数仓建设和保障场景的必要性。
在实时数仓保障的起始阶段,我们借鉴了离线侧的保障流程和规范,按照生命周期划分了三个阶段:研发阶段、生产阶段和服务阶段。
- 研发阶段构建了模型设计规范、模型开发规范以及发布的 checklist。
- 生产阶段主要构建底层监控能力,对于时效性、稳定性、准确性几个方面进行监控,并且依照监控能力进行 SLA 优化和治理提升。
- 服务阶段明确了上游对接的服务标准和保障级别,以及对于整个服务的价值评估。
但是相比于离线,实时的学习成本颇高,完成以上建设后,各个结算依然存在几个问题:
- 研发阶段:Flink SQL 的学习曲线相比于 Hive SQL 更高,容易在开发阶段引入隐患。另外,实时计算场景下,活动出现洪峰时能否快速消费,也是一个未知数。最后,DWD 层的重复消费对于实时侧的资源挑战也很大,在选择数据源和依赖关系时需要考虑资源问题。
- 生产阶段:state 没有清理机制会导致状态变大、作业频繁失败。另外高优先级和低优先级部署需要机房隔离,因此需要在上线前就安排好,上线后再进行调整,成本会比离线高很多。
- 服务阶段:对于一个实时任务,最无法接受的就是作业流程失败、重启,导致数据重复或者曲线掉坑的问题。为了避免这类问题,需要有标准化的方案,而离线大概率可以保证重启后数据一致性。
抽象来看,实时数仓相比于离线,还存在几个保障难点,具体体现在以下几个方面:
- 高时效性。相比于离线的执行时间,实时情况下,延迟分钟级就要介入运维,对时效性要求很高。
- 复杂性。主要体现在两个方面:一方面数据不是导入即可查,数据逻辑验证的难度更高;另外一方面,实时大多是有状态,服务发生问题的时候状态不一定能够被完整保存,会存在很多无法复现的 bug。
- 数据流量大。整体的 QPS 比较高,入口流量级别在亿级。
- 问题随机性。实时数仓发生问题的时间点更加随机,没有规律可循。
- 开发能力良莠不齐。如何保证通用场景的开发方案统一,防止因开发方案不同而产生不可控的问题。
02快手实时数仓保障体系架构
基于以上保障的难度,我们设计了两条思路来解决,主要分为两个方面:
- 一方面是以开发生命周期为基础的正向保障思路,确保每一个生命周期都有规范和方案指导,标准化 80% 的常规需求。
- 另一方面是以故障注入和场景模拟为基础的反向保障思路,通过场景模拟和故障注入,确保保障措施真正落地并符合预期。
2.1 正向保障
正向保障的整体思路如下:
- 开发阶段主要做需求调研,针对开发过程中基础层如何开发、应用层如何开发进行标准化处理,可以解决 80% 的通用需求,剩余 20% 的个性化需求通过方案评审的方式来满足,同时不断从个性化需求中沉淀标准化方案。
- 测试阶段主要做质量验证和离线侧对比以及压测资源预估。自测阶段主要通过离线实时的一致性对比、server 看板和实时结果对比来保障整体准确性。
- 上线阶段主要针对重要任务上线需要准备的预案,确认上线前动作、上线中部署方式和上线后的巡检机制。
- 服务阶段主要是针对于目标做监控和报警机制,确保服务是在 SLA 标准之内的。
- 最后是下线阶段,主要做资源的回收和部署还原工作。
快手的实时数仓分为三个层次:
- 第一,DWD 层。DWD 层逻辑侧比较稳定且很少有个性化,逻辑修改分为三种不同的格式数据:客户端、服务端和 Binlog 数据。
- 第一项操作是拆分场景,由于实时数仓没有分区表的逻辑,所以场景拆分的目的是生成子 topic,防止重复消费大 topic 的数据。
- 第二个操作就是字段标准化,其中包括纬度字段的标准化处理、脏数据的过滤、IP 和经纬度一一映射关系的操作。
- 第三是处理逻辑的维度关联,通用维度的关联尽量在 DWD 层完成,防止下游过多流量依赖导致维表压力过大,通常维表是通过 KV 存储 + 二级缓存的方式来提供服务。
- 第二,DWS 层。这里有两种不同的处理模式:一是以维度和分钟级窗口聚合为基础的 DWS 层,为下游可复用场景提供聚合层的支撑;二是单实体粒度的 DWS 层数据,比如原始日志里核心用户和设备粒度的聚合数据,可以极大地减少 DWD 层大数据量的关联压力,并能够更有效地进行复用。DWS 层数据也需要进行维度扩充,由于 DWD 层数据量过大,无法完全 cover 维度关联的场景,因此维度关联 QPS 过高并有一定延时的需求,需要在 DWS 层完成。
- 第三,ADS 层。它的核心是依赖 DWD 层和 DWS 层的数据进行多维聚合并最终输出结果。
基于以上设计思路,不难发现针对 DWD 和 DWS 的拆流的逻辑、字段清洗标准化和维度关联,都是针对不同格式但逻辑相同。可以把基础的逻辑开发成模板化 SDK,后续相同逻辑都使用相同的 SDK API 方法。这样有两个好处,重复的逻辑不需要再复制一遍代码,一些优化的经验和教训也沉淀在了模板里。
针对 ADS 层数据,我们通过业务需求沉淀出诸多解决方案,比如多维度的 PV/UV 如何计算、榜单如何计算、指标卡的 SQL 如何表达以及分布类存在回撤的场景如何产出。
SQL 本身上手快、效率高,能大规模简化开发时间,但它的执行效率相比于 API 有一定的劣势,所以针对于基础储层和 DWS 层大流量场景,我们还是使用 API 进行开发,应用层通过 SQL 进行开发。
快手的大部分活动中,业务最关注的指标是某些维度下参与人数、领取金钱的累计曲线,并且希望能够产出一个每分钟计算 0 点到当前时刻的曲线,这类指标开发覆盖了 60% 左右的活动侧需求。那么开发过程中有哪些难点呢?
用常规的滚动窗口 + 自定义状态的计算对数据进行去重有一个弊端:如果窗口乱序较大,会造成数据丢失严重,影响数据的准确性。如果希望数据更准,就要承受更大的数据延迟,而想要延迟低一些就可能存在数据不准确的情况。此外,异常情况下会存在数据从某一个时间点开始回溯的场景,回溯场景下增大吞吐量会因为取最大时间戳导致中间结果丢失。
为了解决这个问题,快手自研了渐进式窗口的解决方案,它存在两个参数,天级别的窗口和输出的分钟步长。整体的计算分为两个部分,首先产出一个天级别的窗口,读取数据源按照 key 进行分筒,把 key 相同的数据分到同一个筒内,然后按照事件时间进行 watermark 推进,超过对应的窗口步长就会触发窗口计算。
如上图所示, key=1 的数据分到同一个 task,task watermark 更新到超过步长产生的小窗口之后会合并产出 bitmap 和 pv 的计算结果,并发送给下游数据,按照 servertime 落到对应的窗口,并且通过 watermark 机制进行触发。在 global window 进行合筒操作时,会把分筒的结果进行累加和去重,最终输出结果。这样如果存在乱序和晚到的数据就不会丢弃数据,而是会记录延迟之后的时间节点,更好地保证了数据的准确性,整体的数据差异从 1% 下降到 0.5%。
另外一方面,watermark 超过步长 window 窗口就触发计算,曲线延迟可以控制在一分钟以内完成,更好地保证了时效性。最后通过 watermark 控制步长的窗口输出可以保障步长窗口每个点都进行输出,输出曲线最大程度保障了平滑性。
上图是一个具体的 SQL 案例,内部是一个按照 deviceID 分筒,然后构建 cumulate window 的过程。window 有两个部分,一个是按天累计的计算参数,另外一个是 watermark 划分窗口的参数,外层会对不同分筒产生的指标进行聚合计算。
在上线阶段,首先是做好时间线的保障规范,包括时间、操作人、预案内容、操作记录和检查点。
- 活动前,部署任务确保没有计算热点、check 参数是否合理、观察作业情况以及集群情况;
- 活动中,检查指标输出是否正常、任务状态巡检以及遇到问题的故障应对和链路切换;
- 活动后,下线活动任务、回收活动资源、恢复链路部署及复盘。
这里的链路是从 Kafka 数据源开始导入到 ODS、DWD、DWS 层,针对 C 端用户会导入到 KV 存储里,针对分析类场景会导入到 ClickHouse,最后生成数据服务。我们将任务分成 4 个等级,p0 ~ p3。
- P0 任务是活动大屏,C 端应用对于 SLA 的要求是秒级延迟以及 0.5% 内误差,但是整体保障时间比较短,一般活动周期都在 20 天左右,除夕类活动 1~2 天内完成。我们应对延迟的方案是针对于 Kafka 和 OLAP 引擎都进行了多机房容灾,针对于 Flink 做了热备双机房部署。
- 针对 P1 级别的任务,我们对 Kafka 和 OLAP 引擎进行双机房部署,一方面双机房部署可以做容灾逃生,另一方面在线机房的配置比较好,很少出现机器故障导致作业重启的情况。
- 针对 P2 和 P3 级别的任务,我们在离线机房部署,如果存在一些资源空缺的情况,会先停止 P3 任务,腾挪资源给其他任务使用。
服务阶段主要分成 4 个层次:
- 第一,SLA 监控主要监控整体产出指标的质量、时效性和稳定性。
- 第二,链路任务监控主要对任务状态、数据源、处理过程、输出结果以及底层任务的 IO、CPU 网络、信息做监控。
- 第三,服务监控主要包括服务的可用性和延迟。
- 最后是底层的集群监控,包括底层集群的 CPU、IO 和内存网络信息。
准确性的目标具体包括以下三部分:离线实时指标一致性用来保障整体的数据处理逻辑是正确的,OLAP 引擎和应用接口一致性用来保证服务的处理逻辑是正确的,指标逻辑错误报警用来保障业务逻辑是正确的。
- 准确性报警又分成 4 个方面,准确性、波动性、一致性和完整性。准确性包括主备链路侧的一些对比,维度下钻是否准确;波动性是衡量持续指标的波动范围,防止波动大产生的异常;一致性和完整性通过枚举和指标度量保证产出一致且不存在残缺的情况。
- 时效性的目标也有 3 个,接口延迟的报警、OLAP 引擎报警和接口表 Kafka 延迟报警。拆分到链路层面,又可以从 Flink 任务的输入、处理和输出三个方面进行分析:输入核心关注延迟和乱序情况,防止数据丢弃;处理核心关注数据量和处理数据的性能指标;输出则关注输出的数据量多少,是否触发限流等。
- 稳定性的目标有 2 个,一个是服务和 OLAP 引擎的稳定性、批流延迟,另一个是 Flink 作业的恢复速度。Flink 作业 failover 之后能否快速恢复,对于链路的稳定性也是很大的考验。稳定性主要关注作业执行的负载情况,以及对应服务依赖的状态、整体集群的负载以及单个任务的负载。我们通过目标进行报警,目标拆解的子目标进行监控,构建整体的监控报警体系。
2.2 反向保障
线上活动正常的开发测试很难模拟真正的线上环境和压测进度,所以反向保障的重点是要测试活动流量预期的情况下能否扛住洪峰,以及出现故障时如何处理?
核心思路是通过压测演练来模拟活动洪峰的真实场景。首先通过单作业压测确定每个作业的资源分布和作业所在集群的编排方式,通过全链路压测确保集群资源使用在一定水位并且平稳消费洪峰,不会过大或过小。其次,进行容灾建设,主要针对作业失败、消费延迟、机房故障等提出了一些保障手段。然后,通过演练的方式,确保这些手段可以被正常使用并且能够达到预期效果。最后,针对演练的预期和目标进行复盘和链路风险的改进。
我们构建了自己的压测链路,上面是正常的链路,下面是压测链路。首先读取线上 topic 的数据作为压测链路的初始数据源,利用 rate limit 算法进行流量控制。比如有 4 个 task,希望获得 1 万 QPS,那么每个 task 生成的 QPS 会限制在 2500,并且生成数据的过程中会利用人群包修改对应的 user 和生成的时间戳,模拟当天真实的用户数。
读取压测的数据源 topic 并经过作业处理生成新的 topic 后,如何判断压测是否真正通过,有三个标准:第一,确保作业输入读取延迟为毫秒级,且作业本身无任何反压。第二,CPU的利用率不超过整体资源的 60%,保障集群有空余 buffer。第三,计算结果和人群包保持一致,证明逻辑是正确的。
经过单作业压测之后,我们可以得到很多信息用于指导后续工作。比如,可以证明活动能在预期流量下保障 SLA,可以发掘作业性能瓶颈,指导优化达成对应标准以及场景 benchmark,方便低优作业的资源部署。
完成单作业压测之后,还是无法判断所有作业是否完全启动。对于 Flink 机房整体的 CPU、IO 还有 memory 压力等情况,我们可以把每个作业按照压测目标值启动起来,观察整体作业和集群的表现。
那么如何判断全链路压测是否通过呢?也有三个标准:
- 第一,确保作业输入读取延迟为毫秒级,且无反压。
- 第二,CPU利用率整体不超过 60%。
- 第三,计算结果最终和人群包保持一致。
通过全链路压测之后,可以证明活动在预期流量的峰值情况下能够保障 SLA,确保 QPS 作用下作业的资源编排情况,提前确定每个作业所需的资源和部署参数,确保每个数据源上游最大流量信息,为后续的限流保障提供基础。
故障演练有两种方式:
- 一个是单作业的故障演练,包括 Kafka topic 作业故障、Flink 作业失败以及 Flink 作业 CP 失败。
- 二是更体系化的故障,比如链路故障,比如单机房故障如何保障正常产出,活动流量超过预期很多如何避免雪崩效应?某个作业 lag 超过一个小时,需要多久能恢复?
容灾建设分为两个部分,链路的故障容灾和链路的容量保障。
链路的故障容灾保障核心是解决单机房和单作业失败恢复时间长的问题和服务的稳定性问题。Kafka 本身可以做双机房容灾,生成流量会写入到两个机房的 Kafka,出现单机房故障时会自动把流量切换到另外一个机房,而且保证 Flink 作业无感知。另外一方面机房故障恢复之后,可以自动探测 Kafka 机房的状态加入流量。
同样,容灾策略也适用于 OLAP 引擎。针对于 Flink 任务,我们热备部署了双链路,主备链路同逻辑,某个机房出现故障时可以直接将应用侧 OLAP 引擎切换到另一个链路使用,保障应用端对于故障是无感知的。
链路容量的保障是为了解决两个问题:如果活动流量超过预期很多,如何保障稳定性?如果产生了 lag,评估需要多久能够追赶消费延迟?
根据之前全链路压测的结果,能够得到每个任务入口的最大流量,并且将这个流量值作为作业的最大限流值,当活动流量超过了预期很高,数据源侧会触发读取限流,Flink 作业会按照压测最大负载执行。这个时候作业消费虽有延迟,但是能够保护链路中其他作业正常运行。并且在洪峰结束后,可以根据 lag 数据和入口流量计算出作业恢复正常需要的时间,这个是链路的故障容灾和容量保障的核心措施。
03春节活动实时保障实践
春节活动有以下几个需求:
- 高稳定性,海量数据要求链路整体保持稳定或出现故障能够快速恢复。
- 高时效性,亿级别流量下,要求大屏指标卡秒级延迟、曲线 1 分钟级别延迟。
- 高准确性,复杂链路情况下,离线和实时指标差异不超过 0.5%。
- 高灵活性,能够支持活动过程中的多维分析应用场景。
春节活动的整体方案分为正向和反向的保障措施。
正向保障措施的基础是监控报警体系,分为两个部分。一方面是对时效性、准确性、稳定性做 SLA 目标报警建设。另外一方面是基于链路的监控体系建设,包括链路监控、链路依赖的服务可用性监控以及集群资源监控。
在监控体系的基础之上,正向保障措施主要是做开发阶段、测试阶段和上线阶段的标准化。开发阶段 80% 的需求通过标准化模板来解决,而 20% 的剩余需求可以通过评审的方式解决风险问题。测试阶段通过对比的方式保证逻辑准确性,上线阶段做分期部署和任务巡检。
反向保障措施需要构建两个基础能力。第一是压测能力,主要是通过单作业压测确定任务性能瓶颈,从而更好地指导优化;通过全链路压测确定作业是否能够扛过洪峰,并为容灾能力提供数据基础。容灾能力主要是通过多机房部署、限流、重试、降级,确保在有故障的情况下有对应的方案。
最后通过故障演练的方式,一方面引入各个组件的故障定位,另一方面模拟流量峰值的情况,确保压测和容灾能力真正得以执行。
最后在上线阶段通过时间线预案保障活动前、中、后操作步骤都有迹可循,活动结束后对于项目进行复盘,发现问题并反馈到正反两个方向的保障体系能力建设。
春节活动的实践获得了巨大的成功。时效性方面,面对上亿级别的流量洪峰,大屏核心链路指标卡秒级延迟,曲线类一分钟内延迟,单个任务处理数据量在万亿级别之上,在流量高峰期是秒级延迟。准确性方面,核心链路离线和实时任务差异 0.5% 以内,大促活动过程无数据质量问题,有效使用 FlinkSQL 渐进式窗口开发,大幅度降低窗口丢失导致的精度损失,数据差异从 1% 降到 0.5%。稳定性方面,核心链路依赖组建双机房容灾、Flink 集群热备双链路部署,出现问题秒级切换,压测和容灾能力的沉淀,为以后的活动保障体系建设奠定基础。
04未来规划
基于对现有的方法论和应用场景的思考,我们对未来规划也做了延伸。
- 第一,保障能力建设。针对压测和故障注入形成标准化剧本预案,预案执行通过平台能力自动化操作。压测之后,能够对问题进行智能诊断,将过往的一些专家经验进行沉淀。
- 第二,批流一体。过往的活动应用场景过程中,批和流是完全割裂的两套体系,我们在一些场景下做了流批一体的实践,并且正在推动整体平台化建设,通过统一 SQL 的方式提升整体开发效率,并且机器错峰使用可以减少作业压力。
- 第三,实时数仓建设。通过丰富实时数仓内容层面,以及开发组件的沉淀和 SQL 化的手段,达成开发效率的提升,最终达到降本提效的目的。