# ElasticSearch

ElasticSearch是一个基于Lucene的搜索服务器。它提供了一个分布式多用户能力的全文搜索引擎,基于RESTful web接口。ElasticSearch是用Java语言开发的,并作为Apache许可条款下的开放源码发布,是一种流行的企业级搜索引擎。

# 倒排索引

倒排索引是通过单词(或短语)快速定位到包含这些内容的文档列表的索引方式。它是基于内容的单词或短语来组织的,每个单词都对应一个倒排列表(Posting List),列出了包含该单词的所有文档的位置信息。倒排索引的结构较为复杂,需要维护一个词典和多个倒排列表,但在文档更新时只需更新相关的倒排列表,更新操作较为高效。

倒排索引原理

倒排索引原理 (opens new window)

# ES与MySQL的概念对比

MySQL ES 说明
Table表 Index索引 索引是相同类型的文档的集合。类比到传统的sql的话,索引的概念近似于sql中的表。
Row行 Document文档 ES是面向文档存储的,文档数据会被序列化为json格式的数据存储。类比到传统的sql的话,文档的概念近似于sql中的表数据。
Column列 Field字段 Field(字段),就是ES的文档(json字符串)中的字段,类比到Mysql就是数据表中列的概念。
Schema约束 Mapping映射 Mapping属性是对索引中文档的约束,常见的Mapping属性有type字段类型属性(属性值有字符串text以及keyword,其中text可分词,keyword不可分词,布尔值,日期,对象),index(是否创建索引,默认为true)。
SQL DSL DSL(Domain Specific Language领域特定语言)是es中的增删改查语法。类比到传统的sql的话,对应的就是sql的DDL语句。

# 索引库的Mapping映射

索引库的Mapping属性主要有type属性,index属性,analyzer分词器属性,properties子属性字段。

# type

type中常见的类型有字符串,数值,布尔类型,日期类型以及对象类型,其中字符串类型又分为text以及keyword,text指可分词的文本,而keyword则指不可分词的文本(如国家,品牌,ip地址信息)。数值类型可分为long,integer,double,float等。

# index

是否针对某属性创建索引,默认为true。

# analyzer

对某属性使用何种类型的分词器。

# properties

用于定义一个字段的子字段。

# 安装ES及Kibana

# 安装ES

1.拉取镜像

docker pull docker.elastic.co/elasticsearch/elasticsearch:8.1.0

2.启动服务

# 设置环境变量"discovery.type=single-node",这对于单节点的Elasticsearch实例是必要的。
#  -e ES_JAVA_OPTS="-Xms512m -Xmx512m" 指定512m的启动内存,不设置的话默认1G内存启动
docker run -d -e ES_JAVA_OPTS="-Xms512m -Xmx512m" --name elasticsearch -p 9200:9200 -p 9300:9300 -e "discovery.type=single-node" docker.elastic.co/elasticsearch/elasticsearch:8.1.0

3.修改es的默认配置

# 进入容器
[root@VM-24-3-centos ~]# docker exec -it 79d386cecbb5 /bin/bash
# 进入配置目录
elasticsearch@79d386cecbb5:~$ cd /usr/share/elasticsearch/config
# 通过vi指令修改配置,将关键配置xpack.security.enabled: true修改为xpack.security.enabled: false
elasticsearch@79d386cecbb5:~/config$ vi elasticsearch.yml

4.验证安装结果

[root@VM-24-3-centos ~]# curl -X GET http://127.0.0.1:9200/
{
  "name" : "79d386cecbb5",
  "cluster_name" : "docker-cluster",
  "cluster_uuid" : "H8RyVm7WR0iwJjJ07QJP7g",
  "version" : {
    "number" : "8.1.0",
    "build_flavor" : "default",
    "build_type" : "docker",
    "build_hash" : "3700f7679f7d95e36da0b43762189bab189bc53a",
    "build_date" : "2022-03-03T14:20:00.690422633Z",
    "build_snapshot" : false,
    "lucene_version" : "9.0.0",
    "minimum_wire_compatibility_version" : "7.17.0",
    "minimum_index_compatibility_version" : "7.0.0"
  },
  "tagline" : "You Know, for Search"
}

# 安装Kibana

1.拉取镜像

# kibana的版本需要和es保持一致
[root@VM-24-3-centos ~]# docker pull kibana:8.1.0

2.启动服务

[root@VM-24-3-centos ~]# docker run -d -e ELASTICSEARCH_HOSTS=http://139.199.188.50:9200/ --name=kibana -p 5601:5601 -e I18N_LOCALE="zh-CN" --log-opt max-size=100m --log-opt max-file=5 kibana:8.1.0

3.验证安装结果

# IK分词器

es在对文档创建倒排索引的时候需要进行分词,同样的,对于用户的检索条件,es也需要分词。es的默认分词器对中文的支持并不是很好,因此我们需要加装阿里云提供的IK分词器。
我们使用默认的分词器对一段中文进行分词,效果如下:

POST /_analyze
{
  # 分词器
  "analyzer": "standard",
  # 待分词的内容
  "text":"秉持自信而活,世人皆是罪之子"
}

