一、看点信息流
在 QQ 浏览器的主页可以看到腾讯看点的信息流,信息流有三种形态:小视频、短视频、图文,属于业界信息流最主要的形态。目前浏览器用户破亿,点击曝光等相关流水每日有百亿左右,机器数接近万级。
二、看点信息流基本架构和挑战
信息流已经发展很多年了,架构层面都是大同小异。一个信息流系统分为最底部的数据层,包括了倒排/特征/用户模型;数据层上面是召回,召回有隐式召回、显式召回等。
显式召回会根据兴趣点、主题运行,隐式召回是根据相似度召回,也有基于图像的召回,如 UCF、ICF 和 RNN 的召回。召回的品类非常丰富,它们构成了整个召回层。
召回数据非常多,在数量较多的情况下需要粗排。粗筛注重性价比,召回十万的量级需要进行筛选再传递到精排,再进行筛选。精排特征更丰富、算法模型更复杂。
最上面是展控,拿到数据以后要进行多样性处理以及人工干预策略,再返回最后的十几条结果给用户。
另外一条数据流就是,用户的点击和曝光数据流上传到后台,计算出物品特征,刻画用户画像。
从架构层面看,做什么事情对推荐系统效果有提升呢?首先是特征系统的实时性。因为推荐系统在选择时,是基于内容之间进行 PK,PK 非常重要的一点是内容的特征实时生成,就像一个人的代谢越快就越健康。
比如,一条内容的 CTR 一分钟之内就得到反馈,和一个小时之后才反馈有着极大差别,这是对实时性的挑战。
再举一个用户模型例子,在信息流中进行浏览,不点击与亚马逊有关的文章,这一次不点击的行为在下一次刷新就可以反馈出来,这是对用户模型的要求,是大型信息流的标配。
内容服务和索引服务指在网上出现突发事件,新文章进入平台时,把内容入库。这里有一系列的流程,比如人工审核、NLP 打分、排重等等处理后,进入倒排能够被召回,进而线上进行曝光,这就是内容入库的实时性。
关于计算力,看点信息流的内容池是千万级别的,现在召回层能够做到十万级,平台能把召回做到万级、精排做到千级,扩大召回计算量也可以提升系统的效果。
计算力首先是大家都通用的办法,那怎么样去并行计算?十万条的结果可以拆成十个一万条进行运算,但需要想一个问题,是不是并行度越高越好?其实不是的,要注意大扇出的问题。
传统后台的优化要求,是看代码、架构上如何进行优化,也就是性能优化。而另一块容易被忽略的,是如何做用户内容曝光过滤,这个功能看似简单,实行起来却容易碰到不少问题,下文也会详细的介绍。
怎么样提升开发效率?这个概念比较泛,首先有两点,一是算法跟架构如何分离,算法只负责训练模型、设计算法、设计特征,代码由架构来写,现在业界没有团队可以做得到。
推荐系统召回有一百多条线路的召回,有这么多人负责这么多召回吗?这也是不可能完成的事情,一百个召回如何提高开发效率也是重点需要解决的问题。
三、特征系统
对于推荐系统,特征越多算法效果越好,比如 CTR 包括全局 CTR、策略 CTR 等,分有有很多的维度。
其次是特征系统的线上挑战非常大,以我们系统为例,每秒钟的峰值达到千亿个特征,换算成流量每秒能达到 TB 级。特征实时生成,特征 CTR 在用户点击曝光后可以很快反馈到线上物品特征中,做到高并发、高吞吐情况下秒级返回。
这里还牵扯到命中率,特征系统设计的方案较多是用多级缓存、文件行同步的。而我们方案比较特别,各个服务在使用特征时,通过 API、lib 库的方式调用,只需要查就可以了,特征数据已经通过后端的特征计算好,push 到各个机器上面。
另外一个问题就是,这样做对内存的要求特别高。我们有着千万级内容,单机存不下全部内容的特征,所以需要做优化。
因为对物品的计算是分片式,所以特征也是可以做到分片式的,这样单机对内存的消耗就可以控制;第二是这样做可以减少很多的跨网络调用,每秒 TB 级的传输,换成本地查就可以规避掉。
要保证低时延、更新快、命中率高,其实就看各家物品情况取折中方案,怎么样达到一个合理的平衡。
四、用户模型
用户点什么、不点什么,下一刷要反馈出来,怎么做到准和快?
用户画像有三个窗口,一是 T+1 的窗口,今天以前的行为,二是当天的行为,最近点了两百条又是一个窗口,所以需要完整的窗口描述用户的行为。
二是打分运算,用模型进行打分,很多团队是通过人工经验进行拟合的。
三是要考虑用户换机,换了一个手机后用户画像如何迁移过去,也要考虑外部的画像,比如类似 BAT 公司不止一个产品,其他的产品用户画像也可以拿来用,这是画像系统需要考虑的一个地方。
具有挑战的地方首先是长期画像 T+1 的处理。我们每天需要处理百亿级的流水,处理对时间上什么要求呢?第二天零点开始到第二天的五、六点这个区间,用户访问逐步上来了,留给我们时间是 4 到 5 个小时,假如机器够多就能很快处理完,一般处理分几个步骤:
首先是对数据进行预处理,进行不同特征的计算,最关键是要把画像 Base 导入一个存储中去,然后交给业务流程处理。
二是机器够不够多,机器多的话可以直接并行计算,速度很快。而最重要的是一些压缩的应用、业务逻辑角度而言如何进行剪枝,有些用户兴趣点非常多,是不是所有都要,这里需要考虑性价比。
把时间缩短两倍,基本上就可以满足 12 点到 4 点的要求了。画像处理后进行生成是用 LR 模型,用户上一刷的点击反馈得非常快,还会外部流水进行融入,三是实时进行的打分,这是线上的流程。
五、online learning
线上样本如何拼接,模型如何训练和分发?
第一是腾讯内部有个分布式学习框架叫无量,我们团队做的工作是将样本拼接进行优化,最早是通过 hive 小时级拼接,后来通过实时进行拼接,落样本的时候需要进行时间窗的等待。
为什么要这样做?因为用户不一定实时点,样本上报之后会成为负样本,而用户点击后上传会变成正样本。时间窗取多少要具体分析,控制在半小时以内,有些产品用户点击得比较快可以取快一些,这是自己设置的过程。
第二是模型更新,可以实现增量更新的,最早也定为全量更新,后来改成分钟级的定时增量+小时级全量更新,这里面模型是怎么发布到机器上去的?腾讯内部有个容达系统,专门解决文件发布问题。
索引服务 – 高性能 内容入库实时生效 满足多种推荐业务需求
我们在最早做推荐系统的时候也调研过索引,因为它是整个系统里的最底层组件,原因如下:
第一,一个用户有很多兴趣点,两三百个兴趣点很正常,并发量每天会有千亿次的查询,它的速度要非常快。
第二,突然插入内容时,如何快速的在线上生效。之前也跟 ES 组件做对比 ,首先是基于索引长度 ,索引拉链非常长,主题的拉链也非常长,需要有能力支持长链。
第三,需要能支持曝光过滤。因为用户的曝光过滤每层都不能少,如果在最后一层十几条一起做,会让很多已经曝光的内容占了坑,导致整个系统效果变差。所以从最下面的索引层就要开始做曝光历史过滤,这个跟业务耦合比较紧密,是不可避免要做的。
第四,拉链长,必然要做截断,这就牵扯到排序的问题,是其他组件无法支持的。
内容的时效性很重要,内容改变之后线上必然需要多个索引去做多个 AB Test,比如时效性的需求要上线,如何去做自动化的 Test 是比较难的。
我们从自研索引处理,设计上并不复杂,通过 hash 后面挂链表的形式进行处理。考虑到整个物品数量是千万级,基本单机可以扛下。方法是单机多部署,这样的话,线上性能表现也比较好,非常稳定一直没有线上事故。
六、高并行大扇出 – 召回条数扩大的
如何扩大单位条数,基本上大家都会把召回数目进行分片处理,把它分发到不同的进程或者线程处理。
此时关键的地方在于要考虑扇出的问题,并发化程度是否越高越好?不一定,也要考虑整体耗时和最慢响应包、分片和集群的数,并行数越高损失概率越大。
关于大量服务做的数据对比,如上图所示。
七、性能优化 – 减少50%的耗时
性能优化方面,整体性能优化是线上累积时减少了将近 50% 的耗时,目前推荐后端耗时要在 500 毫秒以下,如果不做优化,会飙升到一秒,这样的系统基本处于不可用的状态。
归纳首先是架构层面尽量用分布式并行,尽量做并行计算;二是很多计算可以统一起来打分;三是很多在线计算是可以改进的,每次上来之后用户特征不一样,计算的话可以用近线计算,相关特征不会有变化是它的优化点。
后台架构的功能是 cache 如何提升系统性能,如何缓存一些打分结果,精排结果是不是可以进行缓存等等。
机房部署方面,我们分四地进行异地部署,每地之间、机房之间的延时较高,如何推动运维把机房尽量部署到一个交换机上,这些都是需要不断优化的。
而系统公用组件后面许多无谓的开销,需要把整个调用链缩短。系统里有很多全量的计算,一些很明显的问题、UCF,后面是用了计算方法从全量计算变成 top K 计算,可以极大的提升系统性能。
关于代码级的优化,GCC 一个新版本的升级,大部分是因为内层的优化、C++ 新的特性应用、数据结构的优化。而如何减少日志的无效 IO,协议里面训练化和反训练化的开销如何减少,推荐链路非常长,协议也存在这样问题。
怎样优化代码逻辑?举个例子,在展控层特别多的逻辑情况下,算法同学不一定可以把算法代码写好,最近拿到的一百毫秒优化就是架构同学做的,重构代码后耗时的确下降了。
这里要保证无 diff,如果打分值被改变了,这里引入了一些 Bug,影响了推荐系统的效果,所以我们的代码都要求做无 diff 测试。
八、用户曝光内容过滤
做用户过滤基本都会采用布隆过滤器,一是信息流容忍误杀,概率比较小用户没什么感知;二是基于 hash 可以省内存,速度快,用户历史过滤变成一个库,全链路都可以过滤;三是根据用户的刷新速度动态调整过滤块的大小,不能专门给用户一个块,但块大小等级分布还可以根据动态变化,动态变化更新可以更省内存。
分布式跨地域用户曝光历史线上有什么问题呢?用户历史过滤的场景是:用户上一刷曝光十条,下一刷把十条过滤掉,上一刷和下一刷时间间隔短,我们的用户历史部署在腾讯的 KV 组件中,数据有变化的要先写到主再同步到备。
所以到了晚上流量特别高的时候,机房之间延迟较大,用户两刷间隔短的情况就会出现重复,所以我们会本地就存一俩份解决。
但是推荐系统多地容灾部署,用户有一定概率切换到异地,因为是考虑容灾做的,所以要考虑用户不同机房之间迁移的情况。
通过一致性的 hash+版本号机制保证同步过滤数据一致,通过这样的设计重复率降低了一个数量级,基本上解决了高峰期重复的问题。
推荐系统里有一个特点,上文讲的曝光用户是基于后台曝光,真实的曝光很多内容是后台曝光了,但用户没有看到的。
可能没有看到过的内容反而是质量非常高、打分非常高的内容,如果有办法把这些内容捞回来,重新召回推荐,这肯定对系统提升有帮助,我们也通过真实的曝光修正后台曝光,对点击效果的提升也很大。
提升召回开发率
关于开发效率的提升,算法跟架构的分离目前来看还不是特别理想的状态,线上有特别多的召回,一百多路召回不可能做到每个人都做专门的召回,还要考虑开发效率问题。
于是我们设计了一个默认的召回打分模型,它有几个特点:以前召回的不要自己打分了,全部由 PE 完成,PE 进行召回初筛,物理的离线计算对物品出差结果再做精细化的计算精度更高,对计算结果拆成两个做,把整个计算流程分布式并行计算,能够提升计算量,15 毫秒就可以完成十万量级的文档预测,效果提升也比较明显。
Q&A
Q:用户曝光历史过滤具体是怎么做的?A:原理是布隆过滤器,一般而言是做信息流的标配的方案。布隆过滤器线上如何用,第一点如何设计过滤器更加省内存,二是分布式场景,考虑过滤器的内容如何同步,三是真实曝光修正后台曝光,这样可以提升业务效果 Q:bloomfilter是redis保存的吗?A:对。 Q:看点用户访问是规律的吗?A:是规律的,会有早高峰和晚高峰,早高峰是 9 点,晚上是 8~10 点。 Q:看点用户的并发访问是规律的么,如何处理资源弹性的问题?A:坦白说这是运维同学要做的,我们底层 RPC 通讯框架本身就要支持自动的伸缩,自动伸缩无外乎留一定的阈值上限,自动触发扩容,调用服务无感知这是属于基础组件同学需要考虑的问题。
Q:如何动态扩容过滤器?A:刚开始我们对用户部署多少也不知道,对一个用户的摸索情况,一个用户 1M、2M,用户刷的多,就可以多分配,类似 vector 的内容机制。 Q:索引服务用的是 DCache 吗?A:对。 Q:索引能用es存储吗?效率会不会很低?A:用什么保存都无关,问题是怎么做同步,跟场景,两刷之间非常快,就有这样的问题。 Q:新用户如何推荐?A:用户维度就是去拿额外的数据,内容维度就是新热。 Q:信息流推荐里怎么利用用户搜索画像呢?A:把搜索直接作为一个特征,交给模型去学
Q:索引服务如何做到无锁,并发写怎么解决?A:采用无锁的数据结构,整个索引只有单个写,没有并发写。 Q:召回推荐什么时候触发?A:用户请求主 feed 就会触发 Q:推荐系统的架构如何保证时延要求呢?A:架构层面优化和代码层面的优化。 Q:索引服务使用 golang 写能扛?A:应该是可以的,语言层面的开销差别不会太大,我们线上就已经 2ms,如果差别能差几毫秒呢,应该是影响不大。 Q:有删索引的场景吗?A:当然有,但是只删正排(快照) 不删倒排。 Q:统一的打分服务如何处理不同召回的个性化需求?A:不是所有召回都必须接 PE 召回之间会看 unique 价值来保证个性化需求。