当前位置: 首页 > 后端技术 > Java

面试官:如何在几千万的数据中查询10W的数据,有什么规划?

时间:2023-04-01 23:33:26 Java

作者:VariableSpeedFengsheng\链接:https://juejin.cn/post/7104090532015505416前言在开发中遇到一个业务需求,需要从几千万条数据中筛选出不超过10W的数据锅数据。并按照配置的权重规则进行排序和分散(例如一个类别下的商品数据不能连续出现3次)。下面介绍业务需求的实现、设计思路和方案优化,以下方案是针对“千万级数据中查询10W级数据”设计的多线程+CK翻页方案ESscroll扫描深度翻页方案ES+Hbase组合方案RediSearch+RedisJSON组合方案初步设计方案整体方案设计如下:首先,根据配置的“过滤规则”从pot表中过滤出“目标数据”,以及然后根据配置的“排序规则”过滤“目标数据”。数据”排序,获取“结果数据”的技术方案如下:每天运行衍生任务,将已有的千万锅数据(Hive表)导入Clickhouse,然后使用CK表进行数据筛选.将业务配置的过滤规则和排序规则构建成一个“过滤+排序”的对象SelectionQueryCondition,当从CKpot表中取“目标数据”时,启动多线程,进行页面过滤,将获取到的“目标数据”存储起来"在结果列表中。//页面大小默认5000intpageSize=this.getPageSize();//页码intpageCnt=totalNum/this.getPageSize()+1;List>result=Lists.newArrayList();List>>>futureList=newArrayList<>(pageCnt);//开启多线程调用for(inti=1;i<=pageCnt;i++){//配置业务筛选规则和顺序规则构造为SelectionQueryCondition对象SelectionQueryConditionselectionQueryCondition=buildSelectionQueryCondition(selectionQueryRuleData);selectionQueryCondition.setPageSize(pageSize);selectionQueryCondition.setPage(i);futureList.add(selectionQueryEventPool.submit(newQuerySelectionDataThread(selectionQueryCondition)));}for(Future>>future:futureList){//RPC调用List>queryRes=future.get(20,TimeUnit.SECONDS);if(CollectionUtils.isNotEmpty(queryRes)){//将目标数据存入resultresult.addAll(query资源);}}对目标数据result进行排序,得到最终的“结果数据”推荐一个开源免费的SpringBoot最全教程:https://github.com/javastacks/spring-boot-best-practiceStep3ofthe“FirstEditionDesignPlan”章节提到“从CKpot表中取目标数据时,开启多线程并进行分页筛选”。这里介绍CK分页查询。封装queryPoolSkuList方法,负责从CK表中获取目标数据。此方法在内部调用sqlSession.selectList方法。publicList>queryPoolSkuList(Mapparams){List>resultMaps=newArrayList<>();QueryConditionqueryCondition=parseQueryCondition(params);List>mapList=lianNuDao.queryPoolSkuList(getCkDt(),queryCondition);如果(CollectionUtils.isNotEmpty(mapList)){for(Mapdata:mapList){resultMaps.add(camelKey(data));}}returnresultMaps;}//lianNuDao.queryPoolSkuList@Autowired@Qualifier("ckSqlNewSession")privateSqlSessionsqlSession;publicList>queryPoolSkuList(Stringdt,QueryConditionqueryCondition){queryCondition.setDt(dt);queryCondition.checkMultiQueryItems();returnsqlSession.selectList("LianNu.queryPoolSkuList",queryCondition);}sqlSession.selectList方法中调使用了和CK交互的queryPoolSkuList查询方法,部分子代码如下selectsku_pool_id,item_sku_id,skuPoolName,price,......businessTypefromliannu_sku_pool_indicator_allwheredt=#{dt}and${queryItem.field}=#{queryItem.value}......按sku_pool_id,item_sku_id分组按sku_pool_id,item_sku_id,${orderBy}排序${orderBy}${orderAd}limit#{limitStart},#{limitEnd}可以看到在CK页面查询时,是通过limit#{limitStart},#{limitEnd}实现的}有分页限制的分页方案在“深度翻页”时会出现性能问题。第一版方案上线后,从1000W锅数据中过滤出10W数据最差也需要10s到18s左右。使用ESScrollScan优化深度翻页优化CK深度翻页的性能问题,使用Elasticsearch的scrollscan翻页方案进行优化。ES的翻页方案ES的翻页,有以下几种选择:from+size翻页scrollpage-turningscrollscanpage-turningsearchafterpage-turningmethodperformance优缺点scenefrom+sizelowflexibilitygood,实现简单的深度分页问题数据量比较小,可以容忍深度分页的问题。scroll解决了深分页的问题。需要维护一个scrollId(快照版),不能体现数据的实时性;可以排序,但不能跳页查询海量数据。scrollscan中基于scroll的解决方案,进一步提升了海量数据查询性能,无法排序。其他缺点同scrollquery海量数据搜索后一样。连续分页的实现会比较复杂,因为每次查询都需要上一次查询的结果。不适合大页面跳转查询,适合海量数据的分页。对于上述几种翻页方案,查询不同数量的数据需要时间。数据如下表所示。ES翻页方式1-1049000-4901099000-99010from+size8ms30ms117msscroll7ms66ms36mssearch_after5ms8ms7ms耗时数据这里使用Elasticsearch的scrollscan翻页方案和第一版中的CK翻页方案查询数据,比较它们的耗时数据。从以上测试数据可以发现,以10万、10万、1000万量级的锅为例,锅越大,相同数据量的查询时间越长。当查询结果小于3W时,ES性能优秀;当查询结果大于5W时,CK的多线程性能非常出色。ES+Hbase组合查询方案。在《使用ESScrollScan优化深度翻页》中,使用了Elasticsearch的滚动扫描翻页方案来优化深度翻页问题。是单线程调用,所以最终测试耗时数据不是特别理想,性能和CK翻页方案差不多。研究阶段发现,当10W的目标数据从锅中取出时,一个产品包含多个字段的信息(CK表中一行有150个字段信息),如价格、会员价、学生价格、库存、优惠率等。对于一行记录,当获取的字段数量减少时,查询耗时会明显减少。例如sku1的商品,从之前获取价格、会员价、学生价、亲友价、库存等100个字段信息,缩减为只有价格和库存两??个字段信息。如下图,使用ES查询方案,对于查询相同条数记录的场景(从千万盆中筛选出70000+条数据),每条记录获取的字段数减少从32个减到17个,再减到1个(其实是两个字段,一个是商品的唯一标识sku_id,一个是ES为每个文档记录的doc_id),查询时间会从9.3s下降到4.2s,然后到2.4s。可以得出结论,在一次ES查询中,如果查询字段和信息较多,fetch阶段的耗时要比query阶段的耗时长很多。在ES查询中,如果查询字段和信息较多,通过减少不必要的查询字段,可以显着缩短查询时间。下面对结论中涉及的query和fetch查询阶段进行补充说明。ES查询的两个阶段:query和fetch在ES中,搜索一般包括两个阶段,query和fetch阶段query阶段根据查询条件决定去fetch哪些文档(doc),并根据查询过滤出文档ID(doc_id)到fetch阶段查询阶段返回的文档ID(doc_id)取出具体的文档(doc)ES文件系统缓存ES会自动将磁盘中的数据缓存到文件系统缓存中,在内存中查找,提高了速度.如果文件系统缓存无法容纳索引数据文件,将基于磁盘搜索,此时查询速度会明显变慢。如果数量过大,基于“ESquery和fetch两阶段ESquery”,可以采用ES+HBase架构,保证ES的数据量小于文件系统缓存。保证查询速度结合Hbase的使用通过以上研究发现“减少不必要的查询显示字段”可以显着缩短查询时间。按照这个优化思路,参考参考链接ref-1,设计了一个新的查询方案ES,只做条件过滤,ES的查询结果只包含记录的唯一标识sku_id(其实也包含sku_id由ES为每个文档记录)。doc_id)Hbase是列存储数据库,每一列数据都有一个rowKey。使用rowKey过滤一条记录时,复杂度为O(1)。(类似于根据key从HashMap取值)根据ES查询返回的唯一标识sku_id,作为Hbase查询中的rowKey,在O(1)复杂度下获取其他信息字段,如价格,库存等.使用ES+Hbase组合查询方案,在线进行了小规模的灰度测试。从1000W锅数据中筛选出10W数据,对比CK翻转方案,最差耗时从10~18s优化到3~6s左右。还需要注意的是,使用ES+Hbase的组合查询方案会增加系统的复杂度,数据也需要同时存储在ES和Hbase中。RediSearch+RedisJSON优化方案RediSearch是一个基于Redis的分布式全文搜索和聚合引擎,能够以极快的速度对Redis数据集执行复杂的搜索查询。RedisJSON是一个Redis模块,在Redis中提供JSON支持。RedisJSON可以与RediSearch无缝协作来索引和查询JSON文档。根据一些参考,RediSearch+RedisJSON可以达到极高的性能,可以说是碾压其他NoSQL方案。在后续的版本迭代中,可以考虑对该方案进行进一步优化。RediSearch+RedisJSON的一些性能数据如下。RediSearch性能数据在相同服务器配置下索引了560万个文档(5.3GB),RediSearch建立索引耗时221秒,Elasticsearch耗时349秒。RediSearch比ES快58%。数据被索引后,使用32个客户端检索两个词。RediSearch的吞吐量达到了12.5Kops/sec,ES的吞吐量达到了3.1Kops/sec。RediSearch比ES快4倍。同时,RediSearch的延迟是8ms,而ES是10ms,RediSearch的延迟稍低。对比RedisearchElasticsearch搜索引擎专用引擎基于Lucene引擎编程语言C语言Java存储方案内存盘协议Redis序列化协议HTTP集群企业版支持简单查询性能高于ES复杂查询高于RediSearchRedisJSON性能数据根据性能测试报告官网,RedisJson+RedisSearch可以说是碾压其他NoSQL。对于隔离写,RedisJSON比MongoDB快5.4倍,比ES快200多倍。对于隔离读,RedisJSON比MongoDB快12.7倍,比ES快500倍。在混合工作负载场景下,实时更新不会影响RedisJSON的搜索和读取性能,而ES会受到影响。RedisJSON支持的操作数是MongoDB的50倍/秒,是ES的7倍/秒。RedisJSON的延迟比MongoDB低约90倍,比ES低23.7倍。此外,RedisJSON的读取、写入和加载搜索延迟在更高的百分位数上远比ES和MongoDB稳定。RedisJSON也可以在提高写入率时处理越来越高的整体吞吐量。并且当写入比例增加时,ES会降低它可以处理的整体吞吐量。总结本文由一个业务需求引发,针对“从千万级数据中查询10W级数据”,介绍了不同的设计方案。对于“从1000W锅数据中过滤10W数据”的场景,不同方案的耗时如下多线程+CK翻页方案,最差耗时10s~18s单线程+ES滚动扫描深度翻页ES+Hbase组合方案相比CK方案没有明显的优化。最耗时的优化是3s~6sRediSearch+RedisJSON组合方案。这个方案的耗时参考后面测:https://juejin。cn/post/7103848212154286087https://www.infoq.cn/article/wymrl5h80sfawg8u7edehttps://juejin.cn/post/7042476201574662175推荐近期文章:1.1000+Java面试题及答案(2022最新版)2.厉害了!Java协程来了。..3.SpringBoot2.x教程,太全面了!4.不要用爆破爆满画面,试试装饰者模式,这才是优雅的方式!!5.《Java开发手册(嵩山版)》最新发布,赶快下载吧!感觉不错,别忘了点赞+转发!

最新推荐
猜你喜欢