# 分词结果,可以看到这个分词器的结果非常差,每个中文字符都被分成了一个“词”。
{
  "tokens" : [
    {
      "token" : "秉",
      "start_offset" : 0,
      "end_offset" : 1,
      "type" : "<IDEOGRAPHIC>",
      "position" : 0
    },
    {
      "token" : "持",
      "start_offset" : 1,
      "end_offset" : 2,
      "type" : "<IDEOGRAPHIC>",
      "position" : 1
    },
    {
      "token" : "自",
      "start_offset" : 2,
      "end_offset" : 3,
      "type" : "<IDEOGRAPHIC>",
      "position" : 2
    },
    {
      "token" : "信",
      "start_offset" : 3,
      "end_offset" : 4,
      "type" : "<IDEOGRAPHIC>",
      "position" : 3
    },
    {
      "token" : "而",
      "start_offset" : 4,
      "end_offset" : 5,
      "type" : "<IDEOGRAPHIC>",
      "position" : 4
    },
    {
      "token" : "活",
      "start_offset" : 5,
      "end_offset" : 6,
      "type" : "<IDEOGRAPHIC>",
      "position" : 5
    },
    {
      "token" : "世",
      "start_offset" : 7,
      "end_offset" : 8,
      "type" : "<IDEOGRAPHIC>",
      "position" : 6
    },
    {
      "token" : "人",
      "start_offset" : 8,
      "end_offset" : 9,
      "type" : "<IDEOGRAPHIC>",
      "position" : 7
    },
    {
      "token" : "皆",
      "start_offset" : 9,
      "end_offset" : 10,
      "type" : "<IDEOGRAPHIC>",
      "position" : 8
    },
    {
      "token" : "是",
      "start_offset" : 10,
      "end_offset" : 11,
      "type" : "<IDEOGRAPHIC>",
      "position" : 9
    },
    {
      "token" : "罪",
      "start_offset" : 11,
      "end_offset" : 12,
      "type" : "<IDEOGRAPHIC>",
      "position" : 10
    },
    {
      "token" : "之",
      "start_offset" : 12,
      "end_offset" : 13,
      "type" : "<IDEOGRAPHIC>",
      "position" : 11
    },
    {
      "token" : "子",
      "start_offset" : 13,
      "end_offset" : 14,
      "type" : "<IDEOGRAPHIC>",
      "position" : 12
    }
  ]
}

# 安装IK分词器

1.进入es容器中

[root@VM-24-3-centos ~]# docker exec -it 79d386cecbb5 /bin/bash

2.安装IK分词器

elasticsearch@79d386cecbb5:~$ ./bin/elasticsearch-plugin install https://github.com/infinilabs/analysis-ik/releases/download/v8.1.0/elasticsearch-analysis-ik-8.1.0.zip

3.重启es

elasticsearch@79d386cecbb5:~$ exit
[root@VM-24-3-centos ~]# docker restart 79d386cecbb5

4.测试ik分词器

# 测试ik分词器
POST /_analyze
{
  # 最少切分
  "analyzer": "ik_smart",
  "text":"秉持自信而活,世人皆是罪之子"
}

{
  "tokens" : [
    {
      "token" : "秉持",
      "start_offset" : 0,
      "end_offset" : 2,
      "type" : "CN_WORD",
      "position" : 0
    },
    {
      "token" : "自信",
      "start_offset" : 2,
      "end_offset" : 4,
      "type" : "CN_WORD",
      "position" : 1
    },
    {
      "token" : "而",
      "start_offset" : 4,
      "end_offset" : 5,
      "type" : "CN_CHAR",
      "position" : 2
    },
    {
      "token" : "活",
      "start_offset" : 5,
      "end_offset" : 6,
      "type" : "CN_CHAR",
      "position" : 3
    },
    {
      "token" : "世人",
      "start_offset" : 7,
      "end_offset" : 9,
      "type" : "CN_WORD",
      "position" : 4
    },
    {
      "token" : "皆是",
      "start_offset" : 9,
      "end_offset" : 11,
      "type" : "CN_WORD",
      "position" : 5
    },
    {
      "token" : "罪",
      "start_offset" : 11,
      "end_offset" : 12,
      "type" : "CN_CHAR",
      "position" : 6
    },
    {
      "token" : "之子",
      "start_offset" : 12,
      "end_offset" : 14,
      "type" : "CN_WORD",
      "position" : 7
    }
  ]
}

# 测试ik分词器
POST /_analyze
{
  # 最细切分
  "analyzer": "ik_max_word",
  "text":"秉持自信而活,世人皆是罪之子"
}

{
  "tokens" : [
    {
      "token" : "秉持",
      "start_offset" : 0,
      "end_offset" : 2,
      "type" : "CN_WORD",
      "position" : 0
    },
    {
      "token" : "自信",
      "start_offset" : 2,
      "end_offset" : 4,
      "type" : "CN_WORD",
      "position" : 1
    },
    {
      "token" : "而",
      "start_offset" : 4,
      "end_offset" : 5,
      "type" : "CN_CHAR",
      "position" : 2
    },
    {
      "token" : "活",
      "start_offset" : 5,
      "end_offset" : 6,
      "type" : "CN_CHAR",
      "position" : 3
    },
    {
      "token" : "世人",
      "start_offset" : 7,
      "end_offset" : 9,
      "type" : "CN_WORD",
      "position" : 4
    },
    {
      "token" : "皆是",
      "start_offset" : 9,
      "end_offset" : 11,
      "type" : "CN_WORD",
      "position" : 5
    },
    {
      "token" : "罪",
      "start_offset" : 11,
      "end_offset" : 12,
      "type" : "CN_CHAR",
      "position" : 6
    },
    {
      "token" : "之子",
      "start_offset" : 12,
      "end_offset" : 14,
      "type" : "CN_WORD",
      "position" : 7
    }
  ]
}

# 配置扩展词字典

1.进入es容器

[root@VM-24-3-centos ~]# docker exec -it 79d386cecbb5 /bin/bash

2.切换到ik分词器目录

elasticsearch@79d386cecbb5:~$ cd /usr/share/elasticsearch/plugins/.installing-7703281305028211551/config

3.修改配置文件,添加扩展词配置文件

elasticsearch@79d386cecbb5:~/plugins/.installing-7703281305028211551/config$ cat IKAnalyzer.cfg.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd">
<properties>
        <comment>IK Analyzer 扩展配置</comment>
        <!--用户可以在这里配置自己的扩展字典 -->
        <entry key="ext_dict">ext.dic</entry>
         <!--用户可以在这里配置自己的扩展停止词字典-->
        <entry key="ext_stopwords">stopword.dic</entry>
</properties>
# 查看扩展词
elasticsearch@79d386cecbb5:~/plugins/.installing-7703281305028211551/config$ cat ext.dic
罪之子

4.重启es使修改生效

[root@VM-24-3-centos ~]# docker restart 79d386cecbb5

