Infoq:腾讯内容千亿级实时计算和规则引擎实践优化之路

  • 传统的方式需要每次对大窗口中的全量数据做计算,而现有方式可以复用前一次计算结果,可极大减少计算量。
  • 我们方案中大窗口是逻辑上的大窗口,相比 Flink 原生的窗口计算会保留大窗口时间内的原始数据,我们在内存中并不存放这些原始数据,只存放算法提到的聚合维度的数据,同时利用数据淘汰机制,防止内存占用过大,节约大量的内存资源。

我们针对大窗口(如 1 天)、超大窗口(如 30 天等),结合计算复杂度和精度要求,采用了不同的计算方案,保障小成本高精准计算多种窗口指标。

大窗口计算

对实时流数据根据数据自身的事件时间是否连续分为如下不同的几种情况:

情况一:分钟级别滑动,每分钟窗口连续有流量的情况

当数据自身的事件时间连续的时候,我们需要拿到上次大窗口的计算结果值,在上次计算结果的基础上,对窗口的头部和尾部进行加减操作就可以得到新的大窗口的值。

图 3-4 分钟级滑动每分钟连续的大窗口

其中,T(6, 4) 代表的是 6min 时候近 4min 的累计值大小,其中 6 代表的是当前最新时间,4 代表的是需要统计的窗口大小,是人为规定的。M(5) 代表的是第 5min 的值。

情况二:分钟级别滑动,每分钟窗口流量不连续情况

当间隔的时间小于窗口大小的情况下,计算当前窗口的值需要得到上一个窗口的值进行加减操作,由于数据自身的事件时间中断,所以要对最后一次窗口的值进行校准。

图 3-5 分钟级滑动每分钟不连续大窗口

其中,T(5, 4) 代表的是 5min 时候近 4min 的累计值大小,其中 5 代表的是当前最新时间,4 代表的是需要统计的窗口大小,是人为规定的,M(5) 代表的是第 5min 的值。

情况三:分钟级别滑动,每分钟窗口流量不连续并且当间隔的时间大于窗口的情况

当间隔的时间大于窗口大小的情况下,由于窗口时间内没有出现流量,可以直接认为大窗口的计算值为当前分钟流量值。

图 3-6 分钟级滑动每分钟不连续大窗口

其中,T(6, 4) 代表的是 6min 时候近 4min 的累计值大小,其中 6 代表的是当前最新时间,4 代表的是需要统计的窗口大小,是人为规定的,M(5) 代表的是第 5min 的值。

超大窗口(如 30 天)

针对 30 天等超大滑动窗口计算,资源开销会成数十倍的膨胀,成本难以承受。我们构建了一套解决方案,成本降低到千分之一,精度只损失了百分之一,在成本和精度间达到了高效平衡。

图 3-7 超大滑动窗口指标计算

如上图所示,计算单个内容 ID 的超大滑动窗口指标过程如下。

  • 状态更新:读取消费流水,更新该 ID 的状态值。

  • 计算超大窗口指标:基于应用内状态进行计算。

    • 如果内容产生时间在 N 天内:取累计流量。

    • 如果内容产生时间在 N 天前:基于输入流量的时间取不同范围的数据,整体半天精度,如 30 天超大窗口的误差约 1.6%。

      • 00:00—12:00:取过去 N 天 + 当天流量值。
      • 12:00—23:59:取过去 N-1 天 + 当天流量值。
3.2.2 延迟流数据滚动大窗口计算

在内容生态场景中,由于历史原因和服务器时钟问题导致会出现超自然时间的数据,以及网络原因造成的延迟的数据。传统通过设置窗口水印的方式存在一定问题,对于超自然数据,会导致窗口立刻关闭;对于延迟数据,窗口关闭后,延迟到来的数据未能被统计到窗口指标中。

为了解决上述问题,我们设计了一种可以同时处理超自然数据和延迟数据的方案,优点如下:

  • 对于大窗口的计算有绝对的优势,普通的方式大窗口计算时候由于窗口太大,窗口不能及时关闭,当内存中存在大量的窗口时性能会急速下降。此技术通过聚合 Key 设计,极大的提高了大窗口情况下计算的稳定性、时效性、准确性。
  • 提供了及时的内存清理机制,保证聚合 Key 在过期时候能够被及时的清理,保证程序不会随着时间的推移而出现性能的损耗。

