2021 年,字节跳动旗下产品总 MAU 已超过 19 亿。在以抖音、今天头条、西瓜视频等为代表的产品事务布景下,强壮的引荐系统显得尤为重要。Flink 供给了十分强壮的 SQL 模块和有状况核算模块。现在在字节引荐场景,实时简略计数特征、窗口计数特征、序列特征现已彻底迁移到 Flink SQL 计划上。结合 Flink SQL 和 Flink 有状况核算才能,咱们正在构建下一代通用的根底特征核算一致架构,期望能够高效支撑常用有状况、无状况根底特征的出产。

事务布景

关于今天头条、抖音、西瓜视频等字节跳动旗下产品,依据 Feed 流和短时效的引荐是中心事务场景。而引荐系统最根底的燃料是特征,高效出产根底特征对事务引荐系统的迭代至关重要。

首要事务场景

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

  • 抖音、火山短视频等为代表的短视频应用引荐场景,例如 Feed 流引荐、重视、社交、同城等各个场景,全体在国内大约有 6 亿 + 规划 DAU;
  • 头条、西瓜等为代表的 Feed 信息流引荐场景,例如 Feed 流、重视、子频道等各个场景,全体在国内有数亿规划 DAU;

事务痛点和应战

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

现在字节跳动引荐场景根底特征的出产现状是“百家争鸣”。离线特征核算的根本形式都是经过消费 Kafka、BMQ、Hive、HDFS、Abase、RPC 等数据源,依据 Spark、Flink 核算引擎完结特征的核算,然后把特征的成果写入在线、离线存储。各种不同类型的根底特征核算散落在不同的服务中,缺少事务笼统,带来了较大的运维本钱和安稳性问题。

而更重要的是,缺少一致的根底特征出产渠道,使事务特征开发迭代速度和保护存在诸多不便。如事务方需自行保护很多离线使命、特征出产链路缺少监控、无法满意不断开展的事务需求等。

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

在字节的事务规划下,构建一致的实时特征出产系统面对着较大应战,首要来自四个方面:

巨大的事务规划:抖音、头条、西瓜、火山等产品的数据规划可到达日均 PB 等级。例如在抖音场景下,晚高峰 Feed 播放量达数百万 QPS,客户端上报用户行为数据高达数千万 IOPS。 事务方期望在任何时分,特征使命都能够做到不断流、消费没有 Lag 等,这就要求特征出产具备十分高的安稳性。

较高的特征实时化要求:在以直播、电商、短视频为代表的引荐场景下,为保证引荐作用,实时特征离线出产的时效性需完结常态安稳于分钟等级。

更好的扩展性和灵敏性:随着事务场景不断杂乱,特征需求更为灵敏多变。从核算、序列、特点类型的特征出产,到需求灵敏支撑窗口特征、多维特征等,事务方需求特征中台能够支撑逐步衍生而来的新特征类型和需求。

事务迭代速度快:特征中台供给的面向事务的 DSL 需求满意场景,特征出产链路尽量让事务少写代码,底层的核算引擎、存储引擎对事务彻底透明,彻底释放事务核算、存储选型、调优的担负,彻底完结实时根底特征的规划化出产,不断提高特征出产力;

迭代演进进程

在字节事务爆发式增长的进程中,为了满意林林总总的事务特征的需求,引荐场景衍生出了众多特征服务。这些服务在特定的事务场景和前史条件下较好支撑了事务快速开展,大体的进程如下:

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

引荐场景特征服务演进进程

在这其间 2020 年初是一个重要节点,咱们开始在特征出产中引进 Flink SQL、Flink State 技术系统,逐步在计数特征系统、模型训练的样本拼接、窗口特征等场景进行落地,探究出新一代特征出产计划的思路。

新一代系统架构

结合上述事务布景,咱们依据 Flink SQL 和 Flink 有状况核算才能从头规划了新一代实时特征核算计划。新计划的定位是:处理根底特征的核算和在线 Serving,供给愈加笼统的根底特征事务层 DSL 在核算层,咱们依据 Flink SQL 灵敏的数据处理表达才能,以及 Flink State 状况存储和核算才能等技术,支撑各种杂乱的窗口核算。极大地缩短事务根底特征的出产周期,提高特征产出链路的安稳性。新的架构里,咱们将 特征出产的链路分为数据源抽取 / 拼接、状况存储、核算三个阶段,Flink SQL 完结特征数据的抽取和流式拼接,Flink State 完结特征核算的中间状况存储。