5.验证修改是否生效

# 测试ik分词器
POST /_analyze
{
  "analyzer": "ik_smart",
  "text":"秉持自信而活,世人皆是罪之子"
}

{
  "tokens" : [
    {
      "token" : "秉持",
      "start_offset" : 0,
      "end_offset" : 2,
      "type" : "CN_WORD",
      "position" : 0
    },
    {
      "token" : "自信",
      "start_offset" : 2,
      "end_offset" : 4,
      "type" : "CN_WORD",
      "position" : 1
    },
    {
      "token" : "而",
      "start_offset" : 4,
      "end_offset" : 5,
      "type" : "CN_CHAR",
      "position" : 2
    },
    {
      "token" : "活",
      "start_offset" : 5,
      "end_offset" : 6,
      "type" : "CN_CHAR",
      "position" : 3
    },
    {
      "token" : "世人",
      "start_offset" : 7,
      "end_offset" : 9,
      "type" : "CN_WORD",
      "position" : 4
    },
    {
      "token" : "皆是",
      "start_offset" : 9,
      "end_offset" : 11,
      "type" : "CN_WORD",
      "position" : 5
    },
    {
      # 可以看到修改生效,这个词能检索到了
      "token" : "罪之子",
      "start_offset" : 11,
      "end_offset" : 14,
      "type" : "CN_WORD",
      "position" : 6
    }
  ]
}

# 配置停止词字典

1.进入es容器

[root@VM-24-3-centos ~]# docker exec -it 79d386cecbb5 /bin/bash

2.切换到ik分词器目录

elasticsearch@79d386cecbb5:~$ cd /usr/share/elasticsearch/plugins/.installing-7703281305028211551/config

3.修改配置文件,添加扩展词配置文件

elasticsearch@79d386cecbb5:~/plugins/.installing-7703281305028211551/config$ cat IKAnalyzer.cfg.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd">
<properties>
        <comment>IK Analyzer 扩展配置</comment>
        <!--用户可以在这里配置自己的扩展字典 -->
        <entry key="ext_dict">ext.dic</entry>
         <!--用户可以在这里配置自己的扩展停止词字典-->
        <entry key="ext_stopwords">stopword.dic</entry>
</properties>

elasticsearch@79d386cecbb5:~$ cd /usr/share/elasticsearch/plugins/.installing-7703281305028211551/config

elasticsearch@79d386cecbb5:~/plugins/.installing-7703281305028211551/config$ cat IKAnalyzer.cfg.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd">
<properties>
        <comment>IK Analyzer 扩展配置</comment>
        <!--用户可以在这里配置自己的扩展字典 -->
        <entry key="ext_dict">ext.dic</entry>
         <!--用户可以在这里配置自己的扩展停止词字典-->
        <entry key="ext_stopwords">stopword.dic</entry>
</properties>
# 查看停用词汇
elasticsearch@79d386cecbb5:~/plugins/.installing-7703281305028211551/config$ cat stopword.dic
罪
之子

4.重启es使修改生效

[root@VM-24-3-centos ~]# docker restart 79d386cecbb5

5.验证修改是否生效

# 测试ik分词器
POST /_analyze
{
  "analyzer": "ik_smart",
  "text":"秉持自信而活,世人皆是罪之子"
}

{
  "tokens" : [
    {
      # 可以看到,“罪”,“之子”这两个之前无意义的分词已经查询不到了
      "token" : "秉持",
      "start_offset" : 0,
      "end_offset" : 2,
      "type" : "CN_WORD",
      "position" : 0
    },
    {
      "token" : "自信",
      "start_offset" : 2,
      "end_offset" : 4,
      "type" : "CN_WORD",
      "position" : 1
    },
    {
      "token" : "而",
      "start_offset" : 4,
      "end_offset" : 5,
      "type" : "CN_CHAR",
      "position" : 2
    },
    {
      "token" : "活",
      "start_offset" : 5,
      "end_offset" : 6,
      "type" : "CN_CHAR",
      "position" : 3
    },
    {
      "token" : "世人",
      "start_offset" : 7,
      "end_offset" : 9,
      "type" : "CN_WORD",
      "position" : 4
    },
    {
      "token" : "皆是",
      "start_offset" : 9,
      "end_offset" : 11,
      "type" : "CN_WORD",
      "position" : 5
    },
    {
      "token" : "罪之子",
      "start_offset" : 11,
      "end_offset" : 14,
      "type" : "CN_WORD",
      "position" : 6
    }
  ]
}

# 索引库

# 创建索引库

PUT /my_index
{
  "mappings":{
    "properties": {
      "info":{
        "type":"text",
        "analyzer": "ik_smart"
      },
      "email":{
        "type":"keyword",
        "index": false
      }
    }
  }
}
# 执行结果
{
  "acknowledged" : true,
  "shards_acknowledged" : true,
  "index" : "my_index"
}
java代码中创建索引库

1.引入es相关依赖

<dependencies>
  <dependency>
    <groupId>org.elasticsearch.client</groupId>
    <artifactId>elasticsearch-rest-high-level-client</artifactId>
    <version>8.1.0</version>
  </dependency>
</dependencies>

2.初始化RestHighLevelClient对象

RestHighLevelClient client = new RestHighLevelClient(RestClient.builder(HttpHost.create("http://139.199.188.50:9200")));

3.创建索引库

// 创建索引库
CreateIndexRequest indexRequest = new CreateIndexRequest("my_index");
// 设置索引库的Mapping属性
indexRequest.source("mapping_str",XContentType.JSON);
// 发起索引库创建请求,这里的client指的是上一步创建的RestHighLevelClient对象
client.indices().create(indexRequest,RequestOptions.DEFAULT);

# 查询索引库

