在订单系统当中使用Elasticsearch
背景
单表亿级别的数据仅仅凭借MySQL的引擎依赖索引去查询已经无法满足当前的需求,面对日益激增的订单数据,而订单又作为状态变更十分敏感的业务产品,对及时性和准确性有着非常高的要求。而业务稳定性的评价,订单的快速准确查询是非常重要的一个指标。
搜索中间件调研
对于Java而已,阿帕奇已经有非常成熟的搜索类库LucLuene。说起这个我们已经经历过Solr的实践(用作搜索POI和城市景点,老东家老旅游公司了,怎么会没有几个全文搜索解决方案?),也深知其在搜索领域的强大,但是当时我们的Solr是自建的,一般用在了低频更新的地点数据上面,这里就先简单介绍下比较熟知的两款中间件。
Solr
官网:https://lucene.apache.org/solr/
Apache Solr是一个基于名为Lucene的Java库构建的开源搜索平台。它以用户友好的方式提供Apache Lucene的搜索功能。作为一个行业参与者近十年,它是一个成熟的产品,拥有强大而广泛的用户社区。它提供分布式索引,复制,负载平衡查询以及自动故障转移和恢复。如果它被正确部署然后管理得好,它就能够成为一个高度可靠,可扩展且容错的搜索引擎。
看了下人的社区氛围真就酸了,由于开源早口碑好,积淀了不少的用户,如果要选择其作为落地的方案的话其实也是比较可靠的.
ElasticSearch
官网:https://www.elastic.co/cn/
Elasticsearch是一个开源(Apache 2许可证),是一个基于Apache Lucene库构建的RESTful搜索引擎。
Elasticsearch是在Solr之后几年推出的。它提供了一个分布式,多租户能力的全文搜索引擎,具有HTTP Web界面(REST)和无架构JSON文档。Elasticsearch的官方客户端库提供Java,Groovy,PHP,Ruby,Perl,Python,.NET和Javascript。
分布式搜索引擎包括可以划分为分片的索引,并且每个分片可以具有多个副本。每个Elasticsearch节点都可以有一个或多个分片,其引擎也可以充当协调器,将操作委派给正确的分片。
Elasticsearch可通过近实时搜索进行扩展。其主要功能之一是多租户。
对于之前加个字段看命令学了一下午的铁笨比我而言,solr其实在配置(经典sechmal.xml rebuild)和运维上都是要难于Es的(Http一把梭,不行就直接PUT),而按场景来划分的话也说了几遍了,不适合热频更新的索引。
关于两者的对比这里有详细的数据:http://solr-vs-elasticsearch.com/
选取ElasticSearch
订单更新频繁、检索多依赖于索引字段,就这一条就够我们抛弃Solr上Es了。但是不同于当时的自建,我们这次的方式是买集群...可能同步方案和更新策略上的考虑比较欠缺,严重依赖阿里云的同步,但是这样一来我们有更多的空间去关注下游的查询,对于数据更新这件事我们无需花更多精力在上面。
当然在阿里云的落地就略显无脑,一路点点点,然后掏钱就好了(真的是这样,只需要考虑需要建哪些索引,同步属性是怎么样,剩下的阿里云统统帮你搞定[阿里云打钱])
基本使用
ES的文档就有点离谱了,由于Es 的版本较多且部分语法在不同版本当中都不太一致,所以使用的时候可能要注意一下。而且文档都是Http请求的Demo文档,Es没有给出一些Java API的使用说明,而这些API其实有点类似JSON的直译总体上比较好理解,只要能够分清什么是Hits、什么是索引、什么是桶、什么是聚合就很快能上手,当然如果以前手撸过Mango 的查询的话可能比较更好处理,因为API的分类比较类似。
这里先附上他们的文档链接:https://www.elastic.co/guide/en/elasticsearch/reference/index.html
这里我检举一下SpringBoot项目的引入流程(仅限版本6.7.0,ES版本不同可能会有一些异样,请注意版本):
1. 引入依赖
1 2 3 4 5 6 7 8 9 10 11 |
<dependency> <groupId>org.elasticsearch</groupId> <artifactId>elasticsearch</artifactId> <version>6.7.0</version> </dependency> <dependency> <groupId>org.elasticsearch.client</groupId> <artifactId>elasticsearch-rest-high-level-client</artifactId> <version>6.7.0</version> </dependency> |
- 配置连接
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 |
@Bean(destroyMethod = "close") // 这个close是调用RestHighLevelClient中的close @Scope("singleton") public RestHighLevelClient createInstance() { log.info("!!!! es !!!!!" + host + " username " + username); try { String[] hosts = host.split(","); HttpHost[] httpHosts = new HttpHost[hosts.length]; for (int i = 0; i < httpHosts.length; i++) { String h = hosts[i]; httpHosts[i] = new HttpHost(h.split(":")[0], Integer.parseInt(h.split(":")[1]), "http"); } RestClientBuilder build = RestClient.builder(httpHosts).setRequestConfigCallback(new RestClientBuilder.RequestConfigCallback() { @Override public RequestConfig.Builder customizeRequestConfig(RequestConfig.Builder requestConfigBuilder) { return requestConfigBuilder; } }); if (!StringUtils.isEmpty(username)) { final CredentialsProvider credentialsProvider = new BasicCredentialsProvider(); credentialsProvider.setCredentials(AuthScope.ANY, new UsernamePasswordCredentials(username, password)); build.setHttpClientConfigCallback(new RestClientBuilder.HttpClientConfigCallback() { @Override public HttpAsyncClientBuilder customizeHttpClient(HttpAsyncClientBuilder httpAsyncClientBuilder) { httpAsyncClientBuilder.setMaxConnTotal(maxConnTotal); //最大连接数 默认30 httpAsyncClientBuilder.setMaxConnPerRoute(maxConnPerRoute);// 最大路由连接数 默认10 httpAsyncClientBuilder.disableAuthCaching(); return httpAsyncClientBuilder.setDefaultCredentialsProvider(credentialsProvider); } }); } else { build.setHttpClientConfigCallback(new RestClientBuilder.HttpClientConfigCallback() { @Override public HttpAsyncClientBuilder customizeHttpClient(HttpAsyncClientBuilder httpAsyncClientBuilder) { httpAsyncClientBuilder.setMaxConnTotal(maxConnTotal); //最大连接数 默认30; httpAsyncClientBuilder.setMaxConnPerRoute(maxConnPerRoute);// 最大路由连接数 默认10 return httpAsyncClientBuilder; } }); restHighLevelClient = new RestHighLevelClient(RestClient.builder(httpHosts)); } restHighLevelClient = new RestHighLevelClient(build); } catch (Exception e) { log.error("", e); return null; } return restHighLevelClient; } |
- 通过Rest发起查询/变更请求
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
public class ElasticsearchTemplate<T, M> { @Autowired RestHighLevelClient client; public Response request(Request request) throws Exception { Response response = client.getLowLevelClient().performRequest(request); return response; } public SearchResponse search(SearchRequest searchRequest) throws IOException { SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT); return searchResponse; } } |
当然这个依赖下封装了很多的工具类和包装类,其查询的语法总体上比较直译,但是包比较多,单独拉一起说一下各种查询语法。
当然 希望下期快一点。