Docker 搭建 ES 集群并整合 Spring Boot

泥瓦匠 @ bysocket.com

向作者提问

小小 ACMer / Spring Boot 小博主 / 开源点小项目 / 并发网小编辑 …

查看本场Chat

一、前言

什么是 Elasticsearch ?

Elasticsearch 是一个基于 Apache Lucene(TM) 的开源搜索引擎。无论在开源还是专有领域,Lucene
可以被认为是迄今为止最先进、性能最好的、功能最全的搜索引擎库,并通过简单的 RESTful API 来隐藏 Lucene 的复杂性,从而让全文搜索变得简单。

Elasticsearch 不仅仅是 Lucene 和全文搜索,我们还能这样去描述它:

  • 分布式的实时文件存储,每个字段都被索引并可被搜索;
  • 分布式的实时分析搜索引擎;
  • 可以扩展到上百台服务器,处理 PB 级结构化或非结构化数据。

Elasticsearch 三大要素:

  • 文档(Document):在面向对象观念就是一个对象。在 ES 里面,是一个大 JSON 对象,是指定了唯一 ID 的最底层或者根对象。文档的位置由 _index_type_id 唯一标识。
  • 索引(Index):用于区分文档成组,即分到一组的文档集合。索引,用于存储文档和使文档可被搜索。比如项目存索引 project 里面,交易存索引 sales 等。
  • 类型(Type):用于区分索引中的文档,即在索引中对数据逻辑分区。比如索引 project 的项目数据,根据项目类型 ui 项目、插画项目等进行区分。

二、ES 集群安装

基于 Dokcer ,单机安装 Docker 版集群。使用版本如下:

  • Elasticsearch 5.3.2
  • Kibana 5.3.2
  • JDK 8

安装步骤:

  1. 安装 ES 集群实例 elasticsearch001
  2. 安装 ES 集群实例 elasticsearch002
  3. 安装 Kibana 监控

1. 安装 ES 集群实例 elasticsearch001

打开命令行执行:

docker run -d -p 9200:9200 -p 9300:9300 --name elasticsearch001 -h elasticsearch001\
 -e cluster.name=lookout-es -e ES_JAVA_OPTS="-Xms512m -Xmx512m" -e xpack.security.enabled=false\
  docker.elastic.co/elasticsearch/elasticsearch:5.3.2

命令浅析如下:

  • -d 设置后台运行容器。
  • -p [宿主机端口]:[容器内端口]
  • --name 设置容器别名。
  • -h 设置容器的主机名。
  • -e 设置环境变量。这里关闭 x-pack 的安全校验功能,防止访问认证。

第一次运行会比较慢,因为拉取 es docker image,但是可以设置国内 Docker 镜像地址。如果成功,命令行会出现如图所示:

![image.png](https://upload-
images.jianshu.io/upload_images/1483536-50d81d3cd3d3aade.png?imageMogr2/auto-
orient/strip%7CimageView2/2/w/1240)

那么验证下是否启动成功,继续执行如下命令:

curl http://localhost:9200/_cat/health\?v

会出现如图所示的结果:

![image.png](https://upload-
images.jianshu.io/upload_images/1483536-c73c01afde838d99.png?imageMogr2/auto-
orient/strip%7CimageView2/2/w/1240)

cluster.name ES 集群名为 lookout-es,这个后面需要指定关联。node 表示只有一个实例。默认 shards
分片为主备两个。status 状态是我们要关心的,状态可能是下列三个值之一:

  • green:所有的主分片和副本分片都已分配。你的集群是 100% 可用的。
  • yellow:所有的主分片已经分片了,但至少还有一个副本是缺失的。不会有数据丢失,所以搜索结果依然是完整的。高可用会弱化把 yellow 想象成一个需要及时调查的警告。
  • red:至少一个主分片(以及它的全部副本)都在缺失中。这意味着你在缺少数据:搜索只能返回部分数据,而分配到这个分片上的写入请求会返回一个异常。

