腾讯看点视频推荐索引构建方案

2020 年 11 月 23 日

腾讯看点视频推荐索引构建方案

一、背景


在视频推荐场景中,一方面我们需要让新启用的视频尽可能快的触达用户,这一点对于新闻类的内容尤为关键;另一方面我们需要快速识别新物品的好坏,通过分发的流量,以及对应的后验数据,来判断新物品是否值得继续分发流量。


而这两点对于索引先验数据和后验数据的延迟都有很高的要求。下文将为大家介绍看点视频推荐的索引构建方案,希望和大家一同交流。文章作者:纪文忠,腾讯 QQ 端推荐研发工程师。


注:这里我们把视频创建时就带有的数据称为先验数据,如 tag,作者账号 id 等,而把用户行为反馈的数据称为后验数据,如曝光、点击、播放等。


二、看点视频推荐整体架构



从数据链路来看此架构图,从下往上来看,首先视频内容由内容中心通过消息队列给到我们,经过一定的处理入库、建索引、生成正排/倒排数据,这时候在存储层可召回的内容约有 1 千万条。


然后经过召回层,通过用户画像、点击历史等特征召回出数千条视频,给到粗排层;粗排将这数千条视频打分,取数百条给到精排层;精排再一次打分,给到重排;重排根据一定规则和策略进行打散和干预,最终取 10+条给到用户;


视频在用户侧曝光后,从上之下,是另一条数据链路:用户对视频的行为,如曝光、点击、播放、点赞、评论等经过上报至日志服务,然后通过实时/离线处理产生特征回到存储层,由此形成一个循环。


基于此架构,我们需要设计一套召回/倒排索引,能够以实时/近实时的延迟来处理所有数据。


三、方案设计


在旧方案中,索引是每半小时定时构建的,无法满足近实时的要求。在分析这个索引构建的方案时,我们遇到的主要挑战有:


  • 数据虽不要求强一致性,但需要保证最终一致性;

  • 后验数据写入量极大,看点用户行为每日达到百亿+;

  • 召回系统要求高并发、低延迟、高可用。


1. 业界主流方案调研



通过对比业界主流方案,我们可以看到,基于 Redis 的方案灵活性较差,直接使用比较困难,需要进行较多定制化开发,可以首先排除。


因此我们可选择的方案主要在自研或者选择开源成熟方案。经过研究,我们发现如果自研索引开发成本较高,而简单的自研方案可能无法满足业务需求,完善的自研索引方案所需要的开发成本往往较高,往往需要多人的团队来开发维护,最终我们选择了基于 ES 的索引服务。


至于为什么选择基于 ES,而不是选择基于 Solr,主要是因为 ES 有更成熟的社区,以及有腾讯云 PaaS 服务支持,使用起来更加灵活方便。


2. 数据链路图


(1)方案介绍


如下图所示:



这个方案从数据链路上分为两大块。


第一块,先验数据链路,就是上半部分,我们的数据源主要来自内容中心,通过解析服务写入到 CDB 中。其中这个链路又分为全量链路和增量链路。


全量链路主要是在重建索引时才需要的,触发次数少但也重要。它从 DB 这里 dump 数据,写入 kafka,然后通过写入服务写入 ES。


增量链路是确保其实时性的链路,通过监听 binlog,发送消息至 kafka,写入服务消费 kafka 然后写入 ES。


第二块,是后验数据链路。看点的用户行为流水每天有上百亿,这个量级直接打入 ES 是绝对扛不住的。所以我们需要对此进行聚合计算。


这里使用 Flink 做了 1 分钟滚动窗口的聚合,然后把结果输出到写模块,得到 1 分钟增量的后验数据。在这里,Redis 存储近 7 天的后验数据,写模块消费到增量数据后,需要读出当天的数据,并于增量数据累加后写回 Redis,并发送对应的 rowkey 和后验数据消息给到 Kafka,再经由 ES 写入服务消费、写入 ES 索引。


(2)一致性问题分析


这个数据链路存在 3 个一致性问题需要小心处理:


第一,Redis 写模块这里,需要先读出数据,累加之后再写入。先读后写,需要保证原子性,而这里可能存在同时有其他线程在同一时间写入,造成数据不一致。


解决方案 1 是通过 redis 加锁来完成;解决方案 2 如下图所示,在 kafka 队列中,使用 rowkey 作为分区 key,确保同一 rowkey 分配至同一分区,而同一只能由同一消费者消费,也就是同一 rowkey 由一个进程处理,再接着以 rowkey 作为分线程 key,使用 hash 算法分线程,这样同一 rowkey 就在同一线程内处理,因此解决了此处的一致性问题。另外,通过这种方案,同一流内的一致性问题都可以解决。



第二,还是 Redis 写模块这里,我们知道 Redis 写入是需要先消费 kafka 的消息的,那么这里就要求 kafka 消息 commit 和 redis 写入需要在一个事务内完成,或者说需要保证原子性。


如果这里先 commit 再进行 redis 写入,那么如果系统在 commit 完且写入 redis 前宕机了,那么这条消息将丢失掉;如果先写入,在 commit,那么这里就可能会重复消费。


