ElasticSearch 笔记

ElasticSearch是一个基于 Lucene 的分布式搜索引擎,业内简称ES。它提供了基于 RESTful 风格的全文搜索API。Elasticsearch 是用 Java 开发的,并作为 Apache 许可条款下的开放源码发布,是当前最流行的企业级搜索引擎。另外,它的分布式设计让它天生就适合用于云计算中,并能够达到准实时搜索,而且安装使用方便,还拥有稳定,可靠,快速等特性。本文是我对 ES 的一个完整记录,方便后期查阅。另外大家还可以查阅更多的相关资料对 ElasticSearch 有更深入的了解。(备注:本文未完待续。。。)

安装

单机安装

ElasticSearch 要求本地的 JDK 版本不低于1.8

  1. 去官网下载最新版的 ES 软件包 https://www.elastic.co/downloads/elasticsearch ,目前 ES 最新版本为6.1.1,我这里采用 wget 命令在终端下载

    1
    2
    cd apps
    wget https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-6.1.1.tar.gz
  2. 如果出现 command not found: wget,如果是 Linux,可以用 yum install -y wget 下载,如果是 Mac 就可以用 brew install wget 下载

  3. 检查本地 JDK 版本,必须保证为1.8版本以上

    1
    2
    3
    4
    $ java -version
    java version "1.8.0_131"
    Java(TM) SE Runtime Environment (build 1.8.0_131-b11)
    Java HotSpot(TM) 64-Bit Server VM (build 25.131-b11, mixed mode)
  4. 解压软件包

    1
    tar -zxvf elasticsearch-6.1.1.tar.gz
  5. 启动 ES

    1
    2
    cd elasticsearch-6.1.1
    ./bin/elasticsearch
  6. 测试,打开 PostMan 测试 http://localhost:9200 ,查看返回的 JSON 信息

    PostMan测试单机部署结果

分布式安装

这里我会在本地启动3个 ES 节点,分别用不同的端口号来模拟分布式的环境,一个节点为 master,另外两个节点为 slave 节点。

  1. 下载安装包,解压3份,分别命名为 elasticsearch-masterelasticsearch-slave1elasticsearch-slave2

  2. 修改 elasticsearch-master 的配置文件,config/elasticsearch.yml

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    http.cors.enabled: true
    http.cors.allow-origin: "*"
    ## 用于指定集群名称
    cluster.name: xkcoding
    ## 用于指定节点名称
    node.name: master
    ## 用于指定当前节点是 master 节点
    node.master: true
    network.host: 127.0.0.1
    http.port: 9200
  3. 修改 elasticsearch-slave1 的配置文件,config/elasticsearch.yml

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    http.cors.enabled: true
    http.cors.allow-origin: "*"
    ## 用于指定集群名称
    cluster.name: xkcoding
    ## 用于指定节点名称
    node.name: slave1
    network.host: 127.0.0.1
    http.port: 8200
    ## 用于发现 master 节点
    discovery.zen.ping.unicast.hosts: ["127.0.0.1"]
  4. 修改 elasticsearch-slave2 的配置文件,config/elasticsearch.yml

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    http.cors.enabled: true
    http.cors.allow-origin: "*"
    ## 用于指定集群名称
    cluster.name: xkcoding
    ## 用于指定节点名称
    node.name: slave2
    network.host: 127.0.0.1
    http.port: 7200
    ## 用于发现 master 节点
    discovery.zen.ping.unicast.hosts: ["127.0.0.1"]
  5. 启动 head 插件,打开浏览器,访问 http://localhost:9100,查看分布式节点的运行状态

    分布式 ES 的集群状态

插件安装

head 插件

