ElasticSearch万字入门教程 一天上手ElasticSearch

92 阅读26分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

1.ElasticSearch简介

The Elastic Stack,包括Elasticsearch、 Kibana、 Beats 和Logstash (也称为ELK Stack)。 能够安全可靠地获取任何来源、任何格式的数据,然后实时地对数据进行搜索、分析和可视 化。Elaticsearch,简称为ES,ES 是一个开源的高扩展的分布式全文搜索引擎,是整个Elastic Stack技术栈的核心。它可以近乎实时的存储、检索数据;本身扩展性很好,可以扩展到上 百台服务器,处理PB级别的数据

2.ElasticSearch安装

2.1 环境准备

www.elastic.co/cn/ 前往官网进行下载 在这里插入图片描述 选择相应的版本进行下载。 在这里插入图片描述 解压完成后直接解压,进入bin目录运行elasticsearch 在这里插入图片描述,elasticsearch是支持JDK1.8的,但仅仅是7.17.3及其之前的版本。如果是最新版本,最低的JDK得17及其以上。否则是会提示warning: ignoring JAVA_HOME=D:\Java; using bundled JDK 在这里插入图片描述

2.2 运行测试

在这里插入图片描述 能访问http://localhost:9200/就是成功运行了。 在这里插入图片描述

3. 倒排索引

es用的是倒排索引来快速查询文章内容。 先是通过关键字来查询我们的主键id在通过主键id查询相关的内容,比如先查询name的id 10001在通过1001这个id查询相关的内容。强调的是关键字和文档编号之前的关联。 在这里插入图片描述

4 索引操作

4.1 创建索引

对比关系型数据库,创建索引就等同于创建数据库 在Postman中,向ES服务器发PUT请求: http://127.0.0.1:9200/shopping

后面的shopping表示的是我们索引的昵称,创建一个shopping的索引。成功后会返回一个成功json。 在这里插入图片描述

4.2 获取指定索引的相关信息

使用get来请求就能得到索引的相关信息。 在这里插入图片描述

http://127.0.0.1:9200/shopping
{
    "shopping": {
        "aliases": {},
        "mappings": {},
        "settings": {
            "index": {
                "routing": {
                    "allocation": {
                        "include": {
                            "_tier_preference": "data_content"
                        }
                    }
                },
                "number_of_shards": "1",
                "provided_name": "shopping",
                "creation_date": "1661411062175",
                "number_of_replicas": "1",
                "uuid": "yJvIM_mkSBSxWhmb6Dph6A",
                "version": {
                    "created": "7170299"
                }
            }
        }
    }
}

4.3 获取全部索引

通过使用_cat/indices?v就能得到目前所有索引

http://127.0.0.1:9200/_cat/indices?v

在这里插入图片描述

4.3 删除指定索引

使用DELETE请求即可删除

http://127.0.0.1:9200/shopping

在这里插入图片描述

5.文档操作

5.1创建文档

只能使用post请求创建文档,同时需要添加json数据才能创建成功。

{
    "title":"小米",
    "categoy":"红米"
}
http://127.0.0.1:9200/shopping/_doc

在这里插入图片描述在这里插入图片描述

创建完成后会返回一个随机id属性,id代码我们数据的唯一标识,这个标识是用es生成的。

5.1.1 自定义创建id

在doc的后面添加自己自定义的id,这样id就不会变了,你给的什么他就是什么。

http://127.0.0.1:9200/shopping/_doc/1001

在这里插入图片描述

5.2 主键查询&全查询

5.2.1 主键查询

http://127.0.0.1:9200/shopping/_doc/1001

通过get请求来请求我们的文档,会返回一串数据,有我们的数据源版本号和id。这样的请求只会查询出来一条数据。

5.2.2 全部查询

http://127.0.0.1:9200/shopping/_search

** 后面添加_search用get查询能得到全部的文档信息,返回的数据包含状态信息和查询时间和命中的结果。**

6.全局修改&局部修改&删除

6.1 全局修改

通过使用put请求来进行修改

http://127.0.0.1:9200/shopping/_doc/1001

json里传你要修改的内容进去即可。返回信息中看到updated就是修改成功。

{
    "title":"大米",
    "categoy":"红米"
}

在这里插入图片描述 重新查询可以发现结果已经是我们修改的值了。说明我们索引数据已经发生了改变。 在这里插入图片描述

6.2 局部更新

使用post请求来进行修改

http://127.0.0.1:9200/shopping/_update/1001

json中包含我们要修改的数据

{
    "doc" : {
         "title":"华为"
    }


}