也可以访问 http://localhost:9200/,可以看到成功运行的案例,返回的 JSON 页面。如图:

![image.png](https://www.bysocket.com/wp-
content/uploads/2017/04/WechatIMG236.jpeg)

2. 安装 ES 集群实例 elasticsearch002

继续执行如下命令:

docker run -d -p 9211:9200 -p 9311:9300 --link elasticsearch001\
  --name elasticsearch002 -e cluster.name=lookout-es -e ES_JAVA_OPTS="-Xms512m -Xmx512m" -e xpack.security.enabled=false\
  -e discovery.zen.ping.unicast.hosts=elasticsearch001 docker.elastic.co/elasticsearch/elasticsearch:5.3.2

命令浅析如下:

  • -d 设置后台运行容器。
  • -p [宿主机端口]:[容器内端口],这边指定新的端口,和实例 elasticsearch001 区别开。
  • --link [其他容器名]:[在该容器中的别名] 添加链接到另一个容器, 在本容器 hosts 文件中加入关联容器的记录。
  • --name 设置容器别名。
  • -h 设置容器的主机名。
  • -e 设置环境变量。这里额外指定了 ES 集群的 cluster.name、ES 集群节点淡泊配置 discovery.zen.ping.unicast.hosts 设置为实例 elasticsearch001。

这次运行会很快,成功后命令行会出现如图所示:

![image.png](https://upload-
images.jianshu.io/upload_images/1483536-bcf800e95548d719.png?imageMogr2/auto-
orient/strip%7CimageView2/2/w/1240)

Docker 启动会有一点延时,稍后再使用 health 命令检查下 ES 的集群状态:

curl http://localhost:9200/_cat/health\?v

会出现如图所示的结果:

![image.png](https://upload-
images.jianshu.io/upload_images/1483536-cb9e943f51f9837c.png?imageMogr2/auto-
orient/strip%7CimageView2/2/w/1240)

对比上面检查数值可以看出,首先集群状态为 green , 所有的主分片和副本分片都已分配。你的集群是 100% 可用的。相应的 node 、shards
都增加。

如果你还想装个 elasticsearch003、elasticsearch004 更多 ES 实例,可以试试。

3. 安装 Kibana 监控

为了更好的监控集群,我们装个 Kibana ,命令行如下:

docker run -d --name kibana001  --link elasticsearch001 -e ELASTICSEARCH_URL=http://elasticsearch001:9200 -p 5601:5601\
 docker.elastic.co/kibana/kibana:5.3.2

命令浅析如下:

  • -d 设置后台运行容器。
  • --link [其他容器名]:[在该容器中的别名] 添加链接到另一个容器,在本容器 hosts 文件中加入关联容器的记录。
  • --name 设置容器别名。
  • -e 设置环境变量。这里额外指定了 ELASTICSEARCH_URL 为搜索实例地址。

打开网页访问 127.0.0.1:5601,默认账号为 elasti,密码为 changeme。会出现如下的截图:

![image.png](https://upload-
images.jianshu.io/upload_images/1483536-e29369531504ec6f.png?imageMogr2/auto-
orient/strip%7CimageView2/2/w/1240)

三、Spring Boot 整合 Elasticsearch

首先是代码的 GitHub 地址:

https://github.com/JeffLi1993/springboot_es

1. 工程结构

工程结构相对简单。如图所示:

![image.png](https://upload-
images.jianshu.io/upload_images/1483536-75f02d6167a7d2e8.png?imageMogr2/auto-
orient/strip%7CimageView2/2/w/1240)

主要关注的是如下:

  • application.yml 配置文件
  • entity 包和 repository 包实现了 ES DAO 操作层
  • 最重要的还是 ContentServiceImpl 搜索服务实现层

2. 依赖配置 spring-boot-starter-data-elasticsearch

在 pom.xml 配置如下:

<!-- ES -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>

要了解 Spring Data Elasticsearch 是什么,首先了解什么是 Spring Data。

Spring Data 基于 Spring 为数据访问提供一种相似且一致性的编程模型,并保存底层数据存储。Spring Data
Elasticsearch 是 Spring Data 的 Community Modules 之一,是 Spring Data 对
Elasticsearch 引擎的实现。

Elasticsearch 默认提供轻量级的 HTTP RESTful 接口形式的访问。相对来说,使用 HTTP Client 调用也很简单。但
Spring Data Elasticsearch 可以更快地支持构建在 Spring 应用上,比如在 application.properties 配置
ES 节点信息和 spring-boot-starter-data-elasticsearch 依赖,直接在 Spring Boot 应用上使用。

这里依赖的 spring-boot-starter-data-elasticsearch 版本是 2.0,对应的 Spring Data
Elasticsearch 版本是 5.5.3 Release。对应 ES 尽量安装成版本一致的 5.5.3。

这里会有个坑,ES 的版本升级不是很平滑,比如用这个连接低版本 ES 是连不上的。对应版本如下:

Spring Boot Version (x) | Spring Data Elasticsearch Version (y) |
Elasticsearch Version (z)
—|—|—
x <= 1.3.5 | y <= 1.3.4 | z <= 1.7.2*
x >= 1.4.x | 2.0.0 <=y < 5.0.0** | 2.0.0 <= z < 5.0.0**

  • 只需要你修改下对应的 pom 文件版本号
  • 下一个 ES 的版本会有重大的更新

具体 Spring Data Elasticsearch 和 Elasticsearch 的版本对应:

Spring Data Elasticsearch Elasticsearch
3.2.x 6.5.0
3.1.x 6.2.2
3.0.x 5.5.0
2.1.x 2.4.0
2.0.x 2.2.0
1.3.x 1.5.2

然后配置下 application.yml 配置文件:

# ES
spring:
  data:
    elasticsearch:
      repositories:
        enabled: true
      cluster-nodes: 127.0.0.1:9300
      cluster-name: lookout-es

指定 ES 集群名称和节点地址,默认 9300 是 Java 客户端的端口。有了 starter 组件,配置就相对简单很多了。

更多配置:

  • spring.data.elasticsearch.cluster-name Elasticsearch 集群名。
  • spring.data.elasticsearch.cluster-nodes 集群节点地址列表,用逗号分隔。如果没有指定,就启动一个客户端节点。
  • spring.data.elasticsearch.propertie 用来配置客户端的额外属性。
  • spring.data.elasticsearch.repositories.enabled 开启 Elasticsearch 仓库(默认值 true)。

3. 搜索 DAO 层

搜索案例实体类代码如下:

package com.bysocket.entity;

import lombok.Data;
import org.springframework.data.annotation.Id;
import org.springframework.data.elasticsearch.annotations.Document;

import java.io.Serializable;

/**
 * 文章
 */
@Document(indexName = "content", type = "content")
@Data
public class ContentEntity implements Serializable {

    // 内容 ID
    @Id
    private Long id;

    // 内容标题
    private String title;

    // 内容
    private String content;

    // 内容类型 1:文章 2:问题
    private Integer type;

    // 内容类别
    private String category;

    // 文章阅读数
    private Integer read;

    // 问题支持数
    private Integer support;
}

注意下面几点:

  • ContentEntity 类中字段属性名不支持驼峰式。比如 type 不能写成 contentType。
  • @Document 注解的 indexName 配置必须是全部小写,不然会出异常。索引用于区分文档成组,即分到一组的文档集合;用于存储文档和使文档可被搜索。比如项目存在索引 project 里面,交易存在索引 sales 等。
  • @Document 注解的 type 类型,用于区分索引中的文档,即在索引中对数据逻辑分区。比如索引 project 的项目数据,根据项目类型 ui 项目、插画项目等进行区分。

