关于如何在SpringBoot 1.x 的版本中集成Elasticsearch 2.x可以参考前文Elasticsearch实践(二)在Springboot微服务中集成搜索服务。2017年底,SpringData项目终于更新了Elasticsearch5.x版本的对应release版本:3.0.2.RELEASE。本文结合一个本地的示例,对于ES版本升级进行简单介绍。目前ES已经出到了6.x版本。但是SpringData项目的更新速度一直比较慢。目前比较适合集成的版本就是3.0.2.RELEASE。同时,对应的Springboot也需要升级到2.x。
因为Elasticsearch,以及其周边的相关平台都是强版本依赖的,所以升级的过程也会需要升级其他相关组件。本文主要介绍使用Docker容器来部署Elasticsearch5.x集群。文中源码地址:https://github.com/lijingyao/elasticsearch5-example
Elasticsearch的5.x相当于3.x。之所以从2一跃跳到5,Elastic体系内还有logstash、Kibana,beats等产品。为了统一各产品版本,所以直接将Elasticsearch的版本从2提升到5。
5.x版本提供了许多新的特性,并且基于Lucene6.x。下面简单列举一些升级的特性:
支持IPV6
性能的具体数据可以查看Elasticsearch性能监控。elasricsearch性能的提升,主要是Lucene6版本之后的很多底层结构的优化。Lucene6使用Block K-D trees数据结构来构建索引。BKD Trees是一种可以动态扩展的KD-tree结构。详细的解释可以参考这篇论文Bkd-tree: A Dynamic Scalable kd-tree。
1. 新增Shrink API
Elasticsearch2.x的版本,在创建索引时指定了shard数,并且不支持修改。如果要改变shard数,只能重建索引。5.x新增的Shrink接口,可将分片数进行收缩成它的因数,如果原有的shard数=15,可以收缩成5个或者3个又或者1个。
2. 新增Rollover API
Rollover API对于日志类型的索引提供了友好的创建和管理。比如通过
POST /my_alias/_rollover/my_new_index_name
{
"conditions": {
"max_age": "7d",
"max_docs": 1000,
"max_size": "5gb"
}
}
可以给索引设置rollover规则:索引文档不超过1000个、最多保存7天的数据、每个索引文件不超过5G,超过限制会自动创建新的索引文件别名,如logs-2018.01.25-000002。
3. 新增Reindex
2.x版本的ES的索引重建一直是很麻烦的事情。5.x提供的Reindex可以直接在搜索集群中对数据进行重建。如下可以直接修改mapping。
curl -XPOST 'localhost:9200/_reindex?pretty' -H 'Content-Type: application/json' -d'
{
"source": {
"index": "twitter"
},
"dest": {
"index": "new_twitter"
}
}
'
Elasticsearch5.x还增加了Task Manager、Ingest Node等。
Elasticsearch官方的镜像基于Centos,并且内置了X-Pack。安装过程可以参考官方教程-5.6. 官方5.x版本的Docker镜像内置了X-pack。选择正确的版本即可。
Docker官方的ES镜像Dockerhub-ES也是基于Elastic官方的基础镜像。对于IK 插件版本需要严格对应Elasticsearch的版本。
分词器等插件的安装,可以直接基于官方的镜像,在Dockerfile中重新build自己的镜像。如下示例5.5.0版本Elasticsearch的Dockerfile:
FROM elasticsearch:5.5.0
RUN sed -i 's/deb.debian.org/mirrors.ustc.edu.cn/g' /etc/apt/sources.list
RUN apt-get update && apt-get install zip
RUN mkdir -p /usr/share/elasticsearch/plugins/ik
RUN cd /usr/share/elasticsearch/plugins/ik && wget https://github.com/medcl/elasticsearch-analysis-ik/releases/download/v5.5.0/elasticse arch-analysis-ik-5.5.0.zip && unzip elasticsearch-analysis-ik-5.5.0.zip
编写好Dockerfile之后,再运行docker build即可。
Elasticsearch是强版本依赖的。相信所有在折腾过Elasticsearch的DevOps都被各种插件版本、关联Logstash,Kibana等的版本、集成SpringData相关版本弄得晕头转向。目前SpringDataElasticsearch更新了新的支持ES5.x的版本。本文示例Elasticsearch升级到版本5.5对应SpringDataElasticsearch版本3.0.2.RELEASE。使用Gradle构建的项目添加:
compile 'org.springframework.data:spring-data-elasticsearch:3.0.2.RELEASE'
因为ES 5.x中的Mapping改变,所以原有的@Field(type = FieldType.string)类型的索引映射需要替换成@Field(type = FieldType.keyword)或者@Field(type = FieldType.text)。
对于index原来的analyzed/not_analyzed/no也有相应的改变。keyword,text对于index只接受true/false值。分别代替not_analyzed/no。SpringDataEs中,index默认=true。
所以原有的索引字段,需要根据索引特征进行修改,否则会编译错误。如果要精确搜索就用keyword,否则用text。如果不需要通过该字段进行查询,则index设置false即可。
服务中需要显示log4j-core, log4j-api包。否则会启动异常。
ext.log4jCore = "org.apache.logging.log4j:log4j-core:2.10.0"
ext.log4jApi = "org.apache.logging.log4j:log4j-api:2.10.0"
需要显示引入spring-data-commons。SimpleElasticsearchMappingContext 依赖的org.springframework.data.mapping.model.Property需要spring-data-commons的2.x版本
ext.springDataCommon = "org.springframework.data:spring-data-commons:2.0.2.RELEASE"
如果在2.x升级到5.x的过程。使用分词器的document的索引会引起异常:
failed to load elasticsearch nodes : org.elasticsearch.index.mapper.MapperParsingException: analyzer [ik] not found for field
解决方式:先关掉相关的索引,然后修改对应的settings的analyzer,最后再开启索引。示例代码如下:
curl -XPOST '127.0.0.1:9200/items/_close?pretty'
curl -XPUT '127.0.0.1:9200/items/_settings' -d '{
"analysis": {
"analyzer": {
"ik": {
"type": "custom",
"tokenizer": "ik_smart"
}
}
}
}'
curl -XPOST '127.0.0.1:9200/items/_open?pretty'
使用Gradle构建工程的项目,如果是4.2以下版本的也需要升级。因为Springboot2.x的plugin需要gradle 4.2以上的版本。否则启动时会报错。
如果idea在build工程时还是报错”Could not get unknown property ‘projectConfiguration’ for DefaultProjectDependency”,可以更新最新版的idea。支持更高级别的gradle。
目前的idea版本信息:
IntelliJ IDEA 2017.3.3 (Community Edition)
Build #IC-173.4301.25, built on January 16, 2018
JRE: 1.8.0_152-release-1024-b11 x86_64
更新后问题解决。具体issue: gradle-2936
SpringBoot1.5.x的版本不支持ElasticSearch 5.x。所以需要升级项目到Spring Boot 2。目前Spring Boot 2只有milestone版本。本文示例选择了2.0.0.M2。
相应的 SpringBootGradle 插件也需升级到2.0.0.M2,版本示例见:spring-boot-s-new-gradle-plugin
相应的,原有工程的Spring版本也需要升级到5.x版本。本示例升级到了5.0.2.RELEASE。
如果项目中使用了Spring cloud。也需要随着Springboot升级到符合的版本如Eureka,Feign,Ribbon 可以对应到: 2.0.0.M2。
示例中以一个简单的商品的信息的搜索为例。使用Springboot2,Spring5,Gradle4.4.1。 Elasticsearhc使用5.5.0的镜像部署。
先看下gradle的build文件:
apply plugin: 'java'
apply plugin: 'idea'
apply plugin: 'org.springframework.boot'
apply plugin: 'io.spring.dependency-management'
buildscript {
ext {
springBootVersion = '2.0.0.M2'
}
repositories {
mavenCentral()
maven { url "https://repo.spring.io/snapshot" }
maven { url "https://repo.spring.io/milestone" }
}
dependencies {
classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
}
}
sourceCompatibility = 1.8
targetCompatibility = 1.8
repositories {
mavenCentral()
maven { url "https://repo.spring.io/snapshot" }
maven { url "https://repo.spring.io/milestone" }
}
idea {
project {
jdkName = '1.8'
languageLevel = '1.8'
vcs = 'Git'
}
module {
downloadJavadoc = false
downloadSources = true
}
}
dependencies {
ext.springVersion = "5.0.2.RELEASE"
ext.slf4jVersion = "1.7.21"
compile "org.springframework:spring-beans:${springVersion}"
compile "org.springframework:spring-core:${springVersion}"
compile "org.springframework:spring-context:${springVersion}"
compile "org.springframework:spring-expression:${springVersion}"
compile "org.springframework:spring-web:${springVersion}"
compile "org.springframework:spring-webmvc:${springVersion}"
compile "org.springframework:spring-test:${springVersion}"
compile "org.springframework:spring-orm:${springVersion}"
compile "org.springframework.boot:spring-boot-autoconfigure:${springBootVersion}"
compile "org.springframework.boot:spring-boot:${springBootVersion}"
compile "org.springframework.boot:spring-boot-starter:${springBootVersion}"
compile "org.springframework.boot:spring-boot-starter-aop:${springBootVersion}"
compile "org.springframework.boot:spring-boot-starter-tomcat:${springBootVersion}"
compile "org.apache.commons:commons-lang3:3.4"
compile "commons-io:commons-io:2.5"
compile "commons-logging:commons-logging:1.2"
compile "commons-codec:commons-codec:1.10"
compile "javax.servlet:javax.servlet-api:3.1.0"
compile "org.slf4j:slf4j-api:${slf4jVersion}"
compile "org.apache.logging.log4j:log4j-core:2.10.0"
compile "org.apache.logging.log4j:log4j-api:2.10.0"
compile 'org.springframework.data:spring-data-commons:2.0.2.RELEASE'
compile 'org.springframework.data:spring-data-elasticsearch:3.0.2.RELEASE'
compile 'org.springframework.boot:spring-boot-starter-web'
compile 'org.springframework.boot:spring-boot-starter-logging'
compile 'org.springframework.boot:spring-boot-starter-data-elasticsearch'
compile 'org.elasticsearch:elasticsearch:5.4.1'
compile 'org.elasticsearch.client:transport:5.4.1'
}
Document entity如下 :
/**
* 商品Document -example
*
* Created by lijingyao on 2018/1/18 18:03.
*/
@Document(indexName = ItemDocument.INDEX, type = ItemDocument.TYPE)
public class ItemDocument {
public static final String INDEX = "items";
public static final String TYPE = "item";
public ItemDocument() {
}
public ItemDocument(String id, Integer catId, String name, Long price, String description) {
this.id = id;
this.catId = catId;
this.name = name;
this.price = price;
this.description = description;
}
/**
* 商品唯一标识
*/
@Id
@Field(type = FieldType.keyword)
private String id;
/**
* 类目id
*/
@Field(type = FieldType.Integer)
private Integer catId;
/**
* 商品名称
*/
@Field(type = FieldType.text,index = false)
private String name;
/**
* 商品价格
*/
@Field(type = FieldType.Long)
private Long price;
/**
* 商品的描述
*/
@Field(type = FieldType.text, searchAnalyzer = "ik", analyzer = "ik")
private String description;
... getset ...
@Override
public String toString() {
return "ItemDocument{" +
"id='" + id + '\'' +
", catId=" + catId +
", name='" + name + '\'' +
", description='" + description + '\'' +
", price=" + price +
'}';
}
}
Repository接口代码:
public interface ItemDocumentRepository extends ElasticsearchRepository<ItemDocument, String> {
}
Controller代码:
@RestController
@RequestMapping("/items")
public class SearchController {
@Autowired
private ItemDocumentRepository repository;
@RequestMapping(value = "/{id}",method = {RequestMethod.GET})
public ResponseEntity getItem(@PathVariable("id") String id) {
ItemDocument com = repository.findById(id).get();
return new ResponseEntity(com.toString(), HttpStatus.OK);
}
@RequestMapping(method = {RequestMethod.POST})
public ResponseEntity createItem(@RequestBody ItemDocument document) {
repository.save(document);
return new ResponseEntity(document.toString(), HttpStatus.OK);
}
}
@Configuration
@EnableElasticsearchRepositories(basePackages = "com.lijingyao.es")
public class SearchConfig {
private static final Logger logger = LoggerFactory.getLogger(SearchConfig.class);
@Value("${elasticsearch.port}")
private int esPort;
@Value("${elasticsearch.clustername}")
private String esClusterName;
@Value("#{'${elasticsearch.hosts:localhost}'.split(',')}")
private List hosts = new ArrayList<>();
private Settings settings() {
Settings settings = Settings.builder()
.put("cluster.name", esClusterName)
.put("client.transport.sniff", true).build();
return settings;
}
@Bean
protected Client buildClient() {
TransportClient preBuiltTransportClient = new PreBuiltTransportClient(settings());
if (!CollectionUtils.isEmpty(hosts)) {
hosts.stream().forEach(h -> {
try {
preBuiltTransportClient.addTransportAddress(new InetSocketTransportAddress(InetAddress.getByName(h), esPort));
} catch (UnknownHostException e) {
logger.error("Error addTransportAddress,with host:{}.", h);
}
});
}
return preBuiltTransportClient;
}
@Bean
public ElasticsearchTemplate elasticsearchTemplate() {
Client client = buildClient();
return new ElasticsearchTemplate(client);
}
}
POST http://localhost:8088/items
body:
{
"id":"123",
"catId":1,
"description":"商品,质量好,包邮,售后服务保障",
"name":"商品123",
"price":1000
}
GET http://localhost:8088/items/123
curl -XPOST 'http://127.0.0.1:9200/items/item/_delete_by_query?q=id:123&pretty'
示例代码的git地址:
Elasticsearch 5.5-SpringDataElasticsearch 3.0.2-Example
如果有其他问题欢迎随时交流。
Github-spring-data-elasticsearch
ES5新增功能
Full cluster restart upgrade
Snapshot And Restore
ElasticSearch升级版本
Dockerhub-ES
IK
X-Pack