可以看到返回值版本号有更新。 在这里插入图片描述 查询结果 在这里插入图片描述

6.2 删除数据

资源地址一样但是请求方式用DELETE请求即可删除。

http://127.0.0.1:9200/shopping/_doc/1001

在这里插入图片描述

7.条件查询&分页&查询排序

7.1 数据查询 通过请求路径

使用get请求来查询指定数据

http://127.0.0.1:9200/shopping/_search?q=categoy:红米

** 后面添加?q=sss:xxx sss代表查询的字段 xxx代表你要查询的字符 ** 在这里插入图片描述

7.2 通过请求体查询 条件查询

使用match 匹配查询

通过get请求后面不需要添加?号

http://127.0.0.1:9200/shopping/_search

match 表示匹配查询

{
         "query":{
                "match":{
                    "categoy":"大米"
                }
             }
}

在这里插入图片描述

使用match_all查询全部数据

查询全部数据,使用条件查询请求体添加match_all

{
         "query":{
                "match_all":{
                   
                }
             }

}

在这里插入图片描述

7.3 分页查询

请求体

{
         "query":{
                "match_all":{
                   
                }

             },
        "from":0, //起始位置
        "size":2 //每页查询数据条数
}

结果给我们显示两条数据,也就是分页后的。 页码计算公式:(页码-1)*每页数据条数 在这里插入图片描述

7.4 数据源显示指定内容

{
         "query":{
                "match_all":{
                }
             },
        "from":2, //起始位置
        "size":2,//每页查询数据条数
        "_source":["title"] //查询指定数据
}

在请求体添加 "_source":["title"] 后可以发现返回内容只有我们指定的数据了。

7.5 查询结果排序

使用get请求

http://127.0.0.1:9200/pricetest/_search
{
         "query":{
                "match_all":{
                   
                }

             },
        "from":0, //起始位置
        "size":2,//每页查询数据条数
        "sort":{
                "price":{ // 排序的字段
                    "order":"desc" //排序规则
                }
            }
}

查询结果 在这里插入图片描述

7.6 多条件查询

must 查询必须匹配某些条件才可以返回 must_not查询必须不匹配某些条件 should 当查询满足此条件时,会增加其_score值 filter 必须匹配,但是结果不会计算分值。

7.6.1 bool条件查询 and

**must 查询必须匹配某些条件才可以返回 must也类似于 and ** 例: 我请求标题为鸡蛋的数据

{
         "query":{
              "bool":{
                  "must":[
                      {//表示数组
                        "match":{
                            "title": "鸡蛋"
                        }
                       }
                  ]
              }
    }
}

查询结果 在这里插入图片描述

多条件查询

请求体,要求返回的数据标题是鸡蛋并且鸡蛋的价格是4000

{
         "query":{
              "bool":{
                  "must":[
                      {//表示数组
                        "match":{
                            "title": "鸡蛋"
                        }
                      },
                       {
                            "match":{
                                "price":4000
                            }
                        }
                  ]
              }
    }
}

在这里插入图片描述

7.6.2 should条件查询 or

查询包含鸡蛋或者包含番茄蛋的数据

{
         "query":{
              "bool":{
                  "should":[
                      {//表示数组
                        "match":{
                            "title": "鸡蛋"
                        }
                      },
                       {
                            "match":{
                                "title":"番茄蛋"
                            }
                        }
                  ]
              }
    }
}

结果 在这里插入图片描述

7.6.2 filter范围查询 > < 大于小于

{
         "query":{
              "bool":{
                  "should":[
                      {//表示数组
                        "match":{
                            "title": "鸡蛋"
                        }
                      },
                       {
                            "match":{
                                "title":"番茄蛋"
                            }
                        }
                  ],
                  "filter":{//范围字段
                    "range":{
                        "price":{
                            "gt":2000//价格大于2000
                        }
                    }
              }
              }
    }
}

结果 在这里插入图片描述 要注意是在数组的后面添加 在这里插入图片描述

8.全文检索&完全匹配&高亮查询

8.1 全文检索匹配

我们只查询个蛋发现也可以查询出多条数据,原因是保存文档数据时会对文档数据进行分词拆解操作,然后通过倒排索引进行匹配。可以发现每个拆解后的文字都可以找到。 在这里插入图片描述 就算搜索的内容是两个词他也会在底层进行文字拆解,看起来搜的是两个词其实是一个。 在这里插入图片描述

8.2 完全匹配

使用match_phrase进行完全匹配,字符一样的情况下才会匹配成功。

{
         "query":{
                "match_phrase":{
                    "title":"菜蛋"
                }   
    }
}

在这里插入图片描述