图 3-8 延迟流数据滚动大窗口计算

我们窗口计算转换为 Key 的分类聚合问题,通过对要参与聚合计算的 Key 进行巧妙设计,进而实现聚合统计。

步骤 1:计算数据所属的窗口起始值,窗口起始时间值 = 事件时间 /  窗口大小  *  窗口大小,窗口大小是根据业务需求来指定的。对于超自然数据,需要基于业务场景进行时间矫正。

步骤 2:根据窗口的起始值对数据进行分配,正常数据直接放入正确的窗口中,延迟数据由于只是晚到,但是数据的生成时间是正确的,所以可以根据窗口标记找到对应的窗口,放入对应的窗口中。

步骤 3:对窗口中的数据生成独有的聚合 Key,聚合 Key= 计算 Key+ 日期 + 窗口起始时间值。

步骤 4:按照聚合 Key 的值进行 Shuffle 分组,聚合 Key 相同的数据会被发送到同一个计算任务,进行聚合或者更加复杂的计算,并且清理内存中过期的聚合 Key,避免程序随着时间推移出现性能下降问题。

3.2.3 TB 级实时流数据拼接

Flink 原生实现进行 TB 级数据拼接时,计算较慢,且状态备份时可能异常导致难以升级 APP。

因此,我们构建了可以解决大状态下多流拼接的时效性和稳定性问题的技术方案,并保证最终一致性。

图 3-9 基于 HBase 实现 TB 级实时多流拼接

主要思路如上图所示,我们借助第三方 HBase 存储完成多流关联。

阶段 1:特征拼接,每个源单独加工,抽取自身特征后,进行如下过程:

  • 步骤 1:将自身特征同步到 HBase 中,每个源只更新自身属性对应的列。HBase 中会包含每个内容最新最全的属性。
  • 步骤 2:将有变更的内容推送到消息队列中。当前实现是将所有有变更的内容实时推送下游,可改造该过程,多流水位对齐后再推送,以支持多流拼接的多种语义。

在本阶段的存储设计中,HBase 的 Rowkey 为待关联的 Key,列分别为属性 Key 和属性值。同时,我们进行了大量优化设计:

  • 批量访问:每 50 个 Key 合并访问,减少 IO。
  • 随机主键:将 Key 进行 md5 哈希,让数据均匀分布在 HBase 中,防止热点,提高随机访问性能。
  • 存储压缩:部分属性值较大,将其序列化后,使用 GZIP 压缩,减少存储。
  • 过期机制:按需设置 TTL,防止数据无限膨胀。

阶段 2:特征输出,通过一个程序统一加工处理,可将每个内容的全量特征输出到目标业务系统中。

  • 步骤 3:实时感知特征有变更的内容。
  • 步骤 4:批量拉取内容的全量特征,HBase 中每一列对应一个特征,某个内容的全部列即为其全部特征。
  • 步骤 5:入库,将从 HBase 中获取的全量特征,转换成目标存储格式,输出到目标系统。
3.2.4 融合批数据重建流状态

在内容生态的实时计算场景中,我们经常会遇到累计指标的统计,比如某一条内容的实时总点击数、展现数等。传统的方式主要是用 Lambda 架构进行加工,面对口径发生变化等情形时,会有如下问题:

  • 批处理计算和实时流计算两份代码可能由多人维护开发,因此容易造成计算结果不一致。
  • 批处理计算和实时流计算切换的时候出现数据抖动,影响用户体验。

因此,我们设计了批流状态融合架构,主要优点如下:

  • 只需要维护一份实时流计算代码,通用性较好,适合所有实时流需要计算业务历史数据的场景。
  • 解决了实时流计算批量回溯历史数据时的算力问题,利用存量批处理计算资源回溯历史全量数据,同时,结合仅需从 T 日零点零分零秒开始的实时流数据,得到口径变化后的完整指标数据。规避了数据的抖动,提供好了良好的用户体验。

图 3-10 批量融合状态重建架构

首先计算业务历史全量累计数据存入 Key-Value 缓存中作为基准数据,把实时数据和基准数据进行融合计算得到最新累计值,并可根据下游系统的负载能力调整数据的输出间隔。

