乐趣区

关于elasticsearch:腾讯看点视频推荐索引构建方案

一、背景

在视频举荐场景中,一方面咱们须要让新启用的视频尽可能快的触达用户,这一点对于新闻类的内容尤为要害;另一方面咱们须要疾速辨认新物品的好坏,通过散发的流量,以及对应的后验数据,来判断新物品是否值得持续散发流量。

而这两点对于索引先验数据和后验数据的提早都有很高的要求。下文将为大家介绍看点视频举荐的索引构建计划,心愿和大家一起交换。文章作者:纪文忠,腾讯 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 画像召回、账号画像召回等许多路召回,为看点视频带来较大业务增长。将来随着业务进一步增长,咱们会进一步优化该计划,目前来看,该技术计划还当先于业务一段时间。最初欢送各位同学交换,欢送在评论区留言。

退出移动版