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


有状态特征是非常重要的一类特征,其中最常用的就是带有各种窗口的特征,例如统计最近 5 分钟视频的播放 VV 等 。对于窗口类型的特征在字节内部有一些基于存储引擎的方案,整体思路是“ 轻离线重在线”,即把窗口状态存储、特征聚合计算全部放在存储层和在线完成 。离线数据流负责基本数据过滤和写入,离线明细数据按照时间切分聚合存储(类似于 micro batch),底层的存储大部分是 KV 存储、或者专门优化的存储引擎,在线层完成复杂的窗口聚合计算逻辑,每个请求来了之后在线层拉取存储层的明细数据做聚合计算 。
我们新的解决思路是“ 轻在线重离线”,即把比较重的 时间切片明细数据状态存储和窗口聚合计算全部放在离线层 。窗口结果聚合通过 离线窗口触发机制完成,把特征结果 推到在线 KV 存储 。在线模块非常轻量级,只负责简单的在线 serving,极大地简化了在线层的架构复杂度 。在离线状态存储层 。我们主要依赖 Flink 提供的 原生状态存储引擎 RocksDB,充分利用离线计算集群本地的 SSD 磁盘资源,极大减轻在线 KV 存储的资源压力 。
对于长窗口的特征(7 天以上窗口特征),由于涉及 Flink 状态层明细数据的回溯过程,Flink Embedded 状态存储引擎没有提供特别好的外部数据回灌机制(或者说不适合做) 。因此对于这种“ 状态冷启动”场景,我们引入了中心化存储作为底层状态存储层的存储介质,整体是 Hybrid架构 。例如 7 天以内的状态存储在本地 SSD,7~30 天状态存储到中心化的存储引擎,离线数据回溯可以非常方便的写入中心化存储 。
除窗口特征外,这套机制同样适用于其他类型的有状态特征(如序列类型的特征) 。
实时特征分类体系

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

文章插图
整体架构
5年迭代5次,抖音推荐系统演进历程

文章插图
带有窗口的特征,例如抖音视频最近 1h 的点赞量(滑动窗口)、直播间用户最近一个 session 的看播时长(session 窗口)等;
数据源层
在新的一体化特征架构中,我们统一把各种类型数据源抽象为 Schema Table,这是因为底层依赖的 Flink SQL 计算引擎层对数据源提供了非常友好的 Table Format 抽象 。在推荐场景,依赖的数据源非常多样,每个特征上游依赖一个或者多个数据源 。数据源可以是 Kafka、RMQ、KV 存储、RPC 服务 。对于多个数据源,支持数据源流式、批式拼接,拼接类型包括 Window Join 和基于 key 粒度的 Window Union Join,维表 Join 支持 Abase、RPC、HIVE 等 。具体每种类型的拼接逻辑如下:
5年迭代5次,抖音推荐系统演进历程

文章插图
三种类型的 Join 和 Union 可以组合使用,实现复杂的多数据流拼接 。例如 (A union B) Window Join (C Lookup Join D) 。
5年迭代5次,抖音推荐系统演进历程

文章插图
另外,Flink SQL 支持复杂字段的计算能力,也就是业务方可以基于数据源定义的 TableSchema 基础字段实现扩展字段的计算 。业务计算逻辑本质是一个 UDF,我们会提供 UDF API 接口给业务方,然后上传 JAR 到特征后台加载 。另外对于比较简单的计算逻辑,后台也支持通过提交简单的 Python 代码实现多语言计算 。
业务 DSL
从业务视角提供高度抽象的特征生产 DSL 语言,屏蔽底层计算、存储引擎细节,让业务方聚焦于业务特征定义 。业务 DSL 层提供:数据来源、数据格式、数据抽取逻辑、数据生成特征类型、数据输出方式等 。
5年迭代5次,抖音推荐系统演进历程

文章插图
状态存储层
5年迭代5次,抖音推荐系统演进历程

文章插图
如上文所述,新的特征一体化方案解决的主要痛点是:如何应对各种类型(一般是滑动窗口)有状态特征的计算问题 。对于这类特征,在离线计算层架构里会有一个状态存储层,把抽取层提取的 RawFeature 按照切片 Slot 存储起来 (切片可以是时间切片、也可以是 Session 切片等) 。切片类型在内部是一个接口类型,在架构上可以根据业务需求自行扩展 。状态里面其实存储的不是原始 RawFeature(存储原始的行为数据太浪费存储空间),而是转化为 FeaturePayload 的一种 POJO 结构,这个结构里面支持了常见的各种数据结构类型: