当前位置: 首页 > 科技观察

架构师必备:多维查询优秀实践_0

时间:2023-03-18 21:46:22 科技观察

背景常见的多维查询场景有两种,分别是:多过滤条件的列表查询和其他维度不分库分表列查询的普通数据库查询,以上需求场景都很难实现,更不用说模糊查询和全文搜索了。下面结合楼主的经验和知识介绍下初级方案和进阶方案(在ElasticSearch上)。大多数情况下,推荐使用ElasticSearch来实现多维查询。赶时间的读者可以直接跳到《进阶方案:在现有系统中添加ElasticSearch》。主要解决方案1、根据常见的查询场景,增加相应字段的组合索引。这是为了实现多过滤条件的列表查询。优点很简单。读和写的不一致是短的。取决于数据库主从的同步延迟,一般在毫秒级。缺点非常有限:除非过滤条件比较固定,否则很难应对后续添加或修改过滤条件。如果每次都有新的过滤器去查询字段的需求,再加一个索引,最终导致索引过大,影响性能。于是出现了一个经典场景:产品要求支持一个新领域的筛选查询,开发反馈说做不到,或者成本很高,所以随它去吧:)2.生成多份数据更优雅的方法是生成多份数据。比如C端按用户维度查询,B端按门店维度查询,如果有供应商则按供应商维度查询。一个数据库只能按一个维度来划分。(1)程序写多个数据源的好处是:非常简单。缺点跨库写入存在一致性问题(除非不同维度的表使用公共分库,事务性写入),性能低,不能灵活支持其他维度的更多查询(2)与Canal自动同步数据通过Canal同步数据,多维度的异构数据源。具体可以参考我之前写的这篇文章:架构师必备:使用Canal实现异步解耦架构。优点是:比较优雅,不改变程序的主流程。缺点还是不能解决变化的需求,无法异构创建新的数据进阶方案来支持新的维度:在现有系统应用架构中加入ElasticSearch现有系统普遍使用MySQL数据库,需要引入ES,增强系统多维查询功能。MySQL继续承担业务的实时读写请求和事务操作,ES承担近实时的多维查询请求。ES可以支持10万级别的qps(取决于节点数、分片数、副本数)。需要注意的是,同步数据到ES是秒级延迟(主要花在索引刷新上),而查询已经进入索引的文档是几毫秒到几百毫秒级别。导入数据需要同步机制,将数据从MySQL导入到ES中。主要流程如下:预定义ES索引的映射配置,不依赖ES自动生成映射的初始全量导入,后续增量导入:Canal+MQ数据管道同步,不需要或只需要很少代码工作量数据过滤:不导入不需要检索的字段,减小索引大小,提高性能数据扁平化:如果数据库中有json字段列,需要将业务字段提取出来,避免嵌套类型提升性能的字段查询数据从ES8.x版本开始,推荐使用Javaapi客户端,需要Java8以上环境,可以使用各种lambda函数提高代码可读性。优点是新的客户端和服务端代码完全耦合(相比原来的Javatransportclient,在8.x版本已经过时),API风格非常接近httprestapi(相比原来的Javarestclient,8.x版本已经废弃),只要熟练掌握httpjsonrequestbody的写法,就可以快速上手。底层仍然使用原来的lowlevelrestclient,实现http长连接、负载均衡和访问ES节点的failover,底层依赖apachehttpasyncclient。ES7.x及以下版本,或者Java7及以下版本,建议升级,否则只能继续使用高阶restclient。代码示例如下(有详细注释):publicclassEsClientDemo{//demodemo:创建一个客户端,然后搜索publicvoidcreateClientAndSearch()throwsException{//在底层创建一个低级的restclient,连接到ES节点的9200端口RestClientrestClient=RestClient.builder(newHttpHost("localhost",9200)).build();//创建一个传输类,传入底层的低级rest客户端和json解析器ElasticsearchTransporttransport=newRestClientTransport(restClient,newJacksonJsonpMapper());//创建一个核心客户端类,后续操作将围绕这个对象进行ElasticsearchClientesClient=newElasticsearchClient(transport);//多条件搜索//FluentAPI风格,并使用lambda函数提高代码可读性,可以看到Javaapi客户端的语法与httpjson请求体非常相似StringsearchText="bike";字符串品牌=“全新”;双最大价格=1000;//根据商品名称做全文匹配查询QuerybyName=MatchQuery.of(m->m.field("name").query(searchText))._toQuery();//根据品牌,做term精准查询QuerybyBrand=newQuery.Builder().term(t->t.field("brand").value(v->v.stringValue(brand))).build();//根据价格做区间查询QuerybyMaxPrice=RangeQuery.of(r->r.field("price").lte(JsonData.of(maxPrice)))._toQuery();//调用核心客户端做查询SearchResponseresponse=esClient.search(s->s.index("products")//指定ES索引query(q->q//指定查询DSL.bool(b->b//多个条件必须组合,必须满足同时.must(byName).must(byBrand).must(byMaxPrice))),Product.class);//遍历命中结果List>hits=response.hits().hits();for(Hithit:hits){产品product=hit.source();//通过源eGet结果logger.info("找到商品"+product.getName()+",score"+hit.score());}}}请参考:https://www.elastic.co/guide/en/elasticsearch/client/index.html数据模型转换因为既有MySQL也有ES,有两种异构数据模型,需要定义两种代码中的数据模型,实现类型转换的工具类。MySQL数据VOES数据VOMySQL数据VO、ES数据VO相互转换工具业务层BO接口DTO原理总结ES之所以比MySQL更能进行多维查询和全文检索,是因为底层数据结构不同:如果ES倒排索引是全文检索字段:先进行分词,然后生成term->document的倒排索引。查询的时候,也会对query进行切分,然后检索出相关的文档。相关算法如TF-IDF(termfrequency-inversedocumentfrequency),取决于:词在文档中出现的频率(TF,termfrequency),越高代表越相关;以及词在所有文档中出现的频率(IDF,inversedocumentfrequency),representation越高,相关性越低,相当于一个常用词,对相关性影响较小。如果是精确值字段:不需要分词,查询直接当成一个整体term来查询对应的文档。因为文档中的所有字段都生成了倒排索引,可以处理多维组合查询MySQLB+树B+树的非叶子节点记录的是子节点值的范围,而叶子节点记录的是真正的值集合,和同级需要显式创建有序链表组合索引:选择要索引的字段,顺序很重要,所以如果要查询的字段不在索引中,就无法进行高效查询,并且有可能演变成全表扫描(对聚集索引的叶子节点做一次遍历)另外简单回顾一下ES架构的要点:节点分为主节点和数据节点。一个节点上可以有多个分片,分片分为主分片和副本分片。分片、一对多、主分片和副本分片分布在不同的节点上以实现高可用性。primaryshard的个数需要在创建时指定,创建后不能随意更改(更改会导致路由出错);并且可以增加复制分片来提高ES集群的查询QPS路由算法:id%主分片个数,如果创建文档时不指定id,ES会自动生成;一般会传递自定义业务id的优缺点支持各字段的多维组合查询,不用担心以后新增字段(主要成本在于新增字段后重建索引),与现有系统,适合架构演化。数据量远胜Mysql,最多支持PB高层数据存储和秒级查询读写不一致的缺点:因为有两个耗时阶段,一个是同步阶段写入数据从MySQL数据库到ES,还有一个就是ES的索引刷新阶段,数据从buffer写入到索引后才可以找到。所以一个trick就是在写操作之后,前端延迟调用后端的list查询接口,比如延迟1秒,超高并发下有瓶颈,有a稳定性问题:目前原生版本支持大约3-50000个分片,性能已经达到极限,创建索引基本达到30秒+甚至分钟。节点数量只能达到500个左右,基本是极限了。但它仍然满足大多数场景。数据来源:https://elasticsearch.cn/slides/259#page=30ES最佳实践只将需要搜索的数据导入ES,避免大索引数据扁平化,没有嵌套结构,提高性能合理设置字段类型,预先定义映射配置,不依赖ES自动生成映射的精确值作为关键字(映射配置),使用term查询精确值指的是不需要的字段进行范围查询,可以是一个字符串,比如书的作者姓名,也可以是一个值,比如产品id,订单id,书的ISBN号,枚举值。在使用中,大多数场景下,id类作为精确值,避免无路由查询:无路由查询会同时查询多个索引,并对结果进行合并排序,导致集群cpu飙升,影响稳定。避免深度分页查询:比如如果有大量数据查询,建议使用scrollquery设置合理的文件系统缓存(filesytemcache)大小来提高性能:因为ES查询的热点数据在文件系统缓存,创建后ES分片数量不能随意改变,但副本数量可以随时增加,以提高最大QPS。如果单个分片压力过大,需要进行扩容。再者,如前所述,ES的超高并发下存在瓶颈,极端情况下可能会遇到OOM。所以在超高并发下,需要一个用C++实现的专用搜索引擎。类别:电子商务的搜索引擎,比如根据关键词搜索商品,或者根据品牌和价格筛选商品,可以概括为商品搜索、广告、推荐