8.3 字段高亮查询

请求体

{
         "query":{
                "match":{
                    "title":"鸡蛋"
                } 
    },
    
    "highlight":{//高亮显示
            "fields":{
                //查询结果中的这个字段进行高亮显示
                "title":{}
            }
    }
}

结果,满足条件后对我们设置的字段进行一个高亮显示。 在这里插入图片描述

9.聚合操作

9.1 统计个数

请求体

{
    "aggs":{//聚合操作
        "price_group":{//随意昵称
            "terms":{   //分组
                "field":"price"//分组字段
            }
        }
    }
}

结果可以看他他对价格一样的数据进行了统计。 在这里插入图片描述 但是里面会包含我们的原始数据信息,通过添加 "size":0就能不显示我们的原始数据了。

{
    "aggs":{//聚合操作
        "price_group":{//随意昵称
            "terms":{   //分组
                "field":"price"//分组字段
            }
        }
    },
    "size":0
}

结果 在这里插入图片描述

9.2 求平均值

直接改成avg即可,获取了所有价格的平均值。

{
    "aggs":{//聚合操作
        "price_group":{//随意昵称
            "avg":{   //分组
                "field":"price"//分组字段
            }
        }
    },
    "size":0
}

10.映射关系

10.1 创建有映射关系的文档

先创建一个user索引 put请求创建索引

http://127.0.0.1:9200/user

创建文档时候请求路径后面添加_mapping创建他的结构信息

put请求
http://127.0.0.1:9200/user/_mapping

请求体

{
    "properties":{
        "name":{
            //设定用户昵称的类型是text 表示可以进行分词
            "type": "text",
            //表示这个字段是可以索引查询的 
            "index": true 
        },
        "sex":{
            //keyword表示不能进行分词必须完整匹配
            "type": "keyword",
            "index": true
        },
        "tel":{
            "type": "keyword",
            "index": false
        }
    }
}

10.2 映射关系的作用

先创建一个数据 put请求

http://127.0.0.1:9200/user/_create/1001

请求体

{
    "name":"小米",
    "sex":"男的",
    "tel":"1111"
}

结果 在这里插入图片描述 查询name信息,因为我们设置的类型是text只搜一个字符就能匹配到数据体。 在这里插入图片描述 但是我们的性别设置的是keyword关键字要完全匹配才能查询,所以一个男字匹配不到数据体,要完全搜索才能匹配上。 在这里插入图片描述 当我们匹配tel的字段时候可以发现直接报错了,因为我们当初创建时候设置他的index是false无法被查询 在这里插入图片描述

11.使用JavaApi调用

juejin.cn/post/713585…

12. 环境

之前的运行模式都是单机模式,一旦出现了问题容易受到影响配置集群能提高性能,增加容错。

12.1 Windows集群部署

新建一个文件夹把ElasticSearch整个文件复制粘贴成三份出来放进去

在这里插入图片描述

修改集群文件目录中每个节点的 config/elasticsearch.yml 配置文件

在这里插入图片描述

nod1

#节点 1 的配置信息:
#集群名称,节点之间要保持一致
cluster.name: my-elasticsearch
#节点名称,集群内要唯一
node.name: node-1001
node.master: true
node.data: true
#ip 地址
network.host: localhost
#http 端口
http.port: 1001
#tcp 监听端口
transport.tcp.port: 9301
#discovery.seed_hosts: ["localhost:9301", "localhost:9302","localhost:9303"]
#discovery.zen.fd.ping_timeout: 1m
#discovery.zen.fd.ping_retries: 5
#集群内的可以被选为主节点的节点列表
#cluster.initial_master_nodes: ["node-1", "node-2","node-3"]
#跨域配置
#action.destructive_requires_name: true
http.cors.enabled: true
http.cors.allow-origin: "*"

nod2

#节点 2 的配置信息:
#集群名称,节点之间要保持一致
cluster.name: my-elasticsearch
#节点名称,集群内要唯一
node.name: node-1002
node.master: true
node.data: true
#ip 地址
network.host: localhost
#http 端口
http.port: 1002
#tcp 监听端口
transport.tcp.port: 9302
discovery.seed_hosts: ["localhost:9301"]
discovery.zen.fd.ping_timeout: 1m
discovery.zen.fd.ping_retries: 5
#集群内的可以被选为主节点的节点列表
#cluster.initial_master_nodes: ["node-1", "node-2","node-3"]
#跨域配置
#action.destructive_requires_name: true
http.cors.enabled: true
http.cors.allow-origin: "*"

nod3