GET /my_index
# 执行结果
{
  "my_index" : {
    "aliases" : { },
    "mappings" : {
      "properties" : {
        "email" : {
          "type" : "keyword",
          "index" : false
        },
        "info" : {
          "type" : "text",
          "analyzer" : "ik_smart"
        }
      }
    },
    "settings" : {
      "index" : {
        "routing" : {
          "allocation" : {
            "include" : {
              "_tier_preference" : "data_content"
            }
          }
        },
        "number_of_shards" : "1",
        "provided_name" : "my_index",
        "creation_date" : "1725631622678",
        "number_of_replicas" : "1",
        "uuid" : "951nP3GPQ_i5ImlGPVxiLg",
        "version" : {
          "created" : "8010099"
        }
      }
    }
  }
}
java代码中查询索引库

此处省略引入es相关依赖及初始化初始化RestHighLevelClient对象流程。
1.查询索引库

// 查询索引库
GetIndexRequest indexRequest = new GetIndexRequest("my_index");
// 发起索引库查询请求,这里的client指的是上一步创建的RestHighLevelClient对象
boolean exists = client.indices().exist(indexRequest,RequestOptions.DEFAULT);

# 删除索引库

DELETE /my_index

# 执行结果
{
  "acknowledged" : true
}
java代码中删除索引库

此处省略引入es相关依赖及初始化初始化RestHighLevelClient对象流程。
1.删除索引库

// 删除索引库
DeleteIndexRequest indexRequest = new DeleteIndexRequest("my_index");
// 发起索引库删除请求,这里的client指的是上一步创建的RestHighLevelClient对象
client.indices().delete(indexRequest,RequestOptions.DEFAULT);

# 修改索引库

es中索引库不允许修改,可以先查询,再删除,最后重新创建索引。此外,es中允许对现有索引库增加新字段,如下:

PUT /my_index/_mapping
{
 "properties": {
    "introduction":{
      "type":"text",
      "analyzer": "ik_smart"
    }
  }
}

# 执行结果
{
  "acknowledged" : true
}

# 文档

# 创建文档

POST /my_index/_doc/5242f585-d92c-9580-17dd-f2bd4eb82fe5
{
  "info":"howl,30,boy",
  "introduction":"一个乐观的小男孩",
  "email":"504070596@qq.com"
}

# 执行结果
{
  "_index" : "my_index",
  "_id" : "5242f585-d92c-9580-17dd-f2bd4eb82fe5",
  "_version" : 1,
  "result" : "created",
  "_shards" : {
    "total" : 2,
    "successful" : 1,
    "failed" : 0
  },
  "_seq_no" : 0,
  "_primary_term" : 1
}
java代码中新增文档

此处省略引入es相关依赖及初始化初始化RestHighLevelClient对象流程。
1.新增文档

// 新增文档
IndexRequest request = new IndexRequest("my_index").id("1");
// 准备JSON文档
request.source("{\"name\":\"Jack\"}",XContentType.JSON);
// 发送请求
client.index(request,RequestOptions.DEFAULT);
java代码中批量新增文档

此处省略引入es相关依赖及初始化初始化RestHighLevelClient对象流程。
1.批量新增文档

// 批量新增文档
BulkRequest request = new BulkRequest();
// 增加第一条数据
request.add(new IndexRequest("my_index").id("1").source("{\"name\":\"Jack\"}",XContentType.JSON));
// 增加第二条数据
request.add(new IndexRequest("my_index").id("2").source("{\"name\":\"Tony\"}",XContentType.JSON));
// 发送请求
client.bulk(request,RequestOptions.DEFAULT);

# 查询文档

GET /my_index/_doc/5242f585-d92c-9580-17dd-f2bd4eb82fe5

# 执行结果
{
  "_index" : "my_index",
  "_id" : "5242f585-d92c-9580-17dd-f2bd4eb82fe5",
  "_version" : 1,
  "_seq_no" : 0,
  "_primary_term" : 1,
  "found" : true,
  "_source" : {
    "info" : "howl,30,boy",
    "introduction" : "一个乐观的小男孩",
    "email" : "504070596@qq.com"
  }
}
java代码中查询文档

此处省略引入es相关依赖及初始化初始化RestHighLevelClient对象流程。
1.查询文档

// 通过索引名以及文档id查询文档
GetRequest request = new GetRequest("my_index","1");
// 发送请求,获取返回结果
GetResponse response = client.index(request,RequestOptions.DEFAULT);
// 解析返回结果
String str = response.getSourceAsString();
// 输出结果
System.out.println(str);

# 删除文档

DELETE /my_index/_doc/5242f585-d92c-9580-17dd-f2bd4eb82fe5

# 执行结果
{
  "_index" : "my_index",
  "_id" : "5242f585-d92c-9580-17dd-f2bd4eb82fe5",
  "_version" : 2,
  "result" : "deleted",
  "_shards" : {
    "total" : 2,
    "successful" : 1,
    "failed" : 0
  },
  "_seq_no" : 1,
  "_primary_term" : 1
}
java代码中删除文档

此处省略引入es相关依赖及初始化初始化RestHighLevelClient对象流程。
1.删除文档

// 通过索引名以及文档id删除文档
DeleteRequest request = new DeleteRequest("my_index","1");
// 发送请求,执行删除操作
client.delete(request,RequestOptions.DEFAULT);

# 修改文档

# 全量修改

PUT /my_index/_doc/5242f585-d92c-9580-17dd-f2bd4eb82fe5
{
  "info":"howl,30,boy",
  "introduction":"一个乐观的小男孩",
  "email":"504070596@qq.com"
}

# 执行结果
{
  "_index" : "my_index",
  "_id" : "5242f585-d92c-9580-17dd-f2bd4eb82fe5",
  "_version" : 5,
  "result" : "updated",
  "_shards" : {
    "total" : 2,
    "successful" : 1,
    "failed" : 0
  },
  "_seq_no" : 4,
  "_primary_term" : 1
}

# 增量修改

POST /my_index/_update/5242f585-d92c-9580-17dd-f2bd4eb82fe5
{
  "doc":{
    "info":"howl,31,boy"
  }
}

# 执行结果
{
  "_index" : "my_index",
  "_id" : "5242f585-d92c-9580-17dd-f2bd4eb82fe5",
  "_version" : 6,
  "result" : "updated",
  "_shards" : {
    "total" : 2,
    "successful" : 1,
    "failed" : 0
  },
  "_seq_no" : 5,
  "_primary_term" : 1
}
java代码中增量修改文档

