找回密码
 会员注册
查看: 16|回复: 0

ES亿级商品索引拆分实战

[复制链接]

2万

主题

0

回帖

6万

积分

超级版主

积分
63697
发表于 2024-10-12 20:12:30 | 显示全部楼层 |阅读模式
ES亿级商品索引拆分实战 361 商品模型简介ES 在商品后台中的作用历史的一些优化方案和遇到的问题拆分方案设计全量迁移流程流量回放比对验证异步转同步背景伴随政府采购业务的快速发展,的商品数据量也在快速膨胀,其中由 ES 进行提供的商品检索服务压力,也越来越大。由于商品模型中基础商品和交易商品的定义,导致商品本身的量会相对一般的电商场景多出一倍。商品模型简介基础商品真实的商品,拥有仓库,主图,属性等商品主体信息。交易商品共享基础商品的仓库主图等数据,但是可以单独拥有服务,运费模板等配套服务信息。之所以这样设计,是因为不同省、市、区的电子采购管理办法不同,供应商需与政府签订协议才能参与电子卖场交易,基于协议,供应商可以选择基础商品,快速发布交易商品,响应各卖场要求。ES 在商品后台中的作用在商品管理业务中, ES 主要给供应商,监管,运营提供海量商品的检索业务。其中商品数据的来源,主要是监听商品主表以及其他一些附属表信息,每日有千万级数据更新。数据现状索引数据范围数据量级索引体积全量商品索引全量商品(基础商品+交易商品+其他商品(商品服务等历史数据))5亿+1TB+基础索引基础商品+部分交易商品3亿+1.1TB+交易商品索引交易商品3亿+1TB+遇到的问题全量商品索引数据量 5 亿+,查询请求耗时高,大量查询耗时超过 1s 的请求。商品数据的快速膨胀,带来了很大的资源消耗和稳定性问题(如查询抖动等)。商品数据存在冗余,大量的冗余数据,带来了不必要的资源消耗。索引所在集群资源已接近瓶颈,但是扩容的机器成本较高。解决方案探索面对上面的这些情况,我们一开始从索引参数调整, forcemerge 任务引入等多个手段来缓解问题,但是伴随数据的快速膨胀还是遇到类似高命中查询等难以优化的问题,从而引出了索引拆分方案的探索与实施。历史的一些优化方案和遇到的问题调大 merge 线程数,调大 floor_segment 值。通过更多的 merge 来降低,大量写入带来的 Segment 数增长引发的查询速率下降问题。"merge": {          "scheduler": {            "max_thread_count": "2",            "max_merge_count": "4"          },          "policy": {            "floor_segment": "5mb"          }        }索引预排序的引入,实测排序条件和预排序一致时,亿级索引有3倍左右的提升。但是由于业务多样性,导致命中预排序的场景只占一小部分。"sort": {          "field": [            "id",            "gmtModified",            "gmtApplied"          ],          "order": [            "asc",            "desc",            "desc"          ]        }优化索引字段类型,将精确匹配修改为 keyword ,范围匹配修改为数值类型。( ES 针对不同的字段类型,会采用不同的查询策略。keyword 使用 FST 的倒排索引方案,数值类型采用 BKD 方案。前者更适合精确匹配,后者对范围查询更优)。增加索引的分片。当集群资源相对充足是有一定效果,但是如果没有新的数据节点加入,新增分片并不会有明显的性能提升。"number_of_shards": "5"每天跑 forcemerge 任务,降低 Segment 数量,提升白天的查询性能。但是伴随索引体积越来越大, forcemerge 的时间越来越长,有时候整个晚上可能都无法结束。而且 forcemerge 期间,会造成一定的集群抖动,影响一些对请求耗时比较敏感的业务。难以解决的高命中字段查询。在实践中发现,在大表中,如果某个查询字段命中了大量文档,在缓存失效的情况下,大量时间会消耗在在这个字段上。拆分方案设计由于目前常规的操作都已经做过,到目前阶段提升相对较小,所以只能从拆索引的方案去入手。在方案的设计中,我们主要有下面的一些考虑。考虑点要实现不停机迁移。由于商品链路数据是交易中的核心数据,停机操作用户影响比较大。且多个业务线依赖该数据,停机造成的影响范围相对不可控。要做到用户无感的底层数据表切换,支持流量逐步切换,用来观察集群压力,支持快速的回滚,用来应对可能出现的突发问题。能否去除全量商品索引,降低数据冗余,降低集群资源占用。按照何种维度去拆分,拆分后的索引是否会有数据倾斜问题。能否支持后续的二次拆分,伴随业务后续的发展,第一次拆分后的索引,在过了一两年后可能需要,进行二次拆分操作。能否在查询时,尽可能的要降低扫描的数据行数,从而来规避可能遇到的高命中字段影响。如何去除冗余数据去重冗余数据,主要的想法就是去除全量商品索引,通过基础商品索引+交易商品索引+其他商品索引(新增),共同构建一个全量商品数据数据查询能力。这个地方就要解决,索引间数据重复问题,索引间字段不一致问题问题。重新划定的索引数据范围,将之前的全量商品索引数据,分散成三份索引数据,交易商品索引(数据范围与之前相同),基础商品索引(数据范围与之前相同),其他索引(本次新增)。因为基础商品索引中含有部分交易商品,因此对这部分商品打上特殊标识,当三类型索引联查时,过滤掉该部分商品,解决数据重复问题。同时将历史全量商品索引上的字段,同步到 交易,基础,其他 三个索引上。当需要查全量数据时,扫描三个新索引就可以了,这样全量商品索引,就从物理上存在,变成了逻辑上存在。按什么维度拆分,拆多少个一个索引怎么拆,主要看使用的具体场景。比如常见的日志索引,就是按日期滚动拆分。对应政采的后台商品搜索场景,目前就交易商品查询而言,大约77%的请求会带上店铺ID ,就基础商品查询而言,有93%的查询都会带上店铺ID 。因此索引拆分最终是按照店铺维度去拆分。同时基础商品和交易商品的获取,都有对应的使用场景,且调用量较高,所以基础商品索引和交易商品索引的依然保留。最后就是拆多少个索引,每个索引多少分片。拆多少个索引,主要是看数据的分布,拆多个索引,可以保证每个索引上的数据大致相同,不会有严重的数据倾斜问题。每个索引有多少个分片,主要是评估拆完后每个索引有多少个数据,以及未来一段时间的增量。最终的商品索引拆分模型索引数据范围单索引分片单索引数据量级索引个数其他商品商品服务等历史数据1万级1基础商品索引基础商品+部分交易商品53千万+10+交易商品索引交易商品53千万+10+整体迁移流程完成了迁移的数据模型设计,下面就是设计整体的数据迁移流程。整体迁移在设计中主要,分为流量收集,全量写入,增量写入,数据验证,写入方式的异步转同步等阶段。通过完整的迁移流程设计,来保证最终迁移的数据正确性。全量迁移流程该过程主要为历史数据的迁移,并填充历史全量索引的部分数据,重组后的商品数据,分散写入到拆分后的新索引中。全量迁移需要做到两点,其中一个是数据不丢失,第二就是较快的迁移速率。对于第一点,主要解决手段,就是在全量迁移任务开启前,通过消息队列,收集所有迁移过程中的数据。数据拉取慢的问题在迁移过程中,我们遇到的第一个问题,就是全量数据拉取过慢问题。就迁移速度而言,因为本次和一般的索引拆分不同,不是单纯的将一个索引的数据,按店铺拆分到多个索引上,而需要额外填充字段,所以 Reindex 并不满足。即使是通过先将一部分数据 Redinex 数据迁移到新集群上,再二次填充也不太满足,因为 ES 跨集群 Reindex 会限制并发数为1,同时需要将两个集群添加白名单,这个需要将集群进行重启,操作成本也相对较高。之所以不在原集群进行拆分的原因,是原集群的资源已经到达瓶颈,没有足够的磁盘和内存空间,承接新索引。如何在不使用 Reindex 的情况下,保证迁移速率呢。首先我们尝试了 Scroll 方案,但是后续发现,对一个亿级索引做全表 Scroll 查询,单次拉取时间,保持在500-600ms左右,这个拉取时间严重不满足我们的需求。因为在全量数据迁移期间,增量数据要保持收集的,而商品每天平均有千万级别的更新请求,同时在晚上会有大量的数仓回流任务。如果整个迁移要持续好几天,会对在 MQ 中,积压大量的写入消息,不光会导致到时候流量回流时间过长,也可能导致 MQ 集群磁盘被打满。优化方案那么如何提升拉取的效率呢,要提升查询速率,可以通过降低单次扫描数据量,来单次降低查询耗时的方案。提升了单次查询耗时后,就需要将大任务进行拆分,多节点并行的方案,来提升整体的拉取效率。最终我们选择按商品创建时间来作为任务拆分的方案,一个是该字段不可变,第二个是每天商品创建量相对比较恒定,任务相对均匀。任务首先按应用节点拆分为节点级大任务,节点内再按天拆分为更小的任务。这样可以做到多任务并行,并可以根据 ES 集群的压力,通过扩充节点的方案来加快数据迁移。任务执行总共分为两步即数据拉取和写入阶段,首先是数据拉取,该阶段主要负责从原索引获取数据,并填充上全量商品索引的部分字段,这一个阶段的拉取是通过 SearchAfter 方案进行拉取,因为整个迁移流程持续时间较长,部分任务有可能因为网络抖动等问题执行失败,利用 SearchAfter 可以做到任务断点续跑。数据写入阶段,组装完的数据就需要按店铺 ID,选择索引,并写到新集群了。将读写任务进行拆分,可以提升整体的资源利用率,并方便进行拉取或写入的限流。过程中只需要做好失败任务的从事,并监控系统资源即可。通过上述优化,迁移完所有全量数据,总计用时 5 个小时左右。流量回放在全量任务开始之前,我们将老索引的流量拷贝了一份,放入到了消息队列中,流量回放即是将这部分流量在全量任务结束后,进行回放到新索引上。回放没有什么特别,但是有一定要注意。在我们的数据写入场景中,有一种一对多更新的任务,比如店铺名称更新等,如果这种增量流量和普通的商品主表流量一起回放,可能会造成,部分商品店铺信息未修改成功的问题。因为商品主表更新,和店铺信息不处在同一个任务源。如果在商品主表流量未追平之前,就开始进行店铺信息的修改,就会导致部分商品漏改的情况。因此整个回放流程是,商品主表增量流量追平后,再开始回放一对多更新流量。比对验证在迁移完成后要进行比对验证,验证数据和查询逻辑改造的正确性后,才能开启。文档比对文档对比,主要是新老索引文档内容进行比较,比对分两次,一个是正向比对,即通过新索引的 Query 到的数据,去和老索引进行比对。这次主要确认新索引上的字段与老索引保持一致。一个是反向比对,即通过老索引 Query 到的数据,去和新索引进行比对。这次主要解决比如类似新索引数据没有删除,部分商品可能缺失的问题。由于整个商品数量级比较大,且数据在频繁更新。比对主要采用的是抽样 DSL 语句比对。查询流量比对因为本次不光涉及到索引的拆分,还涉及索引的合并。合并必然会带来查询逻辑的变更。因为三类索引上存在对同一个商品属性不同的索引字段名的情况,比如商品的ID,有的索引叫 ID ,有的叫 ItemId 。此外还有查询时路由选择问题,这些查询侧的改动,需要对查询流量进行比对。异步转同步在迁移过程中,为了保障服务的稳定性,采用的是 MQ 异步写入新索引的方案。这样可以在灰度开放过程中,限制新索引的写入流量,同时不影响老索引的写入性能。在完全切换到新索引后,需要由异步写入切换回同步写入。考虑切换回去主要有两点考虑,一个是写入流程中,增加了一个可能的不稳定性因素。一个是可能发生由于某个业务域推送大量变更消息,引发的消息积压。比如大店铺的店铺名称变更操作等,这些大任务可能会阻塞用户正常的商品发布,下架等核心链路流程。因为数据要求最终一致性,核心问题就是如何保证从 MQ 消费写入,更改为直接请求 ES 写入过程中,消息没有乱序。这里主要就是用 Redis 的分布式锁达到一种节点间的分布式共识。这中间主要分为 预备阶段,共识磋商阶段预备阶段首先在 Redis 中创建一把值为0成功锁,和一把值为0失败锁。然后,当观察 MQ 中消费堆积的阈值比较低时,这时即可开启预备阶段。这样消费线程在投递到 MQ 队列之前,会先检测一下当前消息堆积值,当小于设定值时,进入共识磋商阶段。共识磋商阶段应用节点的消费者线程,进入该阶段后,会进行一定次数的自旋,并不投递消息,而是每隔 1s 去 Check 一下当前 MQ 队列的堆积值,如果连续两次 Check 到堆积值为 0,就在 Redis 中把成功锁的值加一。后续执行过程中,如果发现成功锁的值等于参加的节点数,直接将数据写入到 ES 。期间如果有一个节点发现,自己超过设定的自旋次数,就会将失败锁加一,同时将消息投递到 MQ 中,其他节点发现失败锁大于0后,也会结束自旋,将数据投递到 MQ 中。后续可以再通过调整自旋次数等参数,直到所有节点全部达成一致。这样就通过秒级的消费暂停,达到了 MQ 队列下线的效果。多索引联查解决了数据迁移的问题后,关键的问题就是要提升查询的效率,降低查询 RT ,提升请求 QPS 。一般来讲当查询遇到瓶颈,我们往往都会通过建索引,分库分表,历史归档等操作。这些操作之所以能提升查询性能,就在于能降低要扫描的数据规模。越早地降低数据规模,就代表更低的 CPU,磁盘, IO,内存,网络等开销。因此在设计拆分后的索引查询时,也要尽可能地降低要扫描的数据规模。在本次设计中,我们引入了请求改写、索引选择、返回体修改三个功能模块。请求改写当接收到用户请求后,首先要进行一次请求改写。这一步主要有两个目的,一个是要将 DSL 语句改写为3种索引都兼容的格式,因为后续这个语句可能要扫描所有类型的索引。还有一个是解决基础商品索引和交易商品索引中重合的那一部分数据。目前的解决方案是在基础商品索引中做上标识,在出现基础商品索引和交易商品索引联合扫描时,排除掉基础商品索引中的数据。索引选择整体上有两次降低数据规模的机会,在查询进来时,尝试判断用户要看哪一类的商品,基础商品还是交易商品等,这一路如果成功,可以减低 50% 左右的数据规模。在下一步判断供应商所在的具体索引,这一步可以进一步降低要扫描的数据规模。通过两次索引推荐可以降低绝大部分查询要扫描的数据量。后续可以再对全表扫描的请求做针对性优化和限流控制,即可保障整体的稳定性。优化效果在索引拆分完成后,我们达到了如下效果。提升点效果储存和写入储存体积缩减 1TB 左右,写入流量下降 40%慢查询下降慢查询下降 99%性能提升查询 QPS 上限提升 4 倍左右总结与思考本次主要通过索引的拆分与合并,来提升查询性能,同时降低整体集群的资源使用量。过程中我们探索了在线数据的跨集群迁移,多索引联合查询的应用,数据写入的同步异步切换等,希望能够为大家提供解决 ES 大规模数据检索的瓶颈,提供参考。虽然本次相对比较平滑的完成了索引的拆分,但是需要耗费大量的开发和测试资源。伴随业务的快速发展,遇到数据瓶颈的业务线,可能有会逐渐增多,如果届时每个业务域要独自开发和测试,成本还是相对较高的。后续可能考虑将 ES 的索引层和存储层进行分离,通过引入类似 TiKv 或 HBase 等可以无限扩充的 KV 存储来替代 ES 的存储层。通过 KV 存储,来重建 ES 索引。这样可以做到业务方配置化的索引拆分,分片扩容等,无需任何的开发,进一步的降本增效。
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 会员注册

本版积分规则

QQ|手机版|心飞设计-版权所有:微度网络信息技术服务中心 ( 鲁ICP备17032091号-12 )|网站地图

GMT+8, 2024-12-25 13:43 , Processed in 0.527712 second(s), 25 queries .

Powered by Discuz! X3.5

© 2001-2024 Discuz! Team.

快速回复 返回顶部 返回列表