#节点 3 的配置信息:
#集群名称,节点之间要保持一致
cluster.name: my-elasticsearch
#节点名称,集群内要唯一
node.name: node-1003
node.master: true
node.data: true
#ip 地址
network.host: localhost
#http 端口
http.port: 1003
#tcp 监听端口
transport.tcp.port: 9303
#候选主节点的地址,在开启服务后可以被选为主节点
discovery.seed_hosts: ["localhost:9301", "localhost:9302"]
discovery.zen.fd.ping_timeout: 1m
discovery.zen.fd.ping_retries: 5
#集群内的可以被选为主节点的节点列表
#cluster.initial_master_nodes: ["node-1", "node-2","node-3"]
#跨域配置
#action.destructive_requires_name: true
http.cors.enabled: true
http.cors.allow-origin: "*"

分别依次双击执行节点的bin/elasticsearch.bat, 启动节点服务器(可以编写一个脚本启动),启动后,会自动加入指定名称的集群。 集群启动成功

{
    "cluster_name": "my-elasticsearch",
    "status": "green",
    "timed_out": false,
    "number_of_nodes": 3,
    "number_of_data_nodes": 3,
    "active_primary_shards": 0,
    "active_shards": 0,
    "relocating_shards": 0,
    "initializing_shards": 0,
    "unassigned_shards": 0,
    "delayed_unassigned_shards": 0,
    "number_of_pending_tasks": 41,
    "number_of_in_flight_fetch": 0,
    "task_max_waiting_in_queue_millis": 23845,
    "active_shards_percent_as_number": 100.0
}

green:所有的主分片和副本分片都正常运行。 yellow:所有的主分片都正常运行,但不是所有的副本分片都正常运行。 red:有主分片没能正常运行。

状态如果是 "status": "yellow", 代表启动失败,需要删除每个节点中的 data 目录中所有内容重新启动

{
    "cluster_name": "my-elasticsearch",
    "status": "yellow",
    "timed_out": false,
    "number_of_nodes": 1,
    "number_of_data_nodes": 1,
    "active_primary_shards": 6,
    "active_shards": 6,
    "relocating_shards": 0,
    "initializing_shards": 0,
    "unassigned_shards": 4,
    "delayed_unassigned_shards": 0,
    "number_of_pending_tasks": 0,
    "number_of_in_flight_fetch": 0,
    "task_max_waiting_in_queue_millis": 0,
    "active_shards_percent_as_number": 60.0
}

正常状态 在这里插入图片描述

12.2 通过节点来获取和新增

通过PUT请求向集群中的node-1001节点增加索引

 http://127.0.0.1:1001/user

在这里插入图片描述 用GET请求从第三个节点来获取新增索引

http://127.0.0.1:1003/user

在这里插入图片描述 如果在1003创建索引,同样在1001也能获取索引信息,这就是集群能力。就算一个服务器坏了还能用另一个服务器进行使用。

12.3 用Linux节点部署(更新中)

12.4 用Linux集群部署 (更新中)

13. 进阶 核心概念

一个索引就是一个拥有几分相似特征的文档的集合。比如说,你可以有一个客户数据的索引,另一个产品目录的索引,还有一个订单数据的索引。一个索引由一个名字来标识(必须全部是小写字母),并且当我们要对这个索引中的文档进行索引、搜索、更新和删除(CRUD)的时候,都要使用到这个名字。在一个集群中,可以定义任意多的索引。 Elasticsearch索引的精髓:一切设计都是为了提高搜索的性能。 在一个索引中,你可以定义一种或多种类型。

一个类型是你的索引的一个逻辑上的分类/分区,其语义完全由你来定。通常,会为具 有一组共同字段的文档定义一个类型。不同的版本,类型发生了不同的变化。

13.1 文档

一个文档是一个可被索引的基础信息单元,也就是一条数据 比如:你可以拥有某一个客户的文档,某一个产品的一个文档,当然,也可以拥有某个 订单的一个文档。文档以JSON (Javascript Object Notation)格式来表示,而JSON是一个 到处存在的互联网数据交互格式。 在一个index/type里面,你可以存储任意多的文档。

13.2 字段(Field)

相当于是数据表的字段,对文档数据根据不同属性进行的分类标识。

13.3 映射Mapping

mapping 是处理数据的方式和规则方面做一些限制,如:某个字段的数据类型、默认值、分析器、是否被索引等等。这些都是映射里面可以设置的,其它就是处理 ES 里面数据的一些使用规则设置也叫做映射,按着最优规则处理数据对性能提高很大,因此才需要建立映射,并且需要思考如何建立映射才能对性能更好。

13.4 分片Shards