此处省略引入es相关依赖及初始化初始化RestHighLevelClient对象流程。
1.修改文档

// 通过索引名以及文档id修改文档
UpdateRequest request = new UpdateRequest("my_index","1");
// 准备参数,每两个参数对应一对key-value
request.doc("age",31,"name","howl");
// 发送请求,执行更新操作
client.update(request,RequestOptions.DEFAULT);

# DSL查询

# 查询所有

# 可以查询出该索引下的所有数据,一般用作测试,不做生产业务使用
GET /my_index/_search
{
  "query":{
    "match_all":{
    }
  }
}

# 执行结果
{
  "took" : 1,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 1,
      "relation" : "eq"
    },
    "max_score" : 1.0,
    "hits" : [
      {
        "_index" : "my_index",
        "_id" : "5242f585-d92c-9580-17dd-f2bd4eb82fe5",
        "_score" : 1.0,
        "_source" : {
          "info" : "howl,31,boy",
          "introduction" : "一个乐观的小男孩",
          "email" : "504070596@qq.com"
        }
      }
    ]
  }
}
java代码中查询所有文档

此处省略引入es相关依赖及初始化初始化RestHighLevelClient对象流程。
1.查询所有文档

// 准备Request
SearchRequest request = new SearchRequest("my_index");
// 封装DSL语句
request.source().query(QueryBuilders.matchAllQuery());
// 发送请求,获取返回结果
SearchResponse response = client.search(request,RequestOptions.DEFAULT);
// ...解析返回结果

# 全文检索查询

利用分词器对用户输入内容进行分词,然后去倒排索引库中匹配。

# match查询

match查询会对用户输入内容进行分词,然后去倒排索引库查询。

GET /my_index/_search
{
  "query":{
    "match":{
      "introduction":"乐观的小男孩"
    }
  }
}

# 执行结果
{
  "took" : 4,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 1,
      "relation" : "eq"
    },
    "max_score" : 0.8630463,
    "hits" : [
      {
        "_index" : "my_index",
        "_id" : "5242f585-d92c-9580-17dd-f2bd4eb82fe5",
        "_score" : 0.8630463,
        "_source" : {
          "info" : "howl,31,boy",
          "introduction" : "一个乐观的小男孩",
          "email" : "504070596@qq.com"
        }
      }
    ]
  }
}
java代码中全文检索文档

此处省略引入es相关依赖及初始化初始化RestHighLevelClient对象流程。
1.全文检索文档

// 准备Request
SearchRequest request = new SearchRequest("my_index");
// 封装DSL语句
request.source().query(QueryBuilders.matchQuery("introduction","乐观的小男孩"));
// 发送请求,获取返回结果
SearchResponse response = client.search(request,RequestOptions.DEFAULT);
// ...解析返回结果

# multi_match查询

根据多个字段查询,参与查询字段越多,性能越差。

GET /my_index/_search
{
  "query":{
    "multi_match":{
      "query":"乐观",
      "fields": ["info","introduction"]
    }
  }
}

# 查询结果
{
  "took" : 0,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 1,
      "relation" : "eq"
    },
    "max_score" : 0.2876821,
    "hits" : [
      {
        "_index" : "my_index",
        "_id" : "5242f585-d92c-9580-17dd-f2bd4eb82fe5",
        "_score" : 0.2876821,
        "_source" : {
          "info" : "howl,31,boy",
          "introduction" : "一个乐观的小男孩",
          "email" : "504070596@qq.com"
        }
      }
    ]
  }
}

# 精准查询

# 根据精确词条值查找数据,一般是查找keyword,数值,日期,boolean等类型值,索引不会对检索条件进行分词。
GET /my_index/_search
{
  "query":{
    # 除了trem查询,我们还可以使用range查询,range会框定查询的范围
    "term":{
      "email":"504070596@qq.com"
    }
  }
}
# 查询结果
{
  "took" : 0,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 1,
      "relation" : "eq"
    },
    "max_score" : 1.0,
    "hits" : [
      {
        "_index" : "my_index",
        "_id" : "5242f585-d92c-9580-17dd-f2bd4eb82fe5",
        "_score" : 1.0,
        "_source" : {
          "info" : "howl,31,boy",
          "introduction" : "一个乐观的小男孩",
          "email" : "504070596@qq.com"
        }
      }
    ]
  }
}
java代码中精准查询文档

此处省略引入es相关依赖及初始化初始化RestHighLevelClient对象流程。
1.termQuery

// 准备Request
SearchRequest request = new SearchRequest("my_index");
// 封装DSL语句
request.source().query(QueryBuilders.termQuery("email","504070596@qq.com"));
// 发送请求,获取返回结果
SearchResponse response = client.search(request,RequestOptions.DEFAULT);
// ...解析返回结果

2.rangeQuery

// 准备Request
SearchRequest request = new SearchRequest("my_index");
// 封装DSL语句
request.source().query(QueryBuilders.rangeQuery("price").gte(100).lte(150));
// 发送请求,获取返回结果
SearchResponse response = client.search(request,RequestOptions.DEFAULT);
// ...解析返回结果

# 复合查询

复合查询可以将其他简单查询组合起来,实现更复杂的搜索逻辑。如算分函数查询function score,

# 算分函数查询

可以控制文档相关性算分,控制文档排名。

GET /my_index/_search
{
  "query": {
    "function_score": {
      # 原始的查询条件
      "query": {
        "match": {
          "introduction": "乐观的小男孩"
        }
      },
      "functions": [
        {
          # 过滤条件,符合条件的才会被重新算分
          "filter": {
            "term": {
              "id": "5242f585-d92c-9580-17dd-f2bd4eb82fe5"
            }
          },
          # 算分函数,算分函数的结果称为function score,给定一个常量值作为算分结果,将来会与query score运算,得到新算分
          "weight": 10
        }
      ],
      # 加权模式,定义function score与query score的运算方式,multiply表示两者相乘,replace表示用function score替换掉query score
      "boost_mode": "multiply"
    }
  }
}