步骤 1:初始化时或者业务口径变更后,通过离线批处理计算历史全量数据,作为每个 Key 的基准数据,导入到 Key-Value 存储系统。

步骤 2:重启实时流计算应用程序后,每个 Key 根据是否初始化过基准数据,从 Key-Value 中初始化基准数据。

步骤 3:将基准数据和实时数据进行合并计算,通过流量控制把数据写入到下游业务存储系统中,供业务查询使用。

3.2.5 单体流量适应水平扩展

内容生态面临着内容的消费数据越来越大的情况,单个实时流计算程序在 Flink 状态不断增大的情况下,由于单个程序需要维护的状态越来越大,程序频繁出现反压问题,增加程序的并发度也提高不了稳定性。

通常我们会增加实时流应用来适应流量水平扩容的架构,但是增加应用后,如果把数据随机发往扩容后的程序,会有一些潜在的问题,例如在计算某个内容 ID 累计值的场景,需要这个内容 ID 对应的所有数据严格发送到同一个程序,才能保证最终结果的准确性。

图 3-11 单体流量适应水平扩容

为了解决以上问题,我们设计了如下可以适应流量水平扩展的架构。步骤如下:

步骤 1:记录数据首次进入系统的时间,为了防止数据丢失做高可用的持久化存储。

步骤 2:维护系统扩容前后的 buckets 的值,当数据过来之后根据数据首次进入系统时间所处的时间段找到对应的 buckets 的值。

步骤 3:对内容进行寻址,将内容 ID 哈希后分配到 buckets 个桶中,而下游每个 App 对应一个桶。

3.2.6 输出小文件数自适应流量

在内容加工场景中,需要将消息队列数据同步到 HDFS 中。同步时,会有 N 个同步子任务,其中 N 由流量峰值决定,N 在同步过程中不能调整,当数据时效性为分钟时,每分钟会有 N 个子文件。然而,在流量低峰期时,由于 N 不会改变,会产生大量的小文件。

图 3-12 输出文件数自适应流量

如上图所示,我们构建了一种输出小文件数自适应流量减少的解决方案。取单个文件为目标大小 S(如 64MB),以控制文件数目。我们将整个过程由原来的 1 个阶段拆分成了 2 个阶段:Map 阶段和 Reduce 阶段,其中 Map 任务数是 M,Reduce 任务数是 N。以下两个阶段,每分钟调度一次:

  • Map 阶段:读取数据进行自适应映射。

    • 缓存数据:每个任务缓存 1min 的数据。
    • 计算本批次产生的目标文件数 K:缓存的数据大小乘以 M 得到本批次所有数据输出大小 total_size,计算当前批次目标文件数 K=total_size/S。
    • 均匀映射:每条数据依次加上 1 到 K 的 Key,data 转换成 (k, data),以方便 Shuffle 控制。
  • Reduce 阶段:Reduce 子任务 k 只拉取 Key 为 k 的数据,这样,子任务 1 到 K 之间会有数据,剩下的任务无数据。因为空任务不会产生文件,这样可以保障本批次输出的文件数为 K。

3.3 规则引擎

在内容生态中除了实时流信号的生产服务,往往我们还需要进一步基于实时流信号,结合规则引擎管理业务个性化的触发逻辑,以此来支持内容周期智能管理等多种应用场景。

图 3-13 基于规则引擎的实时信号触发

3.3.1 规则管理平台

规则类型

基于不同的业务需求场景,规则定义区分了固定规则和动态规则:

  • 固定规则:同一规则下所有内容阈值相同。
  • 动态规则:同一规则下不同内容阈值可以精细化设置,用于满足基于内容特征属性需要不同的信号触发阈值的需求场景。

规则管理

提供规则以及内容阈值的增加、更新、查询等能力,并支持如下数据管理能力:

  • 规则增删改查:用户可以通过管理端查询规则列表,录入和修改规则。
  • 动态阈值增删改查:提供 Rest Api 对规则下内容的阈值进行新增、更新和查询。该能力可支持预估模块训练阈值后,将相应阈值更新到规则配置中;同时供规则执行引擎查询规则配置。

规则定义

配置模块旨在对规则进行进行抽象,通过定义通用的规则抽象定义,把用户在管理配置信息进行接入存储。解耦用户规则定义和规则引擎,降低用户输入和规则引擎的依赖,这样可以便于我们无负担去升级替换规则引擎而对用户无感。