也可以理解为mysql的分条,一个索引可以存储超出单个节点硬件限制的大量数据。比如,一个具有 10 亿文档数据 的索引占据 1TB 的磁盘空间,而任一节点都可能没有这样大的磁盘空间。 或者单个节点处理搜索请求,响应太慢。为了解决这个问题,**Elasticsearch 提供了将索引划分成多份的能力,每一份就称之为分片。**当你创建一个索引的时候,你可以指定你想要的分片的数量。每个分片本身也是一个功能完善并且独立的“索引”,这个“索引”可以被放置到集群中的任何节点上。 在这里插入图片描述

13.5 副本(Replicas)

在一个网络 / 云的环境里,失败随时都可能发生,在某个分片/节点不知怎么的就处于 离线状态,或者由于任何原因消失了,这种情况下,有一个故障转移机制是非常有用并且是强烈推荐的。为此目的, Elasticsearch 允许你创建分片的一份或多份拷贝,这些拷贝叫做复制分片(副本)。 在这里插入图片描述

13.6 分配Allocation

将分片分配给某个节点的过程,包括分配主分片或者副本。如果是副本,还包含从主分片复制数据的过程。这个过程是由 master 节点完成的。

13.7 系统架构

一个运行中的 Elasticsearch 实例称为一个节点,而集群是由一个或者多个拥有相同 cluster.name 配置的节点组成, 它们共同承担数据和负载的压力。当有节点加入集群中或者从集群中移除节点时,集群将会重新平均分布所有的数据。

当一个节点被选举成为主节点时, 它将负责管理集群范围内的所有变更,例如增加、 删除索引,或者增加、删除节点等。 而主节点并不需要涉及到文档级别的变更和搜索等操作,所以当集群只拥有一个主节点的情况下,即使流量的增加它也不会成为瓶颈。 任何节点都可以成为主节点。我们的示例集群就只有一个节点,所以它同时也成为了主节点。

作为用户,我们可以将请求发送到集群中的任何节点 ,包括主节点。 每个节点都知道 任意文档所处的位置,并且能够将我们的请求直接转发到存储我们所需文档的节点。 无论我们将请求发送到哪个节点,它都能负责从各个包含我们所需文档的节点收集回数据,并将最终结果返回給客户端。 Elasticsearch 对这一切的管理都是透明的。

14. 进阶 分布式集群

14.1 单节点集群

我们在包含一个空节点的集群内创建名为users 的索引。

为了演示目的,我们将分配3 个主分片和一份副本(每个主分片拥有一个副本分片)。 在这里插入图片描述 在这里插入图片描述 通过插件查看集群健康状态 在这里插入图片描述 在这里插入图片描述

14.2 故障转移

当集群中只有一个节点在运行时,意味着会有一个单点故障问题——没有冗余。 幸运的是,我们只需再启动一个节点即可防止数据丢失。当你在同一台机器上启动了第二个节点时,只要它和第一个节点有同样的 cluster.name 配置,它就会自动发现集群并加入到其中。但是在不同机器上启动节点的时候,为了加入到同一集群,你需要配置一个可连接到的单播主机列表。之所以配置为使用单播发现,以防止节点无意中加入集群。只有在同一台机器上 运行的节点才会自动组成集群。

如果启动了第二个节点,集群将会拥有两个节点 : 所有主分片和副本分片都已被分配 。 集群就会恢复健康绿色 在这里插入图片描述

在这里插入图片描述

14.3 水平扩容

怎样为我们的正在增长中的应用程序按需扩容呢?当启动了第三个节点,我们的集群将会拥有三个节点的集群 : 为了分散负载而对分片进行重新分配 。 查看节点健康状态 在这里插入图片描述

在这里插入图片描述

Node 1 和 Node 2 上各有一个分片被迁移到了新的 Node 3 节点,现在每个节点上都拥有 2 个分片, 而不是之前的 3 个。 这表示每个节点的硬件资源(CPU, RAM, I/O)将被更少的分片所共享,每个分片 的性能将会得到提升。 分片是一个功能完整的搜索引擎,它拥有使用一个节点上的所有资源的能力。 我们这个拥有 6 个分 片(3 个主分片和 3 个副本分片)的索引可以最大扩容到 6 个节点,每个节点上存在一个分片,并且每个 分片拥有所在节点的全部资源。 但是如果我们想要扩容超过 6 个节点怎么办呢? 主分片的数目在索引创建时就已经确定了下来。实际上,这个数目定义了这个索引能够 存储 的最大数据量。(实际大小取决于你的数据、硬件和使用场景。) 但是,读操作—— 搜索和返回数据——可以同时被主分片 或 副本分片所处理,所以当你拥有越多的副本分片 时,也将拥有越高的吞吐量。