# 执行结果
{
  "took" : 122,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 1,
      "relation" : "eq"
    },
    "max_score" : 0.8630463,
    "hits" : [
      {
        "_index" : "my_index",
        "_id" : "5242f585-d92c-9580-17dd-f2bd4eb82fe5",
        "_score" : 0.8630463,
        "_source" : {
          "info" : "howl,30,boy",
          "introduction" : "一个乐观的小男孩",
          "email" : "504070596@qq.com"
        }
      }
    ]
  }
}

# 布尔查询

布尔查询是一个或多个查询子句的组合。子查询的组合方式有must(必须匹配每个子查询),should(选择性匹配子查询),must_not(必须不匹配,不参与算分),filter(必须匹配,不参与算分)。

GET /my_index/_search
{
  "query": {
    "bool": {
      "must": [
        {
          "term": {
            "introduction": "乐观"
          }
        }
      ],
      "should": [
        {
          "term": {
            "introduction": "小男孩"
          }
        }
      ]
    }
  }
}
# 查询结果
{
  "took" : 2,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 1,
      "relation" : "eq"
    },
    "max_score" : 0.5753642,
    "hits" : [
      {
        "_index" : "my_index",
        "_id" : "5242f585-d92c-9580-17dd-f2bd4eb82fe5",
        "_score" : 0.5753642,
        "_source" : {
          "info" : "howl,30,boy",
          "introduction" : "一个乐观的小男孩",
          "email" : "504070596@qq.com"
        }
      }
    ]
  }
}
java代码中bool查询

此处省略引入es相关依赖及初始化初始化RestHighLevelClient对象流程。
1.bool查询文档

// 准备Request
SearchRequest request = new SearchRequest("my_index");
// 创建bool查询
BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
// 添加must条件
boolQuery.must(QueryBuilders.termQuery("city","杭州"));
// 封装DSL语句
request.source().query(boolQuery);
// 发送请求,获取返回结果
SearchResponse response = client.search(request,RequestOptions.DEFAULT);
// ...解析返回结果

# 查询结果排序

GET /my_index/_search
{
  "query":{
    "term":{
      "email":"504070596@qq.com"
    }
  },
  "sort": [
    {
      "email": {
        "order": "desc"
      }
    }
  ]
}
# 查询结果
{
  "took" : 0,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 1,
      "relation" : "eq"
    },
    "max_score" : null,
    "hits" : [
      {
        "_index" : "my_index",
        "_id" : "5242f585-d92c-9580-17dd-f2bd4eb82fe5",
        "_score" : null,
        "_source" : {
          "info" : "howl,31,boy",
          "introduction" : "一个乐观的小男孩",
          "email" : "504070596@qq.com"
        },
        "sort" : [
          "504070596@qq.com"
        ]
      }
    ]
  }
}
java代码中对查询结果排序

此处省略引入es相关依赖及初始化初始化RestHighLevelClient对象流程。
1.查询结果排序

// 准备Request
SearchRequest request = new SearchRequest("my_index");
// 封装DSL语句
request.source().query(QueryBuilders.matchQuery("introduction","乐观的小男孩"));
// 排序
request.source().sort("price",SortOrder.ASC);
// 发送请求,获取返回结果
SearchResponse response = client.search(request,RequestOptions.DEFAULT);
// ...解析返回结果

# 查询结果分页

GET /my_index/_search
{
  "query":{
    "term":{
      "email":"504070596@qq.com"
    }
  },
  "from":0,
  "size":20,
  "sort": [
    {
      "email": {
        "order": "desc"
      }
    }
  ]
}
# 默认的分页参数是10条数据一页,可以进行修改
{
  "took" : 0,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 1,
      "relation" : "eq"
    },
    "max_score" : null,
    "hits" : [
      {
        "_index" : "my_index",
        "_id" : "5242f585-d92c-9580-17dd-f2bd4eb82fe5",
        "_score" : null,
        "_source" : {
          "info" : "howl,31,boy",
          "introduction" : "一个乐观的小男孩",
          "email" : "504070596@qq.com"
        },
        "sort" : [
          "504070596@qq.com"
        ]
      }
    ]
  }
}
java代码中对查询结果分页

此处省略引入es相关依赖及初始化初始化RestHighLevelClient对象流程。
1.查询结果分页

// 准备Request
SearchRequest request = new SearchRequest("my_index");
// 封装DSL语句
request.source().query(QueryBuilders.matchQuery("introduction","乐观的小男孩"));
// 分页
request.source().from(0).size(5);
// 发送请求,获取返回结果
SearchResponse response = client.search(request,RequestOptions.DEFAULT);
// ...解析返回结果
ES中深度分页问题

ES天生支持集群,在集群环境下,我们会面临深度分页问题,如对查询结果根据价格排序,查询第50到60条数据,那么就需要把集群中每台机器的前面60条数据找出来,然后进行归并排序后再找到第50到60条数据。
考虑类似的情况,ES默认仅支持查询10000条数据。(像百度,京东等大型互联网应用也有类似问题,都是通过限制查询总数来解决的)

# 查询结果高亮显示

GET /my_index/_search
{
  "query":{
    "match":{
      "introduction":"乐观的小男孩"
    }
  },
  "from":0,
  "size":20,
  "sort": [
    {
      "email": {
        "order": "desc"
      }
    }
  ],
  "highlight": {
    "fields": {
      "introduction": {
        "pre_tags": "<em>",
        "post_tags": "</em>"
      }
    }
  }
}
# 查询结果
{
  "took" : 1,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 1,
      "relation" : "eq"
    },
    "max_score" : null,
    "hits" : [
      {
        "_index" : "my_index",
        "_id" : "5242f585-d92c-9580-17dd-f2bd4eb82fe5",
        "_score" : null,
        "_source" : {
          "info" : "howl,31,boy",
          "introduction" : "一个乐观的小男孩",
          "email" : "504070596@qq.com"
        },
        "highlight" : {
          "introduction" : [
            "一个<em>乐观</em><em>的</em><em>小男孩</em>"
          ]
        },
        "sort" : [
          "504070596@qq.com"
        ]
      }
    ]
  }
}
java代码中对查询结果高亮显示

