对付今日头条、抖音、西瓜视频等字节跳动旗下产品,基于 Feed 流和短时效的推举是核心业务场景。而推举系统最根本的燃料是特色,高效生产根本特色对业务推举系统的迭代至关主要。
紧张业务场景抖音、火山短视频等为代表的短视频运用推举场景,例如 Feed 流推举、关注、社交、同城等各个场景,整体在海内大概有 6 亿+规模 DAU;头条、西瓜等为代表的 Feed 信息流推举场景,例如 Feed 流、关注、子频道等各个场景,整体在海内数亿规模 DAU;业务痛点和寻衅目前字节跳动推举场景根本特色的生产现状是“百花齐放”。离线特色打算的基本模式都是通过消费 Kafka、BMQ、Hive、HDFS、Abase、RPC 等数据源,基于 Spark、Flink 打算引擎实现特色的打算,而后把特色的结果写入在线、离线存储。各种不同类型的根本特色打算散落在不同的做事中,缺少业务抽象,带来了较大的运维本钱和稳定性问题。
而更主要的是,缺少统一的根本特色生产平台,使业务特色开拓迭代速率和掩护存在诸多不便。如业务方需自行掩护大量离线任务、特色生产链路缺少监控、无法知足不断发展的业务需求等。
在字节的业务规模下,构建统一的实时特色生产系统面临着较大寻衅,紧张来自四个方面:
巨大的业务规模:抖音、头条、西瓜、火山等产品的数据规模可达到日均 PB 级别。例如在抖音场景下,晚高峰 Feed 播放量达数百万 QPS,客户端上报用户行为数据高达数千万 IOPS。业务方期望在任何时候,特色任务都可以做到不断流、消费没有 lag 等,这就哀求特色生产具备非常高的稳定性。
较高的特色实时化哀求:在以直播、电商、短视频为代表的推举场景下,为担保推举效果,实时特色离线生产的时效性需实现常态稳定于分钟级别。
更好的扩展性和灵巧性:随着业务场景不断繁芜,特色需求更为灵巧多变。从统计、序列、属性类型的特色生产,到须要灵巧支持窗口特色、多维特色等,业务方须要特色中台能够支持逐渐衍生而来的新特色类型和需求。
业务迭代速率快:特色中台供应的面向业务的 DSL 须要足够场景,特色生产链路只管即便让业务少写代码,底层的打算引擎、存储引擎对业务完备透明,彻底开释业务打算、存储选型、调优的包袱,彻底实现实时根本特色的规模化生产,不断提升特色生产力;
迭代演进过程在字节业务爆发式增长的过程中,为了知足各式各样的业务特色的需求,推举场景衍生出了浩瀚特色做事。这些做事在特定的业务场景和历史条件下较好支持了业务快速发展,大体的进程如下:
推举场景特色做事演进进程
在这个中 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 天状态存储到中央化的存储引擎,离线数据回溯可以非常方便的写入中央化存储。
除窗口特色外,这套机制同样适用于其他类型的有状态特色(如序列类型的特色)。
实时特色分类体系特色类型
定义
特色举例
有状态特色
有状态特色是一类非常主要的特色,我们对有状态特色的定义是:打算特色须要缓存高下文数据。
带有窗口的特色,例如抖音视频最近1h的点赞量(滑动窗口)、直播间用户最近一个 session 的看播时长(session 窗口)等;序列特色,例如最近100个推举展现视频。无状态特色
大略的 ETL 特色,通过大略的数据过滤可以打算的特色。
模型预估特色
须要经由外部繁芜模型预估的特色
用户的年事、性别等特色。
图特色
在直播和社交关系场景存在比较多的须要二跳关系的图类型的特色。
很多图特色同时也是有状态类型的特色。
礼物排序:用户不雅观看最多的主播收到最多的礼物,首选须要找到用户不雅观看最多的主播 ArchorId,然后通过 archon_id 获取到主播收到最多的礼物 id;社交关系:好友(可能是挖掘出来的关系)关注、看播、送礼、连麦的房间,社交关系天然是图数据构造。整体架构数据源层在新的一体化特色架构中,我们统一把各种类型数据源抽象为 Schema Table,这是由于底层依赖的 Flink SQL 打算引擎层对数据源供应了非常友好的 Table Format 抽象。在推举场景,依赖的数据源非常多样,每个特色上游依赖一个或者多个数据源。数据源可以是 Kafka、RMQ、KV 存储、RPC 做事。对付多个数据源,支持数据源流式、批式拼接,拼接类型包括 Window Join 和基于 key 粒度的 Window Union Join,维表 Join 支持 Abase、RPC、HIVE 等。详细每种类型的拼接逻辑如下:
数据源类型
Schema 解析
Kafka、BMQ
Kafka、BMQ 等 message 类型基本都是 JSON 和 PB,是自描述的数据类型。可以非常方便地映射成 SchemaTable 格式,个中对付 PB 类型,业务须要上传 PB IDL 完成 Table Schema 定义。
KV存储
KV 存储里的 Value 大部分为 JSON、PB 格式,和 MQ 类似。业务方通过供应 PB IDL 完成 Table Schema 定义。我们通过 FlinkSQL 的维表 Join 能力,把普通的获取外部存储数据源过程抽象为基本的维表 Join 操作,简化业务开拓周期。
RPC
FlinkSQL 供应了对 RPC 维表的 Join 能力,业务供应 RPC Thrift IDL 完全 rpc response Table Schema 定义。通过维表 Join,我们把普通的通过 RPC 获取外部数据源的过程抽象为了基本维表 Join 模型,简化业务开拓周期。
Hive
Hive 本身便是 SchemaTable 的存储格式,对付在线 Join 数据量较小的离线 Hive 数据(实在便是 MapSide Join),可通过 Hive 维表 Join 实现。
三种类型的 Join 和 Union 可以组合利用,实现繁芜的多数据流拼接。例如(A union B) Window Join (C Lookup Join D)。
拼接类型
拼接逻辑
备注
Window Join
利用 Flink 原生 API 供应的 Join 算子,把多个数据流落入相同窗口的数据 Join 起来。
直接在原始数据流上运用 TumblingWindow 进行切分,根据event_time 或 process_time 对齐两个窗口后再关联数据。
基于 Key 粒度的 Interval State Join
和样本拼接逻辑类似。通过 Union 上游多个数据源,在每个关联主键上面注册 timer,等待一个固定的韶光窗口完成多数据源的 Join 操作。
Interval State Join 是利用 State 存储数据再处理。上游两个数据流经由 Union 后,同一个 uid 的 instance 数据和 label 数据落在同一个 operator 内,Joiner 中正负例样本的产生便是通过这种 Join 办法。
Lookup 维表 Join
通过关联主键,从 Abase、RPC、Hive 等做事查看须要关联的数据,完成数据的 Join 操作。
多数据源 Union
多数据源 Union 起来
其余,Flink SQL 支持繁芜字段的打算能力,也便是业务方可以基于数据源定义的 TableSchema 根本字段实现扩展字段的打算。业务打算逻辑实质是一个 UDF,我们会供应 UDF API 接口给业务方,然后上传 JAR 到特色后台加载。其余对付比较大略的打算逻辑,后台也支持通过提交大略的 Python 代码实现多措辞打算。
业务 DSL从业务视角供应高度抽象的特色生产 DSL 措辞,屏蔽底层打算、存储引擎细节,让业务方聚焦于业务特色定义。业务 DSL 层供应:数据来源、数据格式、数据抽取逻辑、数据天生特色类型、数据输出办法等。
状态存储层
如上文所述,新的特色一体化方案办理的紧张痛点是:如何应对各种类型(一样平常是滑动窗口)有状态特色的打算问题。对付这类特色,在离线打算层架构里会有一个状态存储层,把抽取层提取的 RawFeature 按照切片 Slot 存储起来(切片可以是韶光切片、也可以是 Session 切片等)。切片类型在内部是一个接口类型,在架构上可以根据业务需求自行扩展。状态里面实在存储的不是原始 RawFeature(存储原始的行为数据太摧残浪费蹂躏存储空间),而是转化为 FeaturePayload 的一种 POJO 构造,这个构造里面支持了常见的各种数据构造类型:
Int:存储大略的计数值类型(多维度 counter);HashMap:存储二维计数值,例如 Action Counter,key 为 target_id,value 为计数值;SortedMap: 存储 topk 二维计数 ;LinkedList: 存储 id_list 类型数据;HashMap>:存储二维 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);}
复制代码
无状态特色打算接口
其余通过触发机制来触发特色打算层的实行,目前支持的触发机制紧张有:
策略
阐明
OnTimerTrigger
周期性定时触发特色的打算逻辑
OnUpdateTrigger
上游状态层每次更新即触发特色打算
CustomTrigger
自定义特色打算的触发机遇
业务落地目前在字节推举场景,新一代特色架构已经在抖音直播、电商、推送、抖音推举等场景陆续上线了一些实时特色。紧张是有状态类型的特色,带有窗口的一维统计类型、二维倒排拉链类型、二维 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 的形式组织数据,在实际利用的过程中,有以下特点:
运用层和 RocksDB 的数据交互因此 Bytes 数组的形式进行,运用层每次访问都须要序列化/反序列化;数据以追加的形式不断写入 RocksDB 中,RocksDB 后台会不断进行 compaction 来删除无效数据。业务方利用 State 的场景多是 get-update,在利用 RocksDB 作为本地状态存储的过程中,涌现过以下问题:
爬虫数据导致热 key,状态会不断进行更新(get-update),单 KV 数据达到 5MB,而 RocksDB 追加更新的特点导致后台在不断进行 flush 和 compaction,单 task 涌现慢节点(抖音直播场景)。电阛阓景作业多数为大状态作业(目前已上线作业状态约 60TB),业务逻辑中会频繁进行 State 操作。在领悟 Flink State 过程中创造 CPU 的开销和原有的基于内存或 abase 的实现有 40%~80%的升高。经优化后,CPU 开销紧张集中在序列化/反序列化的过程中。针对上述问题,可以通过在内存掩护一个工具 Cache,达到优化热点数据访问和降落 CPU 开销的目的。通过上述背景先容,我们希望能为 StateBackend 供应一个通用的 Cache 功能,通过 Flink StateBackend Cache 功能设计方案达成以下目标:
减少 CPU 开销:通过对热点数据进行缓存,减少和底层 StateBackend 的交互次数,达到减少序列化/反序列化开销的目的。提升 State 吞吐能力:通过增加 Cache 后,State 吞吐能力应比原有的 StateBackend 供应的吞吐能力更高。理论上在 Cache 足够大的情形下,吞吐能力应和基于 Heap 的 StateBackend 近似。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 语义,统一管理其他外部静态类型的特色做事。例如一些其他业务团队维度的用户分类、标签做事等。作者先容:
郭文飞,字节跳动推举系统根本做事方向卖力人。2015 年初加入字节,紧张卖力推举系统根本做事方向,例如消重、计数、特色等。
字节跳动推举架构团队实时打算方向,卖力抖音、今日头条、西瓜视频等超 10 亿用户产品推举系统架构实时打算系统的设计和开拓,保障系统稳定和高可用。抽象通用实时打算系统、构建统一的推举特色中台,实现灵巧可扩展的高性能存储系统和打算模型,为推举业务实现前辈的消重、计数、特色做事等实时推举数据流系统。目前非常缺人,欢迎对技能有追求的同学加入,一起构建天下级前辈的实时推举数据流系统,联系办法:guowenfei@bytedance.com。