head 插件要求本地的 Node 版本不低于8.0

  1. 前往 GitHub 搜索 elasticsearch-head ,这里我直接给出插件的 GitHub 仓库地址,https://github.com/mobz/elasticsearch-head ,clone 源代码

    1
    2
    cd ~/apps
    git clone https://github.com/mobz/elasticsearch-head.git
  2. 检查本地 Node 版本,必须保证为8.0版本以上

    1
    2
    $ node -v
    v8.5.0
  3. 编译 head 插件

    1
    2
    cd ~/apps/elasticsearch-head
    npm install

    如果觉得这一步的依赖下载比较慢的话,有两种解决方式:

    1. 可以选择科学上网
    2. 使用国内阿里的镜像源,使用 cnpm 安装,具体如何配置,本文不做赘述。具体参考:http://npm.taobao.org/
  4. 配置 ES 支持跨域访问,让 head 插件可以访问到

    1
    2
    cd ~/apps/elasticsearch-6.1.1
    vim config/elasticsearch.yml

    添加进下面两行对跨域的配置

    1
    2
    http.cors.enabled: true
    http.cors.allow-origin: "*"

    保存 :wq! 配置文件,启动 ES

    1
    ./bin/elasticsearch
  5. 运行 head 插件

    1
    2
    cd ~/apps/elasticsearch-head
    npm run start
  6. 打开浏览器,访问 http://localhost:9100/ ,查看 head 插件运行效果

    运行 head 插件,查看单机 ES 状态

ik 中文分词插件

基础概念

集群和节点

每个节点都是一个 ES 的单独实例,通过 node_name 来指定各个节点的名字。

集群是通过配置cluster_name来使得节点找到集群。

索引

索引是含有相同属性的文档集合,在 ES 中是通过一个名字来识别的,而且要求是英文字母小写且不包含中划线

索引对应于 SQL 的 database

类型

索引可以含有一个或者多个类型,文档必须属于一个类型

类型对应于 SQL 的 table

文档

文档是可以被索引的基本数据单位

文档对应于 SQL 的一条记录

分片

每个索引都有多个分片,每个分片都是一个 Lucene 索引

备份

拷贝一份分片就完成了分片的备份

基本用法

请求方式

API 基本格式:http://<ip>:<port>/<索引>/<类型>/<文档 id>

API 请求方式:GET、POST、PUT、DELETE

索引

创建

假设创建一个 people 的索引,打开 PostMan,输入地址 http://localhost:9200/people ,将方法改为 PUT 方法,将👇的 json 放入 Body 请求体,点击 Send,若出现 "acknowledged":true,则代表索引创建成功。

请求体的 json:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
{
"settings":{
"number_of_shards":3,
"number_of_replicas":1
},
"mappings":{
"person":{
"properties":{
"name":{
"type":"text"
},
"age":{
"type":"integer"
},
"country":{
"type":"keyword"
},
"birthday":{
"type":"date",
"format":"yyyy-MM-dd HH:mm:ss||yyyy-MM-dd||epoch_millis"
}
}
}
}
}

json 请求体字段解释:

  1. settings 字段可以不指定
  2. number_of_shards 代表分片数(默认为5)
  3. number_of_replicas 代表备份数(默认为1)
  4. text 类型的字段搜索时将会被分词
  5. keyword 类型的字段搜索时将不会被分词
  6. epoch_millis 类型代表时间戳格式

返回值:

1
2
3
4
5
{
"acknowledged": true,
"shards_acknowledged": true,
"index": "people"
}

如图所示:

创建索引

删除

打开 PostMan,输入地址 http://localhost:9200/people ,将方法改为 DELETE 方法,点击 Send。

返回值:

1
2
3
{
"acknowledged": true
}

插入

现在我们向之前创建的类型中添加一条文档记录,插入分为2种方式,一种是指定 ID,一种是不指定 ID,当不指定 ID 时,ID 由 ES 生成。

指定 ID 插入

打开 PostMan,输入地址 http://localhost:9200/people/person/1 ,将文档的 json 放入 Body 请求体,将方法改为 PUT 方法,点击 Send。

文档内容:

1
2
3
4
5
6
{
"name":"xkcoding",
"age":24,
"country":"中国",
"birthday":"1994-11-22"
}

返回值:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
{
"_index": "people",
"_type": "person",
"_id": "1",
"_version": 1,
"result": "created",
"_shards": {
"total": 2,
"successful": 2,
"failed": 0
},
"_seq_no": 0,
"_primary_term": 1
}

如图所示:

指定 id 插入

不指定 ID 插入

打开 PostMan,输入地址 http://localhost:9200/people/person ,将文档的 json 放入 Body 请求体,将方法改为 POST 方法,点击 Send。

文档内容:

1
2
3
4
5
6
{
"name":"中年xkcoding",
"age":34,
"country":"中国",
"birthday":"1984-11-22"
}