此处省略引入es相关依赖及初始化初始化RestHighLevelClient对象流程。
1.查询结果高亮显示

// 准备Request
SearchRequest request = new SearchRequest("my_index");
// 封装DSL语句
request.source().query(QueryBuilders.matchQuery("introduction","乐观的小男孩"));
// 高亮显示
request.source().highlighter(
  new HighLighterBuilder().
    field("name").
    // 是否需要与查询字段匹配
    requireFieldMatch(false)
);
// 发送请求,获取返回结果
SearchResponse response = client.search(request,RequestOptions.DEFAULT);
// ...解析返回结果,需要特别注意,针对高亮字段,我们需要从结果里拿到highlight属性,这里才是对应的高亮结果

# 数据聚合

聚合指的是对数据进行汇总分析。ES中有三类聚合,分别是Bucket聚合(桶聚合),Metrics聚合(度量聚合,如求平均值,最大值等)以及Pipline管道聚合(类似linux中的pipline | ,基于其他聚合结果再做聚合)。参与聚合的字段必须是keyword,bool,日期类型。

# Bucket聚合

GET /my_index/_search
{
  "size":0, //定义size为0,结果中不包含文档,只包含聚合结果
  "aggs":{// 定义聚合
    "brandAgg":{// 给聚合起个名字
      "terms":{ // 聚合的类型
        "field": "brand", //参与聚合的字段
        "size" : 20 // 希望获取的聚合结果数量
      }
    }
  }
}
java代码实现Bucket聚合

此处省略引入es相关依赖及初始化初始化RestHighLevelClient对象流程。
1.Bucket聚合

// 准备Request
SearchRequest request = new SearchRequest("my_index");
// 封装DSL语句
request.source().size(0);
// 封装DSL语句
request.source().aggregation(
  AggregationBuilders
    .terms("brand_agg")
    .field("brand")
    .size(20);
);
// 发送请求,获取返回结果
SearchResponse response = client.search(request,RequestOptions.DEFAULT);
// ...解析返回结果,需要特别注意,针对聚合结果接下,我们需要从结果的对应位置获取数据

# Metrics聚合

GET /my_index/_search
{
  "size":0, //定义size为0,结果中不包含文档,只包含聚合结果
  "aggs":{// 定义聚合
    "brandAgg":{// 给聚合起个名字
      "terms":{ // 聚合的类型
        "field": "brand", //参与聚合的字段
        "size" : 20 // 希望获取的聚合结果数量
      },
      "aggs": { // barnd聚合的子聚合,也就是分组后对每组分别计算
        "score_stats" : { // 聚合名称
          "stats":{ // 聚合类型,这里可以设置min,max,avg等
            "field":"score" // 聚合字段,这里是score
          }
        }
      }
    }
  }
}

# 自动补全

# 拼音分词器

安装Pinyin Analysis Plugin即可,插件安装方式与IK中文分词器类似。同样的安装完成后,我们可以通过analyze进行验证:

# 测试pinyin分词器,需要注意,拼音分词器仅支持拼音,不支持中文。
POST /_analyze
{
  "analyzer": "pinyin",
  "text":"秉持自信而活,世人皆是罪之子"
}

# 自定义分词器

字段在创建倒排索引的时候使用自定义分词器,在检索时使用ik_smart分词器。相关索引库创建语句如下:

PUT /my_index
{
  "settings": {
    "analysis": {
      "analyzer" : {
        "my_analyzer": {
          "tokenizer":"ik_max_word","filter":"py"
        }
      }
    }
  },
  "mappings": {
    "properties": {
      "name": {
        "type":"text",
        "analyzer" : "my_analyzer", // 在创建倒排索引的时候使用自定义分词器
        "search_analyer": "ik_smart" // 在检索时使用ik_smart分词器
      }
    }
  }
}

# 自动补全查询

1.创建索引库

POST test
{
  "mappings":{
    "properties": {
      "title":{
        "type":"completion"
      }
    }
  }
}

2.索引库中添加文档

POST test/_doc
{
  "title":["Honor","K30"]
}

3.自动补全查询

GET test/_search
{
  "suggest": {
    "title_suggest": {
      "text":"s", //关键字
      "completion": {
        "field": "text",// 补全查询的字段
        "skip_duplicates" : true, //跳过重复的
        "size":10 // 获取前10条结果
      }
    }
  }
}

# MySQL与ES的数据同步

通过引入MQ,实现MySQL与ES的数据同步。这里无需过多介绍。

# ES集群

# ES集群搭建

1.节点1配置

docker run -d \
--name=es_node_1 \
--restart=always \
-p 9201:9200 \
-p 9301:9300 \
--privileged=true \
-v /usr/local/es_cluster/node_1/data:/usr/share/elasticsearch/data \
-v /usr/local/es_cluster/node_1/logs:/usr/share/elasticsearch/logs \
-v /usr/local/es_cluster/node_1/plugins:/usr/share/elasticsearch/plugins \
-e "cluster.name=my-cluster" \
-e "node.name=node-1" \
-e "node.master=true" \
-e "node.data=true" \
-e "network.host=0.0.0.0" \
-e "transport.tcp.port=9300" \
-e "http.port=9200" \
-e "cluster.initial_master_nodes=node-1" \
-e "discovery.seed_hosts=192.168.1.100:9301,192.168.1.100:9302,192.168.1.100:9303" \
-e "gateway.auto_import_dangling_indices=true" \
-e "http.cors.enabled=true" \
-e "http.cors.allow-origin=*" \
-e "ES_JAVA_OPTS=-Xms512m -Xmx512m" \
-e "TAKE_FILE_OWNERSHIP=true" \
elasticsearch:7.9.3