ES 数据操作层实现代码如下:

package com.bysocket.repository;

import com.bysocket.entity.ContentEntity;
import org.springframework.data.elasticsearch.repository.ElasticsearchRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface ContentRepository extends ElasticsearchRepository<ContentEntity, Long> {

}

接口只要继承 ElasticsearchRepository
接口类即可,具体使用的是该接口的方法。有兴趣的读者,可以看看里面的实现逻辑,增删改查、搜索都有了,不需要我们具体实现,只需我们传入对应的参数即可。

ElasticsearchRepository 对应官方文档:

<http://docs.spring.io/spring-
data/elasticsearch/docs/2.1.0.RELEASE/reference/html/>

3. 搜索 Service 层

首先,定义一个接口,作为服务层搜索接口 ContentService,代码如下:

package com.bysocket.service;


import com.bysocket.bean.ContentBean;
import com.bysocket.bean.ContentSearchBean;
import com.bysocket.common.PageBean;

import java.util.List;

public interface ContentService {

    /**
     * 批量向 ES 保存内容
     */
    boolean saveContents(List<ContentBean> contentBeanList);

    /**
     * 搜索
     */
    PageBean searchContent(ContentSearchBean contentSearchBean);
}

ContentSearchBean 定义了一些需要搜索的参数:分页参数 pageNumber、pageSize、搜索内容 searchContent
、内容类型 type 和内容类别 category。

定义好接口,最重要的是实现类了。实现类代码太长,我们先看看 saveContents(List<ContentBean> contentBeanList) 的实现。具体在 ContentServiceImpl 类中,代码如下:

package com.bysocket.service.impl;

import com.bysocket.bean.ContentBean;
import com.bysocket.bean.ContentSearchBean;
import com.bysocket.common.PageBean;
import com.bysocket.constant.SearchConstant;
import com.bysocket.entity.ContentEntity;
import com.bysocket.repository.ContentRepository;
import com.bysocket.service.ContentService;
import lombok.AllArgsConstructor;
import lombok.extern.log4j.Log4j2;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Primary;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
import org.springframework.data.elasticsearch.core.query.SearchQuery;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import java.util.ArrayList;
import java.util.List;

@Service
@Primary
@AllArgsConstructor
@Log4j2
public class ContentServiceImpl implements ContentService {

    private static final Logger LOGGER = LoggerFactory.getLogger(ContentServiceImpl.class);

    private final ContentRepository contentRepository;

    @Override
    public boolean saveContents(List<ContentBean> contentBeanList) {

        List<ContentEntity> contentEntityList = transferToContentEntityList(contentBeanList);
        contentRepository.saveAll(contentEntityList);

        return true;
    }
}

代码中:

  • 注意上面讲的,DAO 层 Bean:ContentRepository。
  • 批量保存接口也很简单,直接传入对应的 ContentEntity 列表。
  • transferToContentEntityList 顾名思义,只是一个 DO/BO 转换。

然后具体看看全部的代码:

package com.bysocket.service.impl;

import com.bysocket.bean.ContentBean;
import com.bysocket.bean.ContentSearchBean;
import com.bysocket.common.PageBean;
import com.bysocket.constant.SearchConstant;
import com.bysocket.entity.ContentEntity;
import com.bysocket.repository.ContentRepository;
import com.bysocket.service.ContentService;
import lombok.AllArgsConstructor;
import lombok.extern.log4j.Log4j2;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Primary;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
import org.springframework.data.elasticsearch.core.query.SearchQuery;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import java.util.ArrayList;
import java.util.List;

@Service
@Primary
@AllArgsConstructor
@Log4j2
public class ContentServiceImpl implements ContentService {

    private static final Logger LOGGER = LoggerFactory.getLogger(ContentServiceImpl.class);

    private final ContentRepository contentRepository;