返回值:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
{
"_index": "people",
"_type": "person",
"_id": "I4_sAmEBVnl1iCRF78pG",
"_version": 1,
"result": "created",
"_shards": {
"total": 2,
"successful": 2,
"failed": 0
},
"_seq_no": 0,
"_primary_term": 1
}

如图所示:

不指定 id 插入

然后前往 head 插件查看是否插入数据

head 插件中查看添加的文档记录

修改

修改文档数据,打开 PostMan,输入地址 http://localhost:9200/people/person/1/_update ,将请求的 json 放入 Body 请求体,将方法改为 POST 方法,点击 Send。

请求体:

1
2
3
4
5
{
"doc":{
"name":"沈扬凯"
}
}

注意:修改的时候 URL 必须后面跟_update,然后要修改的字段必须放在 doc 字段里。

返回值:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
{
"_index": "people",
"_type": "person",
"_id": "1",
"_version": 2,
"result": "updated",
"_shards": {
"total": 2,
"successful": 2,
"failed": 0
},
"_seq_no": 1,
"_primary_term": 1
}

如图所示:

修改文档

删除

打开 PostMan,输入地址 http://localhost:9200/people/1 ,将方法改为 DELETE 方法,点击 Send。

返回值:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
{
"_index": "people",
"_type": "person",
"_id": "1",
"_version": 3,
"result": "deleted",
"_shards": {
"total": 2,
"successful": 2,
"failed": 0
},
"_seq_no": 2,
"_primary_term": 1
}

查询

ES 正如其名,本就是为了 Search 而生,所以查询是 ES 最牛逼的地方。使用上面提到的插入语法,预先插入一堆原始数据,用 head 查看。

预先插入的原始数据

简单查询

打开 PostMan,输入地址 http://localhost:9200/people/person/1 ,将方法改为 GET 方法,点击 Send。

返回 id 为1的文档数据:

1
2
3
4
5
6
7
8
9
10
11
12
13
{
"_index": "people",
"_type": "person",
"_id": "1",
"_version": 1,
"found": true,
"_source": {
"name": "xkcoding",
"age": 24,
"country": "中国",
"birthday": "1994-11-22"
}
}

条件查询

这里还是使用 PostMan 来测试我们的条件查询

请求地址:http://localhost:9200/people/_search

请求方式:POST

返回值因为文章篇幅有限,就不将返回的内容贴在文章中了,请自行测试下方请求体,验证结果

  1. 查询所有文档数据

    请求体:

    1
    2
    3
    4
    5
    {
    "query":{
    "match_all":{}
    }
    }
  2. 分页查询文档数据,第 0 条开始,每页 3 条数据

    请求体:

    1
    2
    3
    4
    5
    6
    7
    {
    "query":{
    "match_all":{}
    },
    "from":0,
    "size":3
    }
  3. 查询名字中包含「小」的文档

    请求体:

    1
    2
    3
    4
    5
    6
    7
    {
    "query":{
    "match":{
    "name":"小"
    }
    }
    }
  4. 查询名字中包含「小」的文档,并且按照生日降序排序

    请求体:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    {
    "query":{
    "match":{
    "name":"小"
    }
    },
    "sort":[
    {
    "birthday":{
    "order":"desc"
    }
    }
    ]
    }

聚合查询

请求地址:http://localhost:9200/people/_search

请求方式:POST

  1. 按照年龄聚合数据,查出文档中不同年龄所占的人数

    请求体:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    {
    "aggs":{
    "group_by_age":{
    "terms":{
    "field":"age"
    }
    }
    }
    }
  2. 多个聚合,分别按照年龄、生日聚合数据

    请求体:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    {
    "aggs":{
    "group_by_age":{
    "terms":{
    "field":"age"
    }
    },
    "group_by_birthday":{
    "terms":{
    "field":"birthday"
    }
    }
    }
    }
  3. 年龄最小

    请求体:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    {
    "aggs":{
    "grades_ages":{
    "min":{
    "field":"age"
    }
    }
    }
    }
  4. 年龄最大

    请求体:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    {
    "aggs":{
    "grades_ages":{
    "max":{
    "field":"age"
    }
    }
    }
    }
  5. 平均年龄

    请求体:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    {
    "aggs":{
    "grades_ages":{
    "avg":{
    "field":"age"
    }
    }
    }
    }
  6. 年龄总和

    请求体:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    {
    "aggs":{
    "grades_ages":{
    "sum":{
    "field":"age"
    }
    }
    }
    }
  7. 统计,将3、4、5、6的信息统一查询

    请求体:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    {
    "aggs":{
    "grades_ages":{
    "stats":{
    "field":"age"
    }
    }
    }
    }