# 各环境变量业务含义说明
cluster.name:配置es集群名称,默认为elasticsearch,同一网段下的不同ES集群通过集群名来区分;
node.name:节点名称,一个或多个节点组成一个cluster集群,集群是一个逻辑的概念,节点是物理概念
node.master:是否可以成为master节点
node.data:是否允许该节点存储数据,默认开启
network.host:绑定主机的ip地址,允许外网访问,同一网段下的ES会自动加入该集群中
transport.tcp.port:设置集群节点之间交互的tcp端口
http.port:设置http端口
cluster.initial_master_nodes:设置哪些ES节点参与第一次master节点选举,其值可以是ES节点IP地址(如:192.168.1.100或192.168.1.100:9300),也可以是ES节点名称
discovery.seed_hosts:设置当前节点与哪些ES节点建立连接,其值可以是127.0.0.1
gateway.auto_import_dangling_indices:是否自动引入dangling索引,默认false
#支持跨域访问:kibana或elasticsearch-head插件作为客户端要连接ES服务(http://192.168.1.100:9200),此时存在跨域问题,elasticsearch默认不允许跨域访问
http.cors.enabled:开启cors跨域访问支持,默认为false
http.cors.allow-origin:跨域访问允许的域名地址:允许所有域名
ES_JAVA_OPTS=-Xms512m -Xmx512m:ES使用Java语言开发,默认jvm为2G,这里通过ES_JAVA_OPTS设置jvm最大最小内存。

2.节点2配置

docker run -d \
--name=es_node_2 \
--restart=always \
-p 9202:9200 \
-p 9302:9300 \
--privileged=true \
-v /usr/local/es_cluster/node_2/data:/usr/share/elasticsearch/data \
-v /usr/local/es_cluster/node_2/logs:/usr/share/elasticsearch/logs \
-v /usr/local/es_cluster/node_2/plugins:/usr/share/elasticsearch/plugins \
-e "cluster.name=my-cluster" \
-e "node.name=node-2" \
-e "node.master=true" \
-e "node.data=true" \
-e "network.host=0.0.0.0" \
-e "transport.tcp.port=9300" \
-e "http.port=9200" \
-e "discovery.seed_hosts=192.168.1.100:9301,192.168.1.100:9302,192.168.1.100:9303" \
-e "gateway.auto_import_dangling_indices=true" \
-e "http.cors.enabled=true" \
-e "http.cors.allow-origin=*" \
-e "ES_JAVA_OPTS=-Xms512m -Xmx512m" \
-e "TAKE_FILE_OWNERSHIP=true" \
elasticsearch:7.9.3

3.节点3配置

docker run -d \
--name=es_node_3 \
--restart=always \
-p 9203:9200 \
-p 9303:9300 \
--privileged=true \
-v /usr/local/es_cluster/node_3/data:/usr/share/elasticsearch/data \
-v /usr/local/es_cluster/node_3/logs:/usr/share/elasticsearch/logs \
-v /usr/local/es_cluster/node_3/plugins:/usr/share/elasticsearch/plugins \
-e "cluster.name=my-cluster" \
-e "node.name=node-3" \
-e "node.master=true" \
-e "node.data=true" \
-e "network.host=0.0.0.0" \
-e "transport.tcp.port=9300" \
-e "http.port=9200" \
-e "discovery.seed_hosts=192.168.1.100:9301,192.168.1.100:9302,192.168.1.100:9303" \
-e "gateway.auto_import_dangling_indices=true" \
-e "http.cors.enabled=true" \
-e "http.cors.allow-origin=*" \
-e "ES_JAVA_OPTS=-Xms512m -Xmx512m" \
-e "TAKE_FILE_OWNERSHIP=true" \
elasticsearch:7.9.3

# 集群状态监控

kibana默认只能访问集群的某一个节点,如果需要kibana支持ES集群访问,需要做很多配置,因此我们使用cerebro来监控ES集群的状态,cerebro的使用非常简单,只需解压缩即可使用。默认情况下登录后,需要输入ES集群中任一节点的地址,cerebro即可监控该节点所对应的ES集群。

# 在集群中对索引库进行分片

PUT /my_index
{
  "settings": {
    "number_of_shards":3,//分片数量
    "number_of_replicas":1//每个分片的备份的数量
  },
  "mappings": {
    "properties": {
      "name": {
        "type":"text",
        "analyzer" : "my_analyzer", // 在创建倒排索引的时候使用自定义分词器
        "search_analyer": "ik_smart" // 在检索时使用ik_smart分词器
      }
    }
  }
}

# ES集群的节点角色

节点类型 配置参数 默认值 节点职责
master eligible node.master true 备选主节点:主节点可以管理和记录集群状态,决定分片在哪个节点,处理创建和删除索引库的请求
data node.data true 数据节点:存储数据,搜索,聚合,增删改查
ingest node.ingest true 数据存储之前的预处理
coordinating 以上三个参数都为false,则为coordinating 路由请求到其他节点,合并其他节点处理的结果,返回给用户

# 什么是脑裂,ES如何规避

# 什么是脑裂

假定有3台机器的ES集群,A机器为主,B,C机器为从,当A与B,C之间通信网络出现问题(A机器并没有宕机),此时B,C会有一台机器成为主,当网络恢复后,集群中就有两个主节点,且都写入了数据,此时就出现了脑裂问题。

# 如何规避脑裂

ES规避脑裂的方式也很简单,在重选主节点的时候进行投票,只有投票数超过(节点数+1)/2,才能当选,如3台机器,主节点异常,另外2台机器的选举票数需要超过(3+1)/2,即超过2(包含2)台机器,因此,A节点会自动降级,禁止相关写操作,B或C机器会有一台机器成为主节点,等网络恢复后,A变成从节点加入到BC构成的主从集群中。

# 如何查看ES中有哪些索引?涉及索引重建,如何确认是否完成?

  1. 通过ES的API可以查看ES中都有哪些索引。
  2. 涉及索引重建,通过查询API不加条件可以查询到当前索引中有多少文档document,索引重建后的document应和索引重建前接近。