5年迭代5次,抖音推荐系统演进历程( 四 )

业务方使用 State 的场景多是 get-update,在使用 RocksDB 作为本地状态存储的过程中,出现过以下问题:

  1. 爬虫数据导致热 key,状态会不断进行更新 (get-update),单 KV 数据达到 5MB,而 RocksDB 追加更新的特点导致后台在不断进行 flush 和 compaction,单 task 出现慢节点(抖音直播场景) 。
  2. 电商场景作业多数为大状态作业 (目前已上线作业状态约 60TB),业务逻辑中会频繁进行 State 操作 。在融合 Flink State 过程中发现 CPU 的开销和原有~~ 的~~ 基于内存或 abase 的实现有 40%~80% 的升高 。经优化后,CPU 开销主要集中在序列化 / 反序列化的过程中 。
针对上述问题,可以通过在内存维护一个对象 Cache,达到优化热点数据访问和降低 CPU 开销的目的 。通过上述背景介绍,我们希望能为 StateBackend 提供一个通用的 Cache 功能,通过 Flink StateBackend Cache 功能设计方案达成以下目标:
  1. 减少 CPU 开销 : 通过对热点数据进行缓存,减少和底层 StateBackend 的交互次数,达到减少序列化 / 反序列化开销的目的 。
  2. 提升 State 吞吐能力 : 通过增加 Cache 后,State 吞吐能力应比原有的 StateBackend 提供的吞吐能力更高 。理论上在 Cache 足够大的情况下,吞吐能力应和基于 Heap 的 StateBackend 近似 。
  3. Cache 功能通用化 : 不同的 StateBackend 可以直接适配该 Cache 功能 。目前我们主要支持 RocksDB,未来希望可以直接提供给别的 StateBackend 使用,例如 RemoteStateBackend 。
经过和字节基础架构 Flink 团队的合作,在实时特征生产升级 ,上线 Cache 大部分场景的 CPU 使用率大概会有高达 50% 左右的收益;
PB IDL 裁剪
在字节内部的实时特征离线生成链路当中,我们主要依赖的数据流是 Kafka 。这些 Kafka 都是通过 PB 定义的数据,字段繁多 。公司级别的大 Topic 一般会有 100+ 的字段,但大部分的特征生产任务只使用了其中的部分字段 。对于 Protobuf 格式的数据源,我们可以完全通过裁剪数据流,mask 一些非必要的字段来节省反序列化的开销 。PB 类型的日志,可以直接裁剪 idl,保持必要字段的序号不变,在反序列化的时候会跳过 unknown field 的解析,这 对于 CPU 来说是更节省的,但是网络带宽不会有收益,预计裁剪后能节省非常多的 CPU 资源 。在上线了 PB IDL 裁剪之后, 大部分任务的 CPU 收益在 30% 左右 。
遇到的问题
新架构特征生产任务本质就是一个有状态的 Flink 任务,底层的状态存储 StateBackend 主要是本地的 RocksDB 。主要面临两个比较难解的问题,一是任务 DAG 变化 Checkpoint 失效,二是本地存储不能很好地支持特征状态历史数据回溯 。
  • 实时特征任务不能动态添加新的特征:对于一个线上的 Flink 实时特征生产任务,我们不能随意添加新的特征 。这是由于引入新的特征会导致 Flink 任务计算的 DAG 发生改变,从而导致 Flink 任务的 Checkpoint 无法恢复,这对实时有状态特征生产任务来说是不能接受的 。目前我们的解法是禁止更改线上部署的特征任务配置,但这也就导致了线上生成的特征是不能随便下线的 。对于这个问题暂时没有找到更好的解决办法,后期仍需不断探索 。
  • 特征状态冷启动问题:目前主要的状态存储引擎是 RocksDB,不能很好地支持状态数据的回溯 。后续规划
当前新一代架构还在字节推荐场景中快速演进,目前已较好解决了实时窗口特征的生产问题 。
出于实现统一推荐场景下特征生产的目的,我们后续会继续基于 Flink SQL 流批一体能力,在批式特征生产发力 。此外也会基于 Hudi 数据湖技术,完成特征的实时入湖,高效支持模型训练场景离线特征回溯痛点 。规则引擎方向,计划继续探索 CEP,推动在电商场景有更多落地实践 。在实时窗口计算方向,将继续深入调研 Flink 原生窗口机制,以期解决目前方案面临的窗口特征数据退场问题 。