    @Override
    public boolean saveContents(List<ContentBean> contentBeanList) {

        List<ContentEntity> contentEntityList = transferToContentEntityList(contentBeanList);
        contentRepository.saveAll(contentEntityList);

        return true;
    }

    @Override
    public PageBean searchContent(ContentSearchBean contentSearchBean) {

        Integer pageNumber = contentSearchBean.getPageNumber();
        Integer pageSize = contentSearchBean.getPageSize();

        PageBean<ContentEntity> resultPageBean = new PageBean<>();
        resultPageBean.setPageNumber(pageNumber);
        resultPageBean.setPageSize(pageSize);

        // 构建查询条件
        BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();

        // 搜索内容
        String searchContent = contentSearchBean.getSearchContent();
        if (!StringUtils.isEmpty(searchContent)) {
            boolQueryBuilder.should(QueryBuilders.matchPhraseQuery(SearchConstant.CONTENT_ES_NAME, searchContent));
            boolQueryBuilder.minimumShouldMatch(SearchConstant.MINIMUM_SHOULD_MATCH);
        }

        // 内容类型筛选
        Integer type = contentSearchBean.getType();
        if (type != null) {
            boolQueryBuilder.must(QueryBuilders.matchQuery(SearchConstant.TYPE_ES_NAME, type).lenient(true));
        }

        // 内容类别筛选
        String category = contentSearchBean.getCategory();
        if (!StringUtils.isEmpty(category)) {
            boolQueryBuilder.must(QueryBuilders.matchQuery(SearchConstant.CATEGORY_ES_NAME, category).lenient(true));
        }

        // 构建分页、排序条件
        Pageable pageable = PageRequest.of(pageNumber, pageSize);
        if (!StringUtils.isEmpty(contentSearchBean.getOrderName())) {
            pageable = PageRequest.of(pageNumber, pageSize, Sort.Direction.DESC, contentSearchBean.getOrderName());
        }
        SearchQuery searchQuery = new NativeSearchQueryBuilder().withPageable(pageable)
                .withQuery(boolQueryBuilder).build();

        // 搜索
        LOGGER.info("\n ContentServiceImpl.searchContent() DSL  = \n " + searchQuery.getQuery().toString());
        Page<ContentEntity> contentPage = contentRepository.search(searchQuery);

        resultPageBean.setResult(contentPage.getContent());
        resultPageBean.setTotalCount((int) contentPage.getTotalElements());
        resultPageBean.setTotalPage((int) contentPage.getTotalElements() / resultPageBean.getPageSize() + 1);
        return resultPageBean;
    }

    /**
     * 转换
     */
    private List<ContentEntity> transferToContentEntityList(final List<ContentBean> contentBeanList) {
        List<ContentEntity> contentEntityList = new ArrayList<>();

        contentBeanList.forEach(contentBean -> {
            ContentEntity contentEntity = transferToContentEntity(contentBean);
            contentEntityList.add(contentEntity);
        });

        return contentEntityList;
    }

    /**
     * 转换
     */
    private ContentEntity transferToContentEntity(final ContentBean contentBean) {
        if (contentBean == null)
            return null;

        ContentEntity contentEntity = new ContentEntity();
        contentEntity.setId(contentBean.getId());
        contentEntity.setTitle(contentBean.getTitle());
        contentEntity.setContent(contentBean.getContent());
        contentEntity.setType(contentBean.getType());
        contentEntity.setCategory(contentBean.getCategory());
        contentEntity.setRead(contentBean.getRead());
        contentEntity.setSupport(contentBean.getSupport());

        return contentEntity;
    }

}

重点看看 searchContent(ContentSearchBean contentSearchBean) 搜索的实现吧。

  • 构造查询条件,利用的是 QueryBuilders 构建查询工程类。可以支持丰富的查询构建,包括:matchAllQuery() 方法用来匹配全部文档、matchQuery("filedname","value") 匹配单个字段,匹配字段名为 filedname、值为 value 的文档等。
  • 这里利用 BoolQueryBuilder 进行复合查询,调用 QueryBuilders.boolQuery() 构建了符合查询的 Builder。
  • 对于内容搜索,利用的是 QueryBuilders.matchPhraseQuery(SearchConstant.CONTENT_ES_NAME, searchContent) 短语匹配查询。
  • 最后利用 PageRequest 构建了分页、排序条件。

