SpringData3.x以及SpringBoot2集成Elasticsearch5.x

说明

关于如何在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

Elasticsearch5.x以及2.x版本对比

5.x版本和2.x版本的对比

Elasticsearch的5.x相当于3.x。之所以从2一跃跳到5,Elastic体系内还有logstash、Kibana,beats等产品。为了统一各产品版本,所以直接将Elasticsearch的版本从2提升到5。
5.x版本提供了许多新的特性,并且基于Lucene6.x。下面简单列举一些升级的特性:

性能方面

  • 磁盘空间可以节省近一半
  • 索引时间减少近50%
  • 查询性能提升近30%
  • 支持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 ManagerIngest Node等。

其他特性

  • Mapping变更中,String的类型的映射,被替换为两种: text/keyword
    text:类似于全文形式,包括被分析。
    keyword:属于字符串类型的精确搜索。
  • 集群节点的配置方面,在节点启动时就会校验配置,如Max File Descriptors,Memory Lock, Virtual Memory等的验证,会启动抛出异常,降低后期稳定性的风险。同时更新配置时更加严格和保证原子性,如果其中一项失败,那个整个都会更新请求都会失败。
  • 插件方面,Delete-by-query和Update-by-query重新增加回core。2.x时被移除,以至于需要手动安装插件,5.x的插件构建在Reindex机制之上,已经可以直接使用了。
  • 允许现有parent类型新增child类型。详见Allow adding additional child types referring to the same parent #17956

使用Docker部署Elasticsearch5.x

使用Elastic官方镜像

Elasticsearch官方的镜像基于Centos,并且内置了X-Pack。安装过程可以参考官方教程-5.6. 官方5.x版本的Docker镜像内置了X-pack。选择正确的版本即可。

自定义Dockerfile安装ik分词器插件

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即可。

SpringDataElasticsearch以及SpringBoot集成

SpringDataElasticsearch集成

SpringDataElasticsearch版本选择

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三方包

服务中需要显示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"  

ClassNotFound-SimpleElasticsearchMappingContext

需要显示引入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'

Springboot2及相关插件升级

Gradle升级到4.4.1

使用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

SpringBoot2.x

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

SpringCore升级到5.0.x

相应的,原有工程的Spring版本也需要升级到5.x版本。本示例升级到了5.0.2.RELEASE。

SpringCloud升级

如果项目中使用了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、Controller

Document entity如下 :



/**
 * 商品Document -example
 * <p>
 * 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);
    }

}

Elasticsearch配置

@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<String> 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
  • 测试delete by query
    使用search API 通过id 删除。
curl -XPOST 'http://127.0.0.1:9200/items/item/_delete_by_query?q=id:123&pretty'  

示例代码Git

示例代码的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

猜你喜欢

转载自blog.csdn.net/lijingyao8206/article/details/79170183