有状况特征是十分重要的一类特征,其间最常用的便是带有各种窗口的特征,例如核算最近 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 结构,这个结构里边支撑了常见的各种数据结构类型:

  • Int:存储简略的计数值类型 (多维度 counter);
  • HashMap<int, int>:存储二维计数值,例如 Action Counter,key 为 target_id,value 为计数值;
  • SortedMap<int, int>: 存储 topk 二维计数 ;
  • LinkedList
  • :存储 id_list 类型数据;
  • HashMap<int, List
  • :存储二维 id_list;
  • 自界说类型,事务能够依据需求 FeaturePayload 里边自界说数据类型

状况层更新的事务接口:输入是 SQL 抽取 / 拼接层抽取出来的 RawFeature,事务方能够依据事务需求完结 UpdateFeatureInfo 接口对状况层的更新。关于常用的特征类型内置完结了 Update 接口,事务方自界说特征类型能够继承 Update 接口完结。

 /**
*  特征状况 update 接口
 */
public interface FeatureStateApi extends Serializable {
    /**
    * 特征更新接口, 上游每条日志会提取必要字段转化为 fields, 用来更新对应的特征状况
    *
    * @param fields
    *      context: 保存特征名称、主键 和 一些装备参数 ;
    *      oldFeature: 特征之前的状况
    *      fields: 渠道 / 装备文件 中的抽取字段
    * @return 
    */
FeaturePayLoad assign(Context context,FeaturePayLoad feature, Map<String, Object> rawFeature);
}

当然关于无状况的 ETL 特征是不需求状况存储层的。

核算层

特征核算层完结特征核算聚合逻辑,有状况特征核算输入的数据是状况存储层存储的带有切片的 FeaturePayload 方针。简略的 ETL 特征没有状况存储层,输入直接是 SQL 抽取层的数据 RawFeature 方针,具体的接口如下:

 /**
*  有状况特征核算接口
 */
public interface FeatureStateApi extends Serializable {
    /**
     * 特征聚合接口,会依据装备的特征核算窗口, 读取窗口内一切特征状况,排序后传入该接口
     *
     * @param featureInfos, 包含 2 个 field
     *      timeslot: 特征状况对应的时刻槽
     *      Feature: 该时刻槽的特征状况
     * @return
     */    
    FeaturePayLoad aggregate(Context context, List<Tuple2<Slot, FeaturePayLoad>> slotStates);
}

有状况特征聚合接口

 /**
*  无状况特征核算接口
 */
public interface FeatureConvertApi extends Serializable {
    /**
    * 转化接口, 上游每条日志会提取必要字段转化为 fields, 无状况核算时,转化为内部的 feature 类型 ;
     *
     * @param fields
     *      fields: 渠道 / 装备文件 中的抽取字段
     * @return
     */
     FeaturePayLoad convert(Context context,  FeaturePayLoad featureSnapshot, Map<String, Object> rawFeatures);
}

无状况特征核算接口

别的经过触发机制来触发特征核算层的履行,现在支撑的触发机制首要有:

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

事务落地

现在在字节引荐场景,新一代特征架构现已在抖音直播、电商、推送、抖音引荐等场景连续上线了一些实时特征。首要是有状况类型的特征,带有窗口的一维核算类型、二维倒排拉链类型、二维 TOPK 类型、实时 CTR/CVR Rate 类型特征、序列类型特征等。

在事务中心方针到达方面成效显着。在直播场景,依托新特征架构强壮的表达才能上线了一批特征之后,事务看播中心方针、互动方针收益十分显着。在电商场景,依据新特征架构上线了 400+ 实时特征。其间在直播电商方面,事务中心 GMV、下单率方针收益显着。在抖音推送场景,依据新特征架构离线状况的存储才能,聚合用户行为数据然后写入下流各路存储,极大地缓解了事务下流数据库的压力,在一些场景中 QPS 能够下降到之前的 10% 左右。此外,抖音引荐 Feed、谈论等事务都在依据新特征架构重构原有的特征系统。

值得一提的是,在电商和抖音直播场景,Flink 流式使命状况最大现已到达 60T,并且这个量级还在不断增大。估计不久的将来,单使命的状况有可能会突破 100T,这对架构的安稳性是一个不小的应战。

功用优化

Flink State Cache