具体接口使用的姿势:

  • BoolQueryBuilder:用于拼装连接查询条件。
  • QueryBuilders:简单的静态工厂,用于查询条件,如区间、精确、多值等条件。
  • NativeSearchQueryBuilder:将连接条件和聚合函数等组合。
  • SearchQuery:生成查询对象。

4. 新增数据接口 & 搜索数据接口

新增 ES 保存接口和搜索接口,代码如下:

package com.bysocket.api;

import com.bysocket.bean.ContentBean;
import com.bysocket.bean.ContentSearchBean;
import com.bysocket.common.PageBean;
import com.bysocket.common.ResponseBean;
import com.bysocket.service.ContentService;
import lombok.AllArgsConstructor;
import lombok.extern.log4j.Log4j2;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@RestController
@AllArgsConstructor
@RequestMapping("/api")
@Log4j2
public class ContentCommonApi {

    private final ContentService contentService;

    @PostMapping("/contents")
    public ResponseBean saveContents(@RequestBody List<ContentBean> contentBeanList) {
        boolean result = contentService.saveContents(contentBeanList);
        if (result) {
            return ResponseBean.success(result);
        }
        return ResponseBean.fail(result);
    }

    @PostMapping(value = "/content/search")
    public ResponseBean searchContent(@RequestBody ContentSearchBean contentSearchBean) {

        PageBean pageBean = contentService.searchContent(contentSearchBean);
        return ResponseBean.successPage(pageBean);
    }

}

在 IDEA 中执行 Application 类启动,任意正常模式或者 Debug 模式。可以在控制台看到成功运行的输出:

... 省略
2017-10-15 10:05:19.994  INFO 17963 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 8080 (http)
2017-10-15 10:05:20.000  INFO 17963 --- [           main] demo.springboot.QuickStartApplication    : Started Application in 5.544 seconds (JVM running for 6.802)

然后打开 Postman 工具,构造保存数据请求:

POST /api/contents HTTP/1.1
Host: 127.0.0.1:8080
Content-Type: application/json
Cache-Control: no-cache
Postman-Token: 5025663f-edc0-9b99-20d8-e30480cd14f0

[
    {
        "id":1,
        "title":"《见识》",
        "content":"摩根说:任意让小钱从身边溜走的人,一定留不住大钱",
        "type":1,
        "category":"文学",
        "read":999,
        "support":100
    },
    {
        "id":2,
        "title":"《态度》",
        "content":"人类的幸福不是来自偶然的幸运,而是来自每天的小恩惠",
        "type":2,
        "category":"文学",
        "read":888,
        "support":88
    },
    {
        "id":3,
        "title":"《Java 编程思想》",
        "content":"Java 是世界上最diao的语言",
        "type":2,
        "category":"计算",
        "read":999,
        "support":100
    }
]

如图所示:

![image.png](https://upload-
images.jianshu.io/upload_images/1483536-b2455e4a98a1403b.png?imageMogr2/auto-
orient/strip%7CimageView2/2/w/1240)

点击 Send ,请求成功会返回如下所示:

{
    "code": 0,
    "message": "success",
    "data": true
}

那接下来去验证下数据是否在 ES。打开网页访问 127.0.0.1:5601,在 Kibana 监控中输入需要监控的 index name 为
content。如下图,取消打钩,然后进入:

![image.png](https://upload-
images.jianshu.io/upload_images/1483536-1de722276a9a092a.png?imageMogr2/auto-
orient/strip%7CimageView2/2/w/1240)