高级查询

query

模糊匹配

查询名字中包含「小」的文档

请求体:

1
2
3
4
5
6
7
{
"query":{
"match":{
"name":"小"
}
}
}

词组匹配

查询名字只包含「小凯」的文档,我们先使用如下的请求体查询

1
2
3
4
5
6
7
{
"query":{
"match":{
"name":"小凯"
}
}
}

返回的结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
// 省略其余部分
"hits": {
"total": 3,
"max_score": 1.8386619,
"hits": [
{
"_index": "people",
"_type": "person",
"_id": "2",
"_score": 1.8386619,
"_source": {
"name": "年轻的小凯",
"age": 14,
"country": "中国",
"birthday": "2004-11-22"
}
},
{
"_index": "people",
"_type": "person",
"_id": "4",
"_score": 0.8984401,
"_source": {
"name": "狂小狗",
"age": 20,
"country": "中国",
"birthday": "1998-01-01"
}
},
{
"_index": "people",
"_type": "person",
"_id": "9",
"_score": 0.8142733,
"_source": {
"name": "小泽",
"age": 32,
"country": "日本",
"birthday": "1986-02-02"
}
}
]
}

通过结果,可以发现,通过 match 去查询的时候,ES 返回的是分别包含「小」、「凯」的文档,如何去查询 name 字段只包含「小凯」的文档呢?使用 match_phrase 可以满足要求。

请求体:

1
2
3
4
5
6
7
{
"query":{
"match_phrase":{
"name":"小凯"
}
}
}

字段查询

查询所有的中国人

请求体:

1
2
3
4
5
6
7
{
"query":{
"term":{
"country":"中国"
}
}
}

范围查询(支持数值型和日期型)

查询年龄 24 ≤ age<30 直接的数据

请求体:

1
2
3
4
5
6
7
8
9
10
{
"query":{
"range":{
"age":{
"gte":24,
"lt":30
}
}
}
}

filter

一般结合 bool 一起使用,

查询年龄为23的文档数据

请求体:

1
2
3
4
5
6
7
8
9
10
11
{
"query":{
"bool":{
"filter":{
"term":{
"age":23
}
}
}
}
}

复合条件查询

固定分数查询

  1. 查询名字中包含「小」的文档,并固定查询分数

    请求体:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    {
    "query":{
    "constant_score":{
    "filter":{
    "match":{
    "name":"小"
    }
    }
    }
    }
    }
  2. 查询名字中包含「小」的文档,并固定查询分数为2

    请求体:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    {
    "query":{
    "constant_score":{
    "filter":{
    "match":{
    "name":"小"
    }
    },
    "boost":2
    }
    }
    }

布尔查询

  1. 查询是国家为中国或者年龄为30的数据

    请求体:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    {
    "query":{
    "bool":{
    "should":[
    {
    "match":{
    "country":"中国"
    }
    },{
    "match":{
    "age":30
    }
    }
    ]
    }
    }
    }
  2. 查询国家为中国并且年龄为23的数据

    请求体:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    {
    "query":{
    "bool":{
    "must":[
    {
    "match":{
    "country":"中国"
    }
    },{
    "match":{
    "age":23
    }
    }
    ]
    }
    }
    }
  3. 查询国家为中国并且年龄为23并且生日比1995-07-01早的数据

    请求体:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    {
    "query":{
    "bool":{
    "must":[
    {
    "match":{
    "country":"中国"
    }
    },{
    "match":{
    "age":23
    }
    }
    ],
    "filter":[{
    "range":{
    "birthday":{
    "lt":"1995-07-01"
    }
    }
    }]
    }
    }
    }

Spring Boot 集成 ES

Spring Boot 集成 ES 的配置类

插入

修改

删除

查询

复合查询

搜索建议

聚合查询

o(╯□╰)o我只要一毛钱的鼓励~~