图 3-14 规则信息

规则描述包括两部分,规则条件表达式 + 规则动作:

  • 表达式条件:上层逻辑支持且 / 或,支持多个运算算子;
  • 表达式动作:支持设置触发优先级以及携带特定信息等
3.3.2 规则执行引擎

基于上面信号产生的实时信号和规则管理提供的规则信息,我们探索了开源的 Aviator、Flink CEP 等组件。Flink CEP 构建规则执行引擎时有如下问题:

  • 不支持规则信息的动态更新,用户使用体验较差。
  • 不支持多规则,导致难以平台化。

Aviator 支持丰富的运算符和表达式,同时具有轻量级和高性能特点,能够完全覆盖我们的场景。为此,我们选取了 Aviator 作为规则匹配引擎。

规则执行引擎主要有如下三个模块:

规则加载

负责进行执行器所需配置的加载和实时感知,主要提供如下能力:

  • 规则变更秒级感知:负责加载规则列表,并保障规则变更后能及时同步,包括规则信息变更、规则添加、规则删除等;
  • 动态规则的阈值同步:动态规则中,每个内容有自己的阈值。在进行动态规则匹配时,可以实时获取(内容 ID、规则 ID)对应的阈值。

规则路由

从输入信号中提取业务渠道,和规则中业务渠道进行关联匹配,依次路由到不同的规则匹配算子中。

规则匹配

为不同规则提供相应的匹配能力:

  • 固定规则:将获取到的信号和每个规则进行轮询匹配。匹配时将规则中表达式和信号作为作为输入,通过 Aviator 进行匹配,如果信号满足规则,则将信号和规则关联,输出到下游。
  • 动态规则:整体流程和固定规则一致,但因为规则中阈值是动态的,需要进行设计以适配 Aviator:动态阈值作为实体元素放入表达式中(如 vv_1_day>=dynamic_value)中,将拉取的(内容 ID、规则 ID)的阈值拼接入输入信号中,字段为 dynamic_value,即可以实现动态规则的匹配。
3.3.3 规则匹配优化

并发匹配:由于单个任务计算能力有限,把数据分为若干份,在多个任务中进行规则匹配,极大的提高了规则引擎在大数据量场景下的匹配能力。

二级缓存:动态规则匹配时,需获取(内容 ID、规则 ID)的阈值,因输入信号峰值 QPS 数十万,拉取阈值会有较大网络 IO,造成极大资源开销。参考前文 ID 映射的解决方案,我们构建了(内容 ID、规则 ID)-> 阈值的二级缓存,可以极大节省匹配资源。

预编译技术:进行规则匹配时,首先将规则编译成机器能理解的字节码,然后将上游信号作为数据输入进行匹配运算。该过程主要耗时在将规则编译成字节码阶段,我们将规则对应的字节码进行缓存,可以节省上千倍的算力开销。

3.3.4 信号去重分发

信号去重

经过规则执行引擎后,仍然能召回大量信号,针对审核等场景,一个内容触发后,短时间内不需要再次输出,以免重复审核。为此,我们进行了个性化去重模块,支持灵活的去重周期,如永久去重、天级去重等,为不同的业务场景召回所需的信号。

信号分发

下游有多个业务系统,基于规则和业务场景的关系,将信号分发给对应的业务模块。分发方式支持消息队列投递以及接口回调,业务可以根据需要进行定制。

3.4 服务质量
3.4.1 端到端全链路服务可观测性

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数大数据工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年大数据全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友。
img
img
img
img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上大数据开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新

如果你觉得这些内容对你有帮助,可以添加VX:vip204888 (备注大数据获取)
img

一个人可以走的很快,但一群人才能走的更远。不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎扫码加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

…(img-WxuiLuY1-1712976542559)]
[外链图片转存中…(img-ksRfFqo0-1712976542560)]
[外链图片转存中…(img-ysaXHwmp-1712976542560)]
[外链图片转存中…(img-lpf5LMXA-1712976542560)]

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上大数据开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新

如果你觉得这些内容对你有帮助,可以添加VX:vip204888 (备注大数据获取)
[外链图片转存中…(img-CPnNQyEC-1712976542560)]

一个人可以走的很快,但一群人才能走的更远。不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎扫码加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值