Elasticsearch实践(二)在Springboot微服务中集成搜索服务
关于如何用Docker搭建Elasticsearch集群环境可以参考前一篇:Elasticsearch实践(一)用Docker搭建Elasticsearch集群。本文主要介绍,如果在Springboot体系中集成Elasticsearch服务。本文基于:Elasticsearch版本是2.2.4,Springboot版本是1.5.3.RELEASE,spring-data-elasticsearch:2.1.3.RELEASE。
Elasticsearch官方API
Elasticsearch提供了多种api。可以直接使用官方提供的Java API进行使用。ElasticSearc Java API。如果是使用Spring框架的项目,还可以用spring-data-elasticsearch的api。基于spring可以使用Annotation,索引文档不需要任何xml式的配置。而且使用上非常简便。其存储、查询接口继承了JpaRepository,所以对于引入JPA的项目来说,上手非常快。
Elasticsearch也提供了http协议的API,资源API风格是restful的,所以也都比较好记忆。在有需要的场景时查官网是最快的。
Springboot项目中使用spring-data-elasticsearch框架集成
Springboot项目集成elasticsearch,可以使用spring-data-elasticsearch。官方链接:
spring-data-elasticsearch Doc 熟悉JPA以及使用过Spring-data-common项目的开发者,应该很快会上手spring-data-elasticsearch。首先要做的就是在gradle项目中,引入‘org.springframework.data:spring-data-elasticsearch:2.1.3.RELEASE’以及‘org.springframework.boot:spring-boot-starter-data-elasticsearch:your_springboot_version’ 。在我们对于索引数据的crud操作api中,主要用的是ElasticsearchRepository 接口,其继承与spring-data的基础repository包的接口CrudRepository。先看一下接口的主要方法:
@NoRepositoryBean
public interface CrudRepository<T, ID extends Serializable> extends Repository<T, ID> {/*** Saves a given entity. Use the returned instance for further operations as the save operation might have changed the* entity instance completely.* * @param entity* @return the saved entity*/<S extends T> S save(S entity);/*** Saves all given entities.* * @param entities* @return the saved entities* @throws IllegalArgumentException in case the given entity is {@literal null}.*/<S extends T> Iterable<S> save(Iterable<S> entities);/*** Retrieves an entity by its id.* * @param id must not be {@literal null}.* @return the entity with the given id or {@literal null} if none found* @throws IllegalArgumentException if {@code id} is {@literal null}*/T findOne(ID id);/*** Returns whether an entity with the given id exists.* * @param id must not be {@literal null}.* @return true if an entity with the given id exists, {@literal false} otherwise* @throws IllegalArgumentException if {@code id} is {@literal null}*/boolean exists(ID id);/*** Returns all instances of the type.* * @return all entities*/Iterable<T> findAll();/*** Returns all instances of the type with the given IDs.* * @param ids* @return*/Iterable<T> findAll(Iterable<ID> ids);/*** Returns the number of entities available.* * @return the number of entities*/long count();/*** Deletes the entity with the given id.* * @param id must not be {@literal null}.* @throws IllegalArgumentException in case the given {@code id} is {@literal null}*/void delete(ID id);/*** Deletes a given entity.* * @param entity* @throws IllegalArgumentException in case the given entity is {@literal null}.*/void delete(T entity);/*** Deletes the given entities.* * @param entities* @throws IllegalArgumentException in case the given {@link Iterable} is {@literal null}.*/void delete(Iterable<? extends T> entities);/*** Deletes all entities managed by the repository.*/void deleteAll();...
}
其对于Elasticsearch的文档(@Document)的数据的操作就类似于JPA中对于数据库表(@Entity)的接口。可以用findByXX的方式进行查询,也可以自定义@Query()方式进行查询。在开发的过程中,对于一些特殊的查询场景,可以查询spring-data-elasticsearch源码中的示例,基本包含了各种场景的API,项目git:spring-data-elasticsearch Git
使用spring-boot-starter-data-elasticsearch做启动时搜索服务的配置
使用Springboot,可以在启动时对很多服务Bean进行注入。一下是通过Autowire方式,使用spring-boot-starter-data-elasticsearch:2.1.3.RELEASE来处理基于Springboot的微服务启动时连接Elasticsearch集群,以及注入应用代码需要使用的 ElasticsearchTemplate。Configuration类如下:
import org.apache.commons.lang3.StringUtils;
import org.elasticsearch.client.transport.TransportClient;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.transport.InetSocketTransportAddress;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.elasticsearch.core.ElasticsearchTemplate;
import org.springframework.data.elasticsearch.repository.config.EnableElasticsearchRepositories;import java.net.InetAddress;/*** 使用的是es 2.4.4 版本,因为springboot 1.5.x,以及目前版本最多支持到es 2.x。* <p>* Created by lijingyao on 2017/5/17 16:32.*/
@Configuration
@EnableElasticsearchRepositories(basePackages = "com.puregold.ms")
public class SearchConfig {// 假设使用三个node,(一主两备)的配置。在实际的生产环境,需在properties文件中替换成实际ip(内网或者外网ip)@Value("${elasticsearch.host1}")private String esHost;// master node@Value("${elasticsearch.host2:}") private String esHost2;//replica node@Value("${elasticsearch.host3:}")private String esHost3;//replica node@Value("${elasticsearch.port}")private int esPort;@Value("${elasticsearch.clustername}")private String esClusterName;@Beanpublic TransportClient transportClient() throws Exception {Settings settings = Settings.settingsBuilder().put("cluster.name", esClusterName).build();TransportClient transportClient = TransportClient.builder().settings(settings).build().addTransportAddress(new InetSocketTransportAddress(InetAddress.getByName(esHost), esPort));if (StringUtils.isNotEmpty(esHost2)) {transportClient.addTransportAddress(new InetSocketTransportAddress(InetAddress.getByName(esHost2), esPort));}if (StringUtils.isNotEmpty(esHost3)) {transportClient.addTransportAddress(new InetSocketTransportAddress(InetAddress.getByName(esHost3), esPort));}return transportClient;}@Beanpublic ElasticsearchTemplate elasticsearchTemplate() throws Exception {return new ElasticsearchTemplate(transportClient());}}
使用spring-data-elasticsearch基于注解的示例API
创建索引和文档,同JPA的 @Entity,@Table,可以通过在搜索的文档实体类添加@Document注解的方式,在启动Springboot应用时会直接创建以及更新Elasticsearch的index以及document。
下面创建一个示例。示例中包含两个Document,一个是OrderDocument,一个是DetailOrderDocument。示例中OrderDocument和DetailOrderDocument是parent-child关联,可以参考官方对于p-c的描述:indexing-parent-child。Elasticsearch支持多种对于文档模型的关联。在建立parent child关系的时候需要注意:child 需要根据parant的id进行路由,parantid 和child的parantid 必须是string。否则回在启动时报错:
nested exception is java.lang.IllegalArgumentException: Parent ID property should be String
OrderDocument
@Document(indexName = OrderDocument.INDEX, type = OrderDocument.ORDER_TYPE, refreshInterval = "-1")
public class OrderDocument {public static final String INDEX = "orders-test";public static final String ORDER_TYPE = "order-document";public static final String DETAIL_TYPE = "order-detail-document";@Idprivate String id;// 订单备注,不需要分词,可以搜索@Field(type = FieldType.String, index = FieldIndex.not_analyzed)private String note;// 订单名称,可以通过ik 分词器进行分词@Field(type = FieldType.String, searchAnalyzer = "ik", analyzer = "ik")private String name;// 订单价格@Field(type = FieldType.Long)private Long price;public String getId() {return id;}public void setId(String id) {this.id = id;}public String getNote() {return note;}public void setNote(String note) {this.note = note;}public String getName() {return name;}public void setName(String name) {this.name = name;}public Long getPrice() {return price;}public void setPrice(Long price) {this.price = price;}
}
DetailOrderDocument
@Document(indexName = OrderDocument.INDEX, type = OrderDocument.DETAIL_TYPE, shards = 10, replicas = 2, refreshInterval = "-1")
public class DetailOrderDocument {@Idprivate String id;// 指定主订单关联的父子关系@Field(type = FieldType.String, store = true)@Parent(type = OrderDocument.ORDER_TYPE)private String parentId;// 子订单价格@Field(type = FieldType.Long)private Long price;public String getId() {return id;}public void setId(String id) {this.id = id;}public String getParentId() {return parentId;}public void setParentId(String parentId) {this.parentId = parentId;}public Long getPrice() {return price;}public void setPrice(Long price) {this.price = price;}
}
以上就在 “orders-test” 索引中创建了两个Document。@Id注解对应着Elasticsearch的id。可以系统自动生成,也可以创建文档数据时指定固定的id,但是一定要保证唯一性。
启动好之后可以通过curl xget来查询索引的结构。结果如下:
{"orders-test" : {"aliases" : { },"mappings" : {"order-detail-document" : {"_parent" : {"type" : "order-document" },"_routing" : {"required" : true },"properties" : {"parentId" : { "type" : "string", "store" : true },"price" : { "type" : "long" } }},"order-document" : {"properties" : {"name" : { "type" : "string", "analyzer" : "ik" },"note" : { "type" : "string", "index" : "not_analyzed" },"price" : { "type" : "long" } }}},"settings" : {"index" : {"refresh_interval" : "-1","number_of_shards" : "10","creation_date" : "1511403448676","store" : {"type" : "fs" },"number_of_replicas" : "2","uuid" : "sHA5s7kEQA2AWCAA8-aBlQ","version" : {"created" : "2040499" }}},"warmers" : { }}
}
另,刚才代码中,通过设置@Document的参数 number_of_shards,number_of_replicas。可以看到创建文档的settings参数:”number_of_shards” : “10”, “number_of_replicas” : “2”。如果不指定参数,则默认分别是 number_of_shards=5,number_of_replicas=1。其他默认参数可以查看public @interface Document源码。
有特殊字符的自生成的id
用findOne 时会报错,可以用findById 来代替,用query terms精确查找是可以的
{"error" : {"root_cause" : [ {"type" : "routing_missing_exception","reason" : "routing is required for [XX]/[YY]/[yourid]","index" : "forests"} ],"type" : "routing_missing_exception","reason" : "routing is required for [XX]/[YY]/[yourid]","index" : "forests"},"status" : 400
}
Repositories&ElasticsearchTemplate
文档创建好之后,对于文档数据的索引可以继承spring-data-elasticsearch的ElasticsearchRepository。使用CurdRepository接口规范来完成基础的查询,存储,更新操作。如下简单举例了两个查询语句。
public interface DetailOrderDocumentRepository extends ElasticsearchRepository<DetailOrderDocument, String> {List<DetailOrderDocument> findByParentId(String parentId, Sort sort);DetailOrderDocument findById(String id);
}
如果是比较复杂的查询场景,可以在Repository接口写@Query语句。也可以使用ElasticsearchTemplate来写更灵活的定制化查询:
@Component
public class OrderManager {@Autowiredprivate ElasticsearchTemplate elasticsearchTemplate;public Page<OrderDocument> queryPagedOrders(Integer pageNo, Integer pageSize, String name, Long minPrice, Long maxPrice) {// 默认,价格升序(为了支持丰富的排序场景,建议将所有可能的排序规则放到统一的enum中Pageable pageable = new PageRequest(pageNo, pageSize, new Sort(new Sort.Order(Sort.Direction.ASC, "price")));NativeSearchQueryBuilder nbq = new NativeSearchQueryBuilder().withIndices(OrderDocument.INDEX).withTypes(OrderDocument.ORDER_TYPE).withSearchType(SearchType.DEFAULT).withPageable(pageable);BoolQueryBuilder bqb = boolQuery();// 匹配订单nameif (StringUtils.isNotEmpty(name)) {bqb.must(termQuery("name", name));}// 查询价格区间 minPrice<=price<=maxPriceif (minPrice != null && minPrice >= 0) {bqb.filter(rangeQuery("price").gte(minPrice));}if (maxPrice != null && maxPrice >= 0) {bqb.filter(rangeQuery("price").lte(maxPrice));}Page<OrderDocument> page = elasticsearchTemplate.queryForPage(nbq.withQuery(bqb).build(), OrderDocument.class);return page;}}
其他注意事项
- 如果需要删除parent child映射的索引
一般的索引都可以直接使用:
curl -XDELETE 'http://yourip:9200/orders-test/?pretty'
但parant-child 关系mapping的时候,删除之后,如果想重建索引,在启动springboot的时候会出现异常:
can’t add a _parent field that points to an already existing type, that isn’t already a parent 解决方案是在@Document 属性中设置 createIndex = false(默认是true) ,只在parent document上设置就可以了.这样就可以自由删除index,启动时重建索引。
- 更新文档的分词
官方对于更新映射的说法:mapping-intro
也就是Elasticsearch不支持直接更新mapping字段的索引方式(不能把一个analyzed字段设置成not_analyzed)。 可以支持添加新的映射字段并且制定分词方式(如 ik),或者只能删除index,重建索引 。
如我们示例代码的:
@Field(type = FieldType.String, searchAnalyzer = "ik", analyzer = "ik")private String name;
一旦索引创建完成,无法再变更name字段为not_analyzed。所以在一开始设计索引文档时需要谨慎判断。
- 分页,数据查询多的场景
对于数据量很大的文档的索引查询,会出现以下报错:
Failed to execute phase [query], all shards failed; shardFailures {[X-XXXXX][YYYY][0]: RemoteTransportException[[your-node][yourip:9300][indices:data/read/search[phase/query]]]; nested:QueryPhaseExecutionException[Result window is too large, from + size must be less than or equal to: [10000] but was [99020].See the scroll api for a more efficient way to request large data sets. This limit can be set by changing the [index.max_result_window] index level parameter.]; }{[X-XXXXX][YYYY][1]: RemoteTransportException[[
可以通过以下命令修改索引index_name。这个是index级别的设置,但是不建议更改设置,会增加ES node的内存负担。
curl -XPUT "http://your_cluster:9200/index_name/_settings" -d '{ "index" : { "max_result_window" : 500000 } }'
虽然可以解决索引数据量大的问题,但是接口的性能会有问题:基本上平均返回时间会+200-300ms。推荐用scroll api:
Elasticsearch在处理大结果集时可以使用scan和scroll。在Spring Data Elasticsearch中,可以向下面那样使用ElasticsearchTemplate来使用scan和scroll处理大结果集。可以参考:关于scroll 。
search api返回一个单一的结果“页”,而 scroll API 可以被用来检索大量的结果(甚至所有的结果),就像在传统数据库中使用的游标 cursor。
使用示例如下:
String scrollId = elasticsearchTemplate.scan(nbq.withQuery(bqb).build(), 1000, false);
Page<OrderDocument> page = elasticsearchTemplate.scroll(scrollId, 2000L, OrderDocument.class);
- 查看节点所有配置信息
http://your_cluster:9200/_nodes?pretty
结果中还可以看到所有可用插件列表。可以用来检验分词插件等是否安装成功。
查看mapping信息:
http://your_cluster:9200/index_name/_mapping/document_name?pretty
- 删除child文档索引值,并且添加其他的索引值:
可以查看官方文档Indexing parent and child。通过curl删除,查询时都需要指定parentId,因为前面已经介绍过了,child文档是通过parentId进行路由的.如下需要添加routing。
curl -XDELETE 'http://your_cluster:9200/orders-test/order-detail-document/_query?routing=parent_order_id&pretty' -H 'Content-Type: application/json' -d'
{"query": {"bool": {"must": [{ "term" : { "id" : "detail_order_id" } }]}}
}
'
查询时也一样:
http://your_cluster:9200/orders-test/order-detail-document/_search?&_routing= parent_order_id&q=id:detail_order_id&pretty
同理,新增的时候也需要指定routing。
相关文档
Elasticsearch相关的文档比较少,实施过程中遇到一些问题基本都是要边对照官方文档和源码来解决的。对于Elasticsearch或者其他搜索框架有问题的希望能一起探讨。公司目前在招聘高级Java工程师,架构师,我们有技术,有激情,有美女,有美食。有兴趣的同学欢迎投递,或者直接联系作者沟通。Java高级开发工程师-招聘
ElasticSearc-V5.4 Java API
Java client api
数据建模
映射-索引类型
First Step with Spring Boot and Elasticsearch
spring-data-elasticsearch Doc
spring-data-elasticsearch Git
更新
最新的SpringDataElasticsearch 可以支持到了5.x版本。目前还是rc版,在Realease版本出来之后,服务会进行一次升级,届时会更新一个升级文章。目前对应版本信息如下:
Elasticsearch实践(二)在Springboot微服务中集成搜索服务相关推荐
- 小白版的springboot中集成mqtt服务(超级无敌详细),实现不了掐我头!!!
大家好,我是雄雄,欢迎关注微信公众号雄雄的小课堂 现在是:2023年3月5日19:03:49 前言 在上一篇文章中,我介绍了如何在服务器中安装emqx消息服务器,这是在操作mqtt协议的时候必不可少的 ...
- WebService CXF系列: SpringBoot同一个项目中集成JaxWS和JaxRS
WebService CXF系列: SpringBoot同一个项目中集成JaxWS和JaxRS 介绍 项目介绍 项目架构 项目介绍 项目集成的原则 SpringBoot集成JaxWS 1. JaxWs ...
- 服务中添加mysql服务_Windows平台下在服务中添加MySQL
widows下查看服务 1.桌面计算机-->右键-->管理-->计算机管理(本地)--->服务和应用程序-->服务 2.运行 中输入 services.msc 在服务中添 ...
- 【苹果imessage群发苹果推位置推】软件安装在系统中集成 USBMuxd 服务
推荐内容IMESSGAE相关 作者推荐内容 iMessage苹果推软件 *** 点击即可查看作者要求内容信息 作者推荐内容 1.家庭推内容 *** 点击即可查看作者要求内容信息 作者推荐内容 2.相册 ...
- 微服务中集成分布式配置中心 Apollo
背景 随着业务的发展.微服务架构的升级,服务的数量.程序的配置日益增多(各种微服务.各种服务器地址.各种参数),传统的配置文件方式和数据库的方式已无法满足开发人员对配置管理的要求:配置修改后实时生效, ...
- 删除服务中的mysql服务
以管理员身份运行命令提示符,然后输入sc delete mysql 这里的mysql是你服务中的mysql名(有些可能是mysql5,或者之类).
- 创建寄宿在Windows服务中的WCF服务
1.创建Windows服务项目 2.Server1改名为你想要的名称,比如WinServer 3.在项目中新建一个WCF文件夹,用于存放wcf服务文件. 注:在WcfServer类的上面还要添加 [S ...
- 微服务中为什么需要服务发现?
最简单的情况下:只有A,B两个服务,他们都有自己的IP地址,如果A调用B,那么需要在A中配置B的地址就可以了. 但是如果B的服务有多个,如下图: 在分布式系统中,多个自治的B并不共享主内存.因此B的服 ...
- Elasticsearch实践(二)linux安装
首先确保环境安装了Java8 1,下载安装包 在ElasticSearch官网根据操作系统,选择需要安装的安装包,以及版本. https://www.elastic.co/downloads/elas ...
最新文章
- 为什么曾经厉害的人突然不厉害了?
- Codeforces Round #556 (Div. 2)
- 网站内容重复了怎么?更好的解决办法是什么?
- AMD为何要选择捆绑中国市场?
- Apache Kafka-事务消息的支持与实现(本地事务)
- Python中关于使用正则表达式相关的部分笔记
- 【牛客网多校】19-7-25-H题 Magic Line
- UVA 10154 Weights and Measures
- Node.js压缩与解压数据
- CSS 奇技淫巧:动态高度过渡动画
- 纯手工打造漂亮的垂直时间轴,使用最简单的HTML+CSS+JQUERY完成100个版本更新记录的华丽转身!...
- ubuntu 20.04 设置静态ip
- python 爬虫图片打不开_爬虫下载图片打不开是什么原因,最新简易爬虫教程
- 沈阳航空航天大学计算机考研真题知识点摘要
- GOT-10k: A Large High-Diversity Benchmark forGeneric Object Tracking in the Wild(论文翻译)
- 物联网在智慧校园里的应用
- M5stack StickCplus ESP32物联网开发板初体验
- 图片裁剪工具vueCropper跨域解决
- 给程序员简历的一些建议
- openstack keystone 用户管理