如何解决这个一致性问题呢?我们通过先写入 redis,并且写入的信息里带上时间戳作为版本号,然后再 commit 消息;写入前会比较消息版本号和 redis 的版本号,若小于,则直接丢弃;这样这个问题也解决了。


第三,我们观察到写入 ES 有 3 个独立的进程写入,不同流写入同一个索引也会引入一致性问题。这里我们可以分析出,主要是先验数据的写入可能会存在一致性问题,因为后验数据写入的是不同字段,而且只有 update 操作,不会删除或者插入。


举一个例子,上游的 MySQL 这里删除一条数据,全量链路和增量链路同时执行,而刚好全量 Dump 时刚好取到这条数据,随后 binlog 写入 delete 记录,那么 ES 写入模块分别会消费到插入和写入两条消息,而他自己无法区分先后顺序,最终可能导致先删除后插入,而 DB 里这条消息是已删除的,这就造成了不一致。


那么这里如何解决该问题呢?其实分析到问题之后就比较好办,常用的办法就是利用 Kfaka 的回溯能力:在 Dump 全量数据前记录下当前时间戳 t1,Dump 完成之后,将增量链路回溯至 t1 即可。而这段可能不一致的时间窗口,也就是 1 分钟左右,业务上是完全可以忍受的。


线上 0 停机高可用的在线索引升级流程如下图所示:



(3)写入平滑


由于 Flink 聚合后的数据有很大的毛刺,导入写入 ES 时服务不稳定,cpu 和 rt 都有较大毛刺,写入情况如下图所示:



此处监控间隔是 10 秒,可以看到,由于聚合窗口是 1min,每分钟前 10 秒写入达到峰值,后面逐渐减少,然后新的一分钟开始时又周期性重复这种情况。


对此我们需要研究出合适的平滑写入方案,这里直接使用固定阈值来平滑写入不合适,因为业务不同时间写入量不同,无法给出固定阈值。


最终我们使用以下方案来平滑写入:



我们使用自适应的限流器来平滑写,通过统计前 1 分钟接收的消息总量,来计算当前每秒可发送的消息总量。具体实现如下图所示,将该模块拆分为读线程和写线程,读线程统计接收消息数,并把消息存入队列;令牌桶数据每秒更新;写线程获取令牌桶,获取不到则等待,获取到了就写入。最终我们平滑写入后的效果如图所示:





在不同时间段,均能达到平滑的效果。


四、召回性能调优


1. 高并发场景优化


由于存在多路召回,所以召回系统有读放大的问题,我们 ES 相关的召回,总 qps 是 50W。这么大的请求量如果直接打入 ES,一定是扛不住的,那么如何来进行优化呢?


由于大量请求的参数是相同的,并且存在大量的热门 key,因此我们引入了多级缓存来提高召回的吞吐量和延迟时间。


我们多级缓存方案如下图所示:



这个方案架构清晰,简单明了,整个链路: 本地缓存(BigCache)<->分布式缓存(Redis)<->ES。


经过计算,整体缓存命中率为 95+%,其中本地缓存命中率 75+%,分布式缓存命中率 20%,打入 ES 的请求量大约为 5%。这就大大提高了召回的吞吐量并降低了 RT。


该方案还考虑缓了存穿透和雪崩的问题,在线上上线之后,不久就发生了一次雪崩,ES 全部请求失败,并且缓存全部未命中。起初我们还在分析,究竟是缓存失效导致 ES 失败,还是 ES 失败导致设置请求失效,实际上这就是经典的缓存雪崩的问题。


我们分析一下,这个方案解决了 4 点问题:


  • 本地缓存定时dump到磁盘中,服务重启时将磁盘中的缓存文件加载至本地缓存。

  • 巧妙设计缓存Value,包含请求结果和过期时间,由业务自行判断是否过期;当下游请求失败时,直接延长过期时间,并将老结果返回上游。

  • 热点key失效后,请求下游资源前进行加锁,限制单key并发请求量,保护下游不会被瞬间流量打崩。

  • 最后使用限流器兜底,如果系统整体超时或者失败率增加,会触发限流器限制总请求量。


2. ES 性能调优


(1)设置合理的 primary_shards


primary_shards 即主分片数,是 ES 索引拆分的分片数,对应底层 Lucene 的索引数。这个值越大,单请求的并发度就越高,但给到上层 MergeResult 的数量也会增加,因此这个数字不是越大越好。


根据我们的经验结合官方建议,通常单个 shard 为 1~50G 比较合理,由于整个索引大小 10G,我们计算出合理取值范围为 1~10 个,接下里我们通过压测来取最合适业务的值。压测结果如下图所示:




根据压测数据,我们选择 6 作为主分片数,此时 es 的平均 rt13ms,99 分位的 rt 为 39ms。


(2)请求结果过滤不需要的字段


ES 返回结果都是 json,而且默认会带上 source 和_id,_version 等字段,我们把不必要的正排字段过滤掉,再使用 filter_path 把其他不需要的字段过滤掉,这样总共能减少 80%的包大小,过滤结果如下图所示:



包大小由 26k 减小到 5k,带来的收益是提升了 30%的吞吐性能和降低 3ms 左右的 rt。


(3)设置合理 routing 字段


ES 支持使用 routing 字段来对索引进行路由,即在建立索引时,可以将制定字段作为路由依据,通过哈希算法直接算出其对应的分片位置。


这样查询时也可以根据指定字段来路由,到指定的分片查询而不需要到所有分片查询。根据业务特点,我们将作者账号 id puin 作为路由字段,路由过程如下图所示:



这样一来,我们对带有作者账号 id 的召回的查询吞吐量可以提高 6 倍,整体来看,给 ES 带来了 30%的吞吐性能提升。


(4)关闭不需要索引或排序的字段


通过索引模板,我们将可以将不需要索引的字段指定为"index":false,将不需要排序的字段指定为"doc_values":false。这里经测试,给 ES 整体带来了 10%左右的吞吐性能提升。


五、结语


本文介绍了看点视频推荐索引的构建方案,服务于看点视频的 CB 类型召回。其特点是,开发成本低,使用灵活方便,功能丰富,性能较高,符合线上要求。


上线以来服务于关注召回、冷启动召回、tag 画像召回、账号画像召回等许多路召回,为看点视频带来较大业务增长。未来随着业务进一步增长,我们会进一步优化该方案,目前来看,该技术方案还领先于业务一段时间。最后欢迎各位同学交流,欢迎在评论区留言。


本文来自 DataFunTalk


原文链接


腾讯看点视频推荐索引构建方案


2020 年 11 月 23 日 14:00746

评论

发布
暂无评论
发现更多内容

第八周总结

小兵

12.5大数据集群资源管理系统Yarn

张荣召

从物理空间到数字世界,数字孪生打造智能化基础设施

华为云开发者社区

IoT 智能 数字

第八周大作业

小兵

五年Java开发经验,从传统企业月薪20k到互联网大厂月薪50k,我只做了这一件事!

Java成神之路

Java 程序员 架构 面试 编程语言

H3C核心交换机故障处理通用流程

网络技术平台

探究神秘的SpringMVC,寻找遗失的web.xml踪迹

996小迁

Java 编程 程序员 架构 面试

12.7作业

张荣召

话题讨论 | 作为程序员你的业余爱好是什么呢?

小天同学

话题讨论 业余爱好

Java中CAS原理分析(volatile和synchronized浅析)

叫练

volatile 多线程 synchronized CAS JUC

GaussDB(DWS)应用实践丨负载管理与作业排队处理方法

华为云开发者社区

数据 负载 GaussDB

云原生体系下的技海浮沉与理论探索

阿里巴巴云原生

Serverless 容器 微服务 云原生 k8s

2020年阿里巴巴136道高频Java面试真题(含答案),附赠Java复习资料。

Java成神之路

Java 程序员 架构 面试 编程语言

12.1大数据技术发展史

张荣召

Eclipse Vert.x 4发布

dinstone

Java Reactive Vert.x

连续肝了好几个晚上,终于把网络相关知识和计算机底层操作系统知识总结整理出来了,两份文档资料分享给你!

Java成神之路

Java 程序员 架构 面试 编程语言

我看技术人的成长路径

阿里巴巴云原生

开发者 云原生 技术人 自我思考 职场成长

12.4大数据计算框架MapReduce-架构

张荣召

DolphinDB与Aliyun HybridDB for PostgreSQL在金融数据集上的比较

DolphinDB

postgresql 阿里云 时序数据库 DolphinDB 数据库开发

面试大厂的尖兵利器!超全算法笔试模拟题精解合集,这份《程序员面试宝典》简直太牛了

Java成神之路

Java 程序员 架构 面试 编程语言

12.2分布式文件系统

张荣召

快来!开源一份阿里微服务指导手册:SpringBoot+SpringCloud+消息中间件

Java架构追梦

Java 架构 面试 微服务

Norvarm波场链系统开发方案丨Norvarm波场源码功能

系统开发咨询1357O98O718

Norvarm波场链系统开发

太牛皮了!这份神仙级面试笔记把所有Java知识面试题都详解出来了。免费分享PDF文档,先到先得。

Java成神之路

Java 程序员 架构 面试 编程语言

12.3大数据计算框架MapReduce-编程框架

张荣召

架构师训练营第 1 期 第 12 周作业

李循律(祥龙)

极客大学架构师训练营

巨头们为什么要开源自己的技术?解析科技企业对软件开源的态度

Marilyn

开源 敏捷开发

ICT芯矿链挖矿矿机系统开发平台丨ICT芯矿链源码案例

系统开发咨询1357O98O718

ICT芯矿链矿机系统开发

12.6大数据仓库Hive

张荣召

忒棒了!阿里P8大牛用这份技术点直接带你玩转高可用服务架构

比伯

Java 编程 架构 互联网 程序人生

学习笔记-week12

张荣召

腾讯看点视频推荐索引构建方案-InfoQ