在运行中的集群上是可以动态调整副本分片数目的,我们可以按需伸缩集群。让我们把 副本数从默认的 1 增加到 2。 使用PUT请求

 http://127.0.0.1:1001/users/_settings

请求体


{
    "number_of_replicas" : 2
}

在这里插入图片描述 每个副本设置为2那么三个副本就是九个分片 在这里插入图片描述 users 索引现在拥有 9 个分片: 3 个主分片和 6 个副本分片。 这意味着我们可以将集群 扩容到 9 个节点,每个节点上一个分片。相比原来 3 个节点时,集群搜索性能可以提升 3 倍。

当然,如果只是在相同节点数目的集群上增加更多的副本分片并不能提高性能,因为每 个分片从节点上获得的资源会变少。 你需要增加更多的硬件资源来提升吞吐量。 但是更多的副本分片数提高了数据冗余量:按照上面的节点配置,我们可以在失去 2 个节点 的情况下不丢失任何数据。

14.4 应对故障

我们关闭第一个节点,这时集群的状态为:关闭了一个节点后的集群。 在这里插入图片描述 我们关闭的节点是一个主节点。而集群必须拥有一个主节点来保证正常工作,所以发生 的第一件事情就是选举一个新的主节点: Node 2 。在我们关闭 Node 1 的同时也失去了主 分片 1 和 2 ,并且在缺失主分片的时候索引也不能正常工作。 如果此时来检查集群的状况,我们看到的状态将会为 red :不是所有主分片都在正常工作。

幸运的是,在其它节点上存在着这两个主分片的完整副本, 所以新的主节点立即将这些分片在 Node 2 和 Node 3 上对应的副本分片提升为主分片, 此时集群的状态将会为yellow。这个提升主分片的过程是瞬间发生的,如同按下一个开关一般。

在这里插入图片描述 为什么我们集群状态是 yellow 而不是 green 呢? 虽然我们拥有所有的三个主分片,但是同时设置了每个主分片需要对应 2 份副本分片,而此 时只存在一份副本分片。 所以集群不能为 green 的状态,不过我们不必过于担心:如果我 们同样关闭了 Node 2 ,我们的程序 依然 可以保持在不丢任何数据的情况下运行,因为 Node 3 为每一个分片都保留着一份副本。

打开目录下的配置文件 在这里插入图片描述 他需要通过添加这行配置来发现我们的集群 9302 和9303是另外两台配置

discovery.seed_hosts: ["localhost:9302", "localhost:9303"]


在这里插入图片描述 集群可以将缺失的副本分片再次进行分配,那么集群的状态也将恢复成之前的状态。 如果 Node 1 依然拥有着之前的分片,它将尝试去重用它们,同时仅从主分片复制发生了修改的数据文件。和之前的集群相比,只是 Master 节点切换了。

15.进阶 分片控制&路由计算

15.1 路由计算

当索引一个文档的时候,文档会被存储到一个主分片中。 Elasticsearch 如何知道一个 文档应该存放到哪个分片中呢?当我们创建文档时,它如何决定这个文档应当被存储在分片 1 还是分片 2 中呢?首先这肯定不会是随机的,否则将来要获取文档的时候我们就不知道从何处寻找了。 在这里插入图片描述

实际上,这个过程是根据下面这个公式决定的:

shard = hash(routing) % number_of_primary_shards

routing 是一个可变值,默认是文档的 _id ,也可以设置成一个自定义的值。 routing 通过hash 函数生成一个数字,然后这个数字再除以 number_of_primary_shards (主分片的数量)后得到余数 。这个分布在 0 到 number_of_primary_shards-1 之间的余数,就是我们所寻求的文档所在分片的位置。 在这里插入图片描述

这就解释了为什么我们要在创建索引的时候就确定好主分片的数量并且永远不会改变这个数量:因为如果数量变化了,那么所有之前路由的值都会无效,文档也再也找不到了。

所有的文档API ( get . index . delete 、 bulk , update以及 mget )都接受一个叫做routing 的路由参数,通过这个参数我们可以自定义文档到分片的映射。一个自定义的路由参数可以用来确保所有相关的文档—一例如所有属于同一个用户的文档——都被存储到同一个分片中。

15.2 分片控制

我们可以发送请求到集群中的任一节点。每个节点都有能力处理任意请求。每个节点都知道集群中任一文档位置,所以可以直接将请求转发到需要的节点上。在下面的例子中,如果将所有的请求发送到Node 1001,我们将其称为协调节点coordinating node。 在这里插入图片描述 当发送请求的时候, 为了扩展负载,更好的做法是轮询集群中所有的节点。

