背景常见的多维查询场景有两种,分别是:多过滤条件的列表查询和其他维度不分库分表列查询的普通数据库查询,以上需求场景都很难实现,更不用说模糊查询和全文搜索了。下面结合楼主的经验和知识介绍下初级方案和进阶方案(在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();//调用核心客户端做查询SearchResponse