进入后,会得到如图所示的界面,里面罗列了该索引 content 下面所有字段:

![image.png](https://upload-
images.jianshu.io/upload_images/1483536-7016d0d85e3c426a.png?imageMogr2/auto-
orient/strip%7CimageView2/2/w/1240)

打开左侧 Discover 栏目,即可看到可视化的搜索界面及数据:

![image.png](https://upload-
images.jianshu.io/upload_images/1483536-c65c1bd594a73a46.png?imageMogr2/auto-
orient/strip%7CimageView2/2/w/1240)

针对这个 JSON ,如下:

{
  "_index": "content",
  "_type": "content",
  "_id": "3",
  "_score": 1,
  "_source": {
    "id": 3,
    "title": "《Java 编程思想》",
    "content": "Java 是世界上最diao的语言",
    "type": 2,
    "category": "计算",
    "read": 999,
    "support": 100
  }
}
  • _index 就是索引,用于区分文档成组,即分到一组的文档集合。索引,用于存储文档和使文档可被搜索。比如项目存索引 project 里面,交易存索引 sales 等。
  • _type 就是类型,用于区分索引中的文档,即在索引中对数据逻辑分区。比如索引 project 的项目数据,根据项目类型 ui 项目、插画项目等进行区分。
  • _id 是该文档的唯一标示,代码中我们一 ID 作为他的唯一标示。

更具体的 Kibana 操作,具体操作操作即可。

最后继续打开 Postman 工具,调用下面搜索请求:

POST /api/content/search HTTP/1.1
Host: 127.0.0.1:8080
Content-Type: application/json
Cache-Control: no-cache
Postman-Token: 19e37f50-cb52-8527-c074-f91903eda6ae

{
    "searchContent":"Java",
    "type":2,
    "pageSize":3,
    "pageNumber":0
}

对应结果如下:

{
    "code": 0,
    "message": "success",
    "data": {
        "pageNumber": 0,
        "pageSize": 3,
        "totalPage": 1,
        "totalCount": 1,
        "result": [
            {
                "id": 3,
                "title": "《Java 编程思想》",
                "content": "Java 是世界上最diao的语言",
                "type": 2,
                "category": "计算",
                "read": 999,
                "support": 100
            }
        ]
    }
}

这里根据 searchContent 匹配短语 +type 匹配单个字段,一起构建了搜索语句。用于搜索出我们期待的结果,就是《Java 编程思想》。

最后,还是多多实践。代码 GitHub 地址:

https://github.com/JeffLi1993/springboot_es


拓展阅读: 《案例上手 Spring
全家桶》

本文首发于 GitChat,未经授权不得转载,转载需与 GitChat 联系。

130

向作者提问

互动评论

评论

李微尘7 个月前

按作者的流程来做 好多问题呀,按照第一个命令拉取容器启动,执行完成后就退出了


鼓掌
回复

评论

马鹿野郎7 个月前

ContentBean找了半天,还以为是某个库自带的方法呢,结果发现源码里面是ContentBean就是文章里面ContentEntity,真是坑,严谨点行吗


鼓掌
回复

评论

XiuWood8 个月前

es可用于持久化方式的存储吗


鼓掌
回复

评论

落雨7 个月前

持久化存储需要考虑数据的一致性,ES作为弱一致性NOSQL,允许部分数据非实时的情况下,是可以做为持久化存储来用的,不过一般来说,都会有一些补偿的程序去纠正ES的数据,背后还是一套Mysql

  • binLog同步的方式来做。由于ES的实时更新性能较差,很多人开始考虑转到MongoDB来做实时索引。不过ES内核可以优化,达到高性能实时更新

鼓掌回复

评论

绿乄茶8 个月前

es 本身就是一个数据库,应该可以的

鼓掌回复

评论

查看更多

更多资源下载交流请加微信:Morstrong,加入永久会员,网盘更新更快捷!

本资源由微信公众号:光明顶一号,提供支持