16.进阶 数据写流程

新建、索引和删除请求都是写操作, 必须在主分片上面完成之后才能被复制到相关的副本分片。 在这里插入图片描述

在客户端收到成功响应时,文档变更已经在主分片和所有副本分片执行完成,变更是安全的。有一些可选的请求参数允许您影响这个过程,可能以数据安全为代价提升性能。这些选项很少使用,因为 Elasticsearch 已经很快,但是为了完整起见, 请参考下文:

consistency 即一致性。在默认设置下,即使仅仅是在试图执行一个写操作之前,主分片都会要求必须要有规定数量quorum(或者换种说法,也即必须要有大多数)的分片副本处于活跃可用状态,才会去执行写操作(其中分片副本 可以是主分片或者副本分片)。这是为了避免在发生网络分区故障(network partition)的时候进行写操作,进而导致数据不一致。 规定数量即: int((primary + number_of_replicas) / 2 ) + 1 consistency 参数的值可以设为: one :只要主分片状态 ok 就允许执行写操作。 all:必须要主分片和所有副本分片的状态没问题才允许执行写操作。 quorum:默认值为quorum , 即大多数的分片副本状态没问题就允许执行写操作。 注意,规定数量的计算公式中number_of_replicas指的是在索引设置中的设定副本分片数,而不是指当前处理活动状态的副本分片数。如果你的索引设置中指定了当前索引拥有3个副本分片,那规定数量的计算结果即:int((1 primary + 3 replicas) / 2) + 1 = 3,如果此时你只启动两个节点,那么处于活跃状态的分片副本数量就达不到规定数量,也因此您将无法索引和删除任何文档。 timeout 如果没有足够的副本分片会发生什么?Elasticsearch 会等待,希望更多的分片出现。默认情况下,它最多等待 1 分钟。 如果你需要,你可以使用timeout参数使它更早终止:100是100 毫秒,30s是30秒。 新索引默认有1个副本分片,这意味着为满足规定数量应该需要两个活动的分片副本。 但是,这些默认的设置会阻止我们在单一节点上做任何事情。为了避免这个问题,要求只有当number_of_replicas 大于1的时候,规定数量才会执行。

17.进阶 数据读流程

在处理读取请求时,协调结点在每次请求的时候都会通过轮询所有的副本分片来达到负载均衡。在文档被检索时,已经被索引的文档可能已经存在于主分片上但是还没有复制到副本分片。 在这种情况下,副本分片可能会报告文档不存在,但是主分片可能成功返回文档。 一旦索引请求成功返回给用户,文档在主分片和副本分片都是可用的。 在这里插入图片描述

18. 进阶 更新流程 & 批量操作流程

18.1 更新流程

部分更新一个文档结合了先前说明的读取和写入流程:

部分更新一个文档的步骤如下: 在这里插入图片描述

部分更新一个文档的步骤如下:

客户端向Node 1发送更新请求。

它将请求转发到主分片所在的Node 3 。

Node 3从主分片检索文档,修改_source字段中的JSON,并且尝试重新索引主分片的文档。如果文档已经被另一个进程修改,它会重试步骤3 ,超过retry_on_conflict次后放弃。

如果 Node 3成功地更新文档,它将新版本的文档并行转发到Node 1和 Node 2上的副本分片,重新建立索引。一旦所有副本分片都返回成功,Node 3向协调节点也返回成功,协调节点向客户端返回成功。

当主分片把更改转发到副本分片时, 它不会转发更新请求。 相反,它转发完整文档的新版本。请记住,这些更改将会异步转发到副本分片,并且不能保证它们以发送它们相同的顺序到达。 如果 Elasticsearch 仅转发更改请求,则可能以错误的顺序应用更改,导致得到损坏的文档。

18.2 批量操作流程

**mget和 bulk API的模式类似于单文档模式。**区别在于协调节点知道每个文档存在于哪个分片中。它将整个多文档请求分解成每个分片的多文档请求,并且将这些请求并行转发到每个参与节点。

协调节点一旦收到来自每个节点的应答,就将每个节点的响应收集整理成单个响应,返回给客户端。 在这里插入图片描述

19. 进阶 倒排索引

分片是Elasticsearch最小的工作单元。但是究竟什么是一个分片,它是如何工作的?

传统的数据库每个字段存储单个值,但这对全文检索并不够。文本字段中的每个单词需要被搜索,对数据库意味着需要单个字段有索引多值的能力。最好的支持是一个字段多个值需求的数据结构是倒排索引。