现在 Flink 供给两类 StateBackend:依据 Heap 的 FileSystemStateBackend 和依据 RocksDB 的 RocksDBStateBackend。关于 FileSystemStateBackend,因为数据都在内存中,拜访速率很快,没有额定开支。而 RocksDBStateBackend 存在查盘、序列化 / 反序列化等额定开支,CPU 运用量会有显着上升。在字节内部有很多运用 State 的作业,关于大状况作业,通常会运用 RocksDBStateBackend 来办理本地状况数据。RocksDB 是一个 KV 数据库,以 LSM 的方式安排数据,在实际运用的进程中,有以下特色:

  1. 应用层和 RocksDB 的数据交互是以 Bytes 数组的方式进行,应用层每次拜访都需求序列化 / 反序列化;
  1. 数据以追加的方式不断写入 RocksDB 中,RocksDB 后台会不断进行 Compaction 来删除无效数据。

事务方运用 State 的场景多是 Get-Update,在运用 RocksDB 作为本地状况存储的进程中,呈现过以下问题:

  1. 爬虫数据导致热 key,状况会不断进行更新 (Get-Update),单 KV 数据到达 5MB,而 RocksDB 追加更新的特色导致后台在不断进行 Flush 和 Compaction,单 Task 呈现慢节点(抖音直播场景)。
  1. 电商场景作业多数为大状况作业 (现在已上线作业状况约 60TB),事务逻辑中会频频进行 State 操作。在融合 Flink State 进程中发现 CPU 的开支和原有的依据内存或 abase 的完结有 40%~80% 的升高。经优化后,CPU 开支首要会集在序列化 / 反序列化的进程中。

针对上述问题,能够经过在内存保护一个方针 Cache,到达优化热点数据拜访和下降 CPU 开支的意图。经过上述布景介绍,咱们期望能为 StateBackend 供给一个通用的 Cache 功用,经过 Flink StateBackend Cache 功用规划计划到达以下方针:

  1. 削减 CPU 开支 : 经过对热点数据进行缓存,削减和底层 StateBackend 的交互次数,到达削减序列化 / 反序列化开支的意图。
  1. 提高 State 吞吐才能 : 经过增加 Cache 后,State 吞吐才能应比原有的 StateBackend 供给的吞吐才能更高。理论上在 Cache 满意大的情况下,吞吐才能应和依据 Heap 的 StateBackend 近似。
  1. 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 原生窗口机制,以期处理现在计划面对的窗口特征数据退场问题。

  • 支撑批式特征:这套特征出产计划首要是处理实时有状况特征的问题,而现在字节离线场景下还有很多批式特征是经过 Spark SQL 使命出产的。后续咱们也会依据 Flink SQL 流批一体的核算才能,供给对批式场景特征的一致支撑,现在也开始有了几个场景的落地;
  • 特征离线入湖:依据 Hudi On Flink 支撑实时特征的离线数仓建设,首要是为了支撑模型训练样本拼接场景离线特征回溯;
  • Flink CEP 规矩引擎支撑:Flink SQL 本质上便是一种规矩引擎,现在在线上咱们把 Flink SQL 作为事务 DSL 过滤语义底层的履行引擎。但 Flink SQL 拿手表达的 ETL 类型的过滤规矩,不能表达带有时序类型的规矩语义。在直播、电商场景的时序规矩需求尝试 Flink CEP 愈加杂乱的规矩引擎。
  • Flink Native Windowing 机制引进:关于窗口类型的有状况特征,咱们现在选用上文所述的笼统 SlotState 时刻切片计划一致进行支撑。别的 Flink 本身供给了十分完善的窗口机制,经过 Window Assigner、Window Trigger 等组件能够十分灵敏地支撑各种窗口语义。因而后续咱们也会在窗口特征核算场景引进 Flink 原生的 Windowing 机制,愈加灵敏地支撑窗口特征迭代。
  • Flink HybridState Backend 架构:现在在字节的线上场景中,Flink 底层的 StateBackend 默许都是运用 RocksDB 存储引擎。这种内嵌的存储引擎不能经过外部机制去供给状况数据的回灌和多使命共享,因而咱们需求支撑 KV 中心化存储计划,完结灵敏的特征状况回溯。
  • 静态特点类型特征一致办理:经过特征渠道供给一致的 DSL 语义,一致办理其他外部静态类型的特征服务。例如一些其他事务团队维度的用户分类、标签服务等。

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

火山引擎流式核算 Flink 版正在公测中,支撑云中立形式,支撑公有云、混合云及多云部署,全面贴合企业上云策略。欢迎申请试用:

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

扫描二维码,了解更多 Flink 试用信息