19.1 倒排索引原理

Elasticsearch使用一种称为倒排索引的结构,它适用于快速的全文搜索。

见其名,知其意,有倒排索引,肯定会对应有正向索引。正向索引(forward index),反向索引(inverted index)更熟悉的名字是倒排索引。

所谓的正向索引,就是搜索引擎会将待搜索的文件都对应一个文件ID,搜索时将这个ID和搜索关键字进行对应,形成K-V对,然后对关键字进行统计计数。

有兴趣的朋友可以去百度搜索看看。

20.进阶 文档搜索

早期的全文检索会为整个文档集合建立一个很大的倒排索引并将其写入到磁盘。 一旦新的索引就绪,旧的就会被其替换,这样最近的变化便可以被检索到。

倒排索引被写入磁盘后是不可改变的:它永远不会修改。

不需要锁。如果你从来不更新索引,你就不需要担心多进程同时修改数据的问题。

一旦索引被读入内核的文件系统缓存,便会留在哪里,由于其不变性。只要文件系统缓存中还有足够的空间,那么大部分读请求会直接请求内存,而不会命中磁盘。这提供了很大的性能提升。

其它缓存(像filter缓存),在索引的生命周期内始终有效。它们不需要在每次数据改变时被重建,因为数据不会变化。

写入单个大的倒排索引允许数据被压缩,减少磁盘IO和需要被缓存到内存的索引的使用量。

当然,一个不变的索引也有不好的地方。主要事实是它是不可变的! 你不能修改它。如果你需要让一个新的文档可被搜索,你需要重建整个索引。这要么对一个索引所能包含的数据量造成了很大的限制,要么对索引可被更新的频率造成了很大的限制。

20.1 动态更新索引

如何在保留不变性的前提下实现倒排索引的更新?

答案是:用更多的索引。通过增加新的补充索引来反映新近的修改,而不是直接重写整个倒排索引。每一个倒排索引都会被轮流查询到,从最早的开始查询完后再对结果进行合并。

Elasticsearch基于Lucene,这个java库引入了按段搜索的概念。每一段本身都是一个倒排索引,但索引在 Lucene 中除表示所有段的集合外,还增加了提交点的概念—一个列出了所有已知段的文件。 在这里插入图片描述 按段搜索会以如下流程执行: 在这里插入图片描述

二、不时地, 缓存被提交。

一个新的段,一个追加的倒排索引,被写入磁盘。 一个新的包含新段名字的提交点被写入磁盘。 磁盘进行同步,所有在文件系统缓存中等待的写入都刷新到磁盘,以确保它们被写入物理文件 三、新的段被开启,让它包含的文档可见以被搜索。

四、内存缓存被清空,等待接收新的文档。 在这里插入图片描述 当一个查询被触发,所有已知的段按顺序被查询。词项统计会对所有段的结果进行聚合,以保证每个词和每个文档的关联都被准确计算。这种方式可以用相对较低的成本将新文档添加到索引。

段是不可改变的,所以既不能从把文档从旧的段中移除,也不能修改旧的段来进行反映文档的更新。取而代之的是,每个提交点会包含一个.del 文件,文件中会列出这些被删除文档的段信息。

当一个**文档被“删除”**时,它实际上只是在 .del 文件中被标记删除。一个被标记删除的文档仍然可以被查询匹配到,但它会在最终结果被返回前从结果集中移除。

文档更新也是类似的操作方式:当一个文档被更新时,旧版本文档被标记删除,文档的新版本被索引到一个新的段中。可能两个版本的文档都会被一个查询匹配到,但被删除的那个旧版本文档在结果集返回前就已经被移除。

21.进阶 文档刷新 & 文档刷写 & 文档合并

21.1 近实时搜索(待更新)

22. Kibana

Kibana是一个免费且肝放的用户界面,能够让你对Elasticsearch 数据进行可视化,并 让你在Elastic Stack 中进行导航。你可以进行各种操作,从跟踪查询负载,到理解请求如 何流经你的整个应用,都能轻松完成。 下载地址:www.elastic.co/cn/download… 修改kibana.yml文件 在这里插入图片描述 添加以下配置

# 默认端口
server.port: 5601
# ES 服务器的地址
elasticsearch.hosts: ["http://localhost:9200"]
# 索引名
kibana.index: ".kibana"
# 支持中文
i18n.locale: "zh-CN"

Windows 环境下执行 bin/kibana.bat 文件。(首次启动有点耗时) 启动完成后通过浏览器访问:http://localhost:5601 访问控制可以发送请求 在这里插入图片描述 在这里插入图片描述