ljzsdut
GitHubToggle Dark/Light/Auto modeToggle Dark/Light/Auto modeToggle Dark/Light/Auto modeBack to homepage

01 Es入门介绍

lucene和elasticsearchl的前世今生

​ lucene是一类jar包,是最先进、功能最强大的搜索库,直接基于lucene开发是非常复杂的。lucene的api复杂(实现一些简单的功能要写大量的java代码),直接使用lucene的api还需要深入理解原理(各种索引结构)。

​ elasticsearch基于lucene开发,隐藏复杂性,提供简单易用的restful api接口、 java api接口(还有其他语言的api接口),es具有如下特点:

  • 分布式的文档存储引擎
  • 分布式的搜索引擎和分析引擎
  • 分布式,支持PB级数据
  • 近实时特性
    • 1、从写入数据到数据可以被搜索到有一个小延迟(大概1秒)
    • 2、基于es执行搜索和分析可以达到秒级,速度快
  • 开箱即用,优秀的默认参数,不需要任何额外设置,完全开源

Elasticsearch索引和Lucene索引的对比:

一个分片就是一个Lucene的索引,也就是一个包含倒排索引的文件目录。它默认存储原始文档的内容,再加上一些额外的信息,如词条字典和词频,这些都能帮助到搜索。词条字典将每个词条和包含该词条的文档映射起来。搜索的时候,Elastisearch没有必要为了某个词条扫描所有的文档,而是根据这个字典快速地识别匹配的文档。

Elasticsearch索引被分解为多块:分片。所以一个Elasticsearch的索引由多个Lucene的索引组成。

词频使得Elasticsearch可以快速地获取某篇文档中某个词条出现的次数。这对于计算结果的相关性得分非常重要。例如,如果搜索“morris",包含多个“morris”的文档通常更为相关。Elasticsearch将给它们更高的得分,让它们出现在结果列表的更前面。

ElasticSearch的核心概念

集群(Cluster)

一个集群(cluster)有多个节点(node)组成。每个节点属于哪个集群是通过一个配置(集群名称,默认是elasticsearch) 来决定的,对于中小型应用来说,刚开始一个集群就一个节点很正常。

一个节点可以通过配置集群名称(cluster.name)的方式来加入一个指定的集群。默认情况下,每个节点都会被安排加入到一个叫做“elasticsearch”的集群中,这意味着,如果你在你的网络中启动了若干个节点,并假定它们能够相互发现彼此,它们将会自动地形成并加入到一个叫做“elasticsearch”的集群中。

节点(Node)

集群中的一个节点,节点一个一个名称(默认是随机分配的)。节点名称很重要 (在执行运维管理操作的时候),默认节点会去加入一个名称为“elasticsearch’的 集群。如果直接启动一堆节点,那么他们会自动组成一个ES集群。当然,一个节点也可以组成一个集群。

分片(shard)

单台机器无法存储大量数据,es可以将一个索引中的数据切分为多个shard,分布在多台服务器上存储。有了shard就可以横向扩展,存储更多数据,让搜索和分析等操作分布到多台服务器上去执行,提升吞吐量和性能。每个shard都是一个lucene index。

副本(replica)

任何一个服务器随时可能故障或宕机,此时shard可能就会丢失,因此可以为每个shard创建多个replica副本。replica可以在shard故障时提供备用服务,保证数据不丢失,多个replica还可以提升搜索操作的吞吐量和性能。primary shard(建立索引时一次设置,创建后不能修改,默认5个),replica shard(随时修改数量,默认1个),默认每个索引10个shard,5个primary shard,5个replica shard,最小的高可用配置,是2台服务器。

副本是分片的副本。分片有主分片(primary Shard)和副本分片(replida Shard)之分。

一个Index数据在物理上被分布在多个主分片中,每个主分片只存放部分数据。

每个主分片可以有多个副本,叫副本分片,是主分片的复制。

文档

一个document相当于关系型数据库中的一行记录。是ES中最小的数据单元,一个document就是一条数据。通常用json格式表示。一个document中有多个字段(filed)。

{
  "product_id": "1",
  "product_name": "高露洁牙膏",
  "product_desc": "高效美白",
  "category_id": "2",
  "category_name": "日化用品"
}

扩展:

文档优势:可以表示对象嵌套关系。是一种面向对象的存储方式。

面向文档的搜索分析引擎

(1)应用系统的数据结构都是面向对象的,复杂的 (2)对象数据存储到数据库中,只能拆解开来,变为扁平的多张表,每次查询的时候还得还原回对象格式,相当麻烦 (3)ES是面向文档的,文档中存储的数据结构,与面向对象的数据结构是一样的,基于这种文档数据结构,es可以提供复杂的索引,全文检索,分析聚合等功能 (4)es的document用json数据格式来表达

public class Employee {

  private String email;
  private String firstName;
  private String lastName;
  private EmployeeInfo info;
  private Date joinDate;

}

private class EmployeeInfo {

  private String bio; // 性格
  private Integer age;
  private String[] interests; // 兴趣爱好

}

EmployeeInfo info = new EmployeeInfo();
info.setBio("curious and modest");
info.setAge(30);
info.setInterests(new String[]{"bike", "climb"});

Employee employee = new Employee();
employee.setEmail("zhangsan@sina.com");
employee.setFirstName("san");
employee.setLastName("zhang");
employee.setInfo(info);
employee.setJoinDate(new Date());

employee对象:里面包含了Employee类自己的属性,还有一个EmployeeInfo对象

两张表:employee表,employee_info表,将employee对象的数据重新拆开来,变成Employee数据和EmployeeInfo数据 employee表:email,first_name,last_name,join_date,4个字段 employee_info表:bio,age,interests,3个字段;此外还有一个外键字段,比如employee_id,关联着employee表

{
    "email":      "zhangsan@sina.com",
    "first_name": "san",
    "last_name": "zhang",
    "info": {
        "bio":         "curious and modest",
        "age":         30,
        "interests": [ "bike", "climb" ]
    },
    "join_date": "2017/01/01"
}

我们就明白了es的document数据格式和数据库的关系型数据格式的区别

索引(Index)

索引,包含一堆有相似结构的文档数据,比如可以有一个客户索引,商品分类索引,订单索引,索引有一个名称。一个index包含很多document,一个index就代表了一类类似的或者相同的document。比如说建立一个product index,商品索引,里面可能就存放了所有的商品数据,所有的商品document。

一个索引可以理解成一个关系型数据库。

类型(Type)

类型,每个索引里都可以有一个或多个type,type是index中的一个逻辑数据分类,一个type下的document,都有相同的field,比如博客系统,有一个索引,可以定义用户数据type,博客数据type,评论数据type。

如何理解type?

商品index,里面存放了所有的商品数据,即商品document。但是商品分很多种类,每个种类的document的field可能不太一样,比如说电器商品,可能还包含一些诸如售后时间范围这样的特殊field;生鲜商品,还包含一些诸如生鲜保质期之类的特殊field。

此时,商品index中就有多种type:日化商品type,电器商品type,生鲜商品type

日化商品type:product_id,product_name,product_desc,category_id,category_name 电器商品type:product_id,product_name,product_desc,category_id,category_name,service_period 生鲜商品type:product_id,product_name,product_desc,category_id,category_name,eat_period

每一个type里面,都会包含一堆document。

{
  "product_id": "2",
  "product_name": "长虹电视机",
  "product_desc": "4k高清",
  "category_id": "3",
  "category_name": "电器",
  "service_period": "1年"
}

{
  "product_id": "3",
  "product_name": "基围虾",
  "product_desc": "纯天然,冰岛产",
  "category_id": "4",
  "category_name": "生鲜",
  "eat_period": "7天"
}

一种type就像一类表,比如user表,order表。

注意:

ES 5.x中一个index可以有多种type。

ES 6.x中一个index只能有一种type。

ES 7.X以后已经移除type这个概念。

映射

mapping定义了每个字段的类型等信息。相当于关系型数据库中的表结构。

简单的集群管理

ES命令行查询es集群的状态、分片、索引

(1)快速检查集群的健康状况

es提供了一套api,叫做cat api,可以查看es中各种各样的数据

GET /_cat/health?v ?v参数会打印标题栏

如何快速了解集群的健康状况?green、yellow、red?

  • green:每个索引的primary shard和replica shard都是active状态的
  • yellow:每个索引的primary shard都是active状态的,但是部分replica shard不是active状态,处于不可用的状态(比如因为节点数量不足,导致replica shard无法调度)
  • red:不是所有索引的primary shard都是active状态的,部分索引有数据丢失了

为什么现在会处于一个yellow状态?

我们现在就一个笔记本电脑,就启动了一个es进程,相当于就只有一个node。现在es中有一个index,就是kibana自己内置建立的index。由于默认的配置是给每个index分配5个primary shard和5个replica shard,即shards=5,replicas=1,而且primary shard和replica shard不能在同一台机器上(为了容错)。现在kibana自己建立的index是1个primary shard和1个replica shard。当前就一个node,所以只有1个primary shard被分配了和启动了,但是一个replica shard没有第二台机器去启动。

做一个小实验:此时只要启动第二个es进程,就会在es集群中有2个node,然后那1个replica shard就会自动分配过去,然后cluster status就会变成green状态。

epoch      timestamp cluster       status node.total node.data shards pri relo init unassign pending_tasks max_task_wait_time active_shards_percent
1488006741 15:12:21  elasticsearch yellow          1         1      1   1    0    0        1             0                  -                 50.0%

epoch      timestamp cluster       status node.total node.data shards pri relo init unassign pending_tasks max_task_wait_time active_shards_percent
1488007113 15:18:33  elasticsearch green           2         2      2   1    0    0        0             0                  -                100.0%

(2)快速查看集群中有哪些索引

GET /_cat/indices?v

health status index   uuid                   pri rep docs.count docs.deleted store.size pri.store.size
yellow open   .kibana rUm9n9wMRQCCrRDEhqneBg   1   1          1            0      3.1kb          3.1kb

索引健康(health),green为正常,yellow表示索引不可靠(单节点),red索引不可用。与集群健康状态一致。

状态(status),表明索引是否打开。

索引名称(index),这里有.kibana和school。

uuid,索引内部随机分配的名称,表示唯一标识这个索引。

主分片(pri),这个就是集群的主分片数。

文档数(docs.count)。

已删除文档数(docs.deleted),这里统计了被删除文档的数量。

索引存储的总容量(store.size),索引的总容量,是主分片总容量的两倍,因为存在一个副本。

主分片的总容量(pri.store.size),主分片容量,是索引总容量的一半。

(3)查看索引shard信息

curl -s http://127.0.0.1:9200/_cat/shards?v
log-000028                                        0     p      STARTED  16456065   4.3gb 100.101.46.23  elasticsearch-3
log-000028                                        0     r      STARTED  16500999   4.1gb 100.101.176.25 elasticsearch-2
log-000024                                        0     p      STARTED 117914041  30.4gb 100.101.46.23  elasticsearch-3
log-000024                                        0     r      STARTED 117914041  30.4gb 100.101.121.22 elasticsearch-4
  • index:所有名称
  • shard:分片数
  • prirep:分片类型,p=pri=primary为主分片,r=rep=replicas为复制分片
  • state:分片状态,STARTED为正常分片,INITIALIZING为异常分片
  • docs:记录数
  • store:存储大小
  • ip:es节点ip
  • node:es节点名称

(3)简单的索引操作

创建索引

PUT /test_index?pretty

health status index      uuid                   pri rep docs.count docs.deleted store.size pri.store.size
yellow open   test_index XmS9DTAtSkSZSwWhhGEKkQ   5   1          0            0       650b           650b
yellow open   .kibana    rUm9n9wMRQCCrRDEhqneBg   1   1          1            0      3.1kb          3.1kb

显示指定mapping创建索引

查看mapping信息

GET /INDEX_NAME

删除索引

DELETE /test_index?pretty

health status index   uuid                   pri rep docs.count docs.deleted store.size pri.store.size
yellow open   .kibana rUm9n9wMRQCCrRDEhqneBg   1   1          1            0      3.1kb          3.1kb

(4)文档的CRUD操作

(1)新增商品:新增文档,建立索引

PUT /index/type/id

PUT /ecommerce/product/1
{
    "name" : "gaolujie yagao",
    "desc" :  "gaoxiao meibai",
    "price" :  30,
    "producer" :      "gaolujie producer",
    "tags": [ "meibai", "fangzhu" ]
}

{
  "_index": "ecommerce",
  "_type": "product",
  "_id": "1",
  "_version": 1,
  "result": "created",
  "_shards": {
    "total": 2,
    "successful": 1,
    "failed": 0
  },
  "created": true
}

PUT /ecommerce/product/2
{
    "name" : "jiajieshi yagao",
    "desc" :  "youxiao fangzhu",
    "price" :  25,
    "producer" :      "jiajieshi producer",
    "tags": [ "fangzhu" ]
}

PUT /ecommerce/product/3
{
    "name" : "zhonghua yagao",
    "desc" :  "caoben zhiwu",
    "price" :  40,
    "producer" :      "zhonghua producer",
    "tags": [ "qingxin" ]
}

es会自动建立index和type,不需要提前创建,而且es默认会对document每个field都建立倒排索引,让其可以被搜索

(2)查询商品:检索文档

GET /${INDEX}/${TYPE}/${ID}

GET /ecommerce/product/1

{
  "_index": "ecommerce",
  "_type": "product",
  "_id": "1",
  "_version": 1,
  "found": true,
  "_source": {
    "name": "gaolujie yagao",
    "desc": "gaoxiao meibai",
    "price": 30,
    "producer": "gaolujie producer",
    "tags": [
      "meibai",
      "fangzhu"
    ]
  }
}

(3)修改商品:覆盖文档PUT

PUT /ecommerce/product/1
{
    "name" : "jiaqiangban gaolujie yagao",
    "desc" :  "gaoxiao meibai",
    "price" :  30,
    "producer" :      "gaolujie producer",
    "tags": [ "meibai", "fangzhu" ]
}

{
  "_index": "ecommerce",
  "_type": "product",
  "_id": "1",
  "_version": 1,
  "result": "created",
  "_shards": {
    "total": 2,
    "successful": 1,
    "failed": 0
  },
  "created": true
}

{
  "_index": "ecommerce",
  "_type": "product",
  "_id": "1",
  "_version": 2,
  "result": "updated",
  "_shards": {
    "total": 2,
    "successful": 1,
    "failed": 0
  },
  "created": false
}


PUT /ecommerce/product/1
{
    "name" : "jiaqiangban gaolujie yagao"
}

覆盖方式有一个不好,即使必须带上所有的field,才能去进行信息的修改

(4)修改商品:更新文档POST

POST /ecommerce/product/1/_update  // _update
{
  "doc": {
    "name": "jiaqiangban gaolujie yagao"
  }
}

{
  "_index": "ecommerce",
  "_type": "product",
  "_id": "1",
  "_version": 8,
  "result": "updated",
  "_shards": {
    "total": 2,
    "successful": 1,
    "failed": 0
  }
}

(5)删除商品:删除文档

DELETE /ecommerce/product/1   // 根据给出的endpoint,决定删除index、type、doc

{
  "found": true,
  "_index": "ecommerce",
  "_type": "product",
  "_id": "1",
  "_version": 9,
  "result": "deleted",
  "_shards": {
    "total": 2,
    "successful": 1,
    "failed": 0
  }
}

{
  "_index": "ecommerce",
  "_type": "product",
  "_id": "1",
  "found": false
}

推荐阅读

Elasticsearch的节点与分片

Elasticsearch 节点选举和primary分片

2 万字详解,吃透 ES!

节点和分片

为了有个全局的理解,我们首先要知道Elasticsearch索引创建的时候,究竟发生了什么,理解数据在物理是如何上组织的?

默认情况下,每个索引由5个主要分片组成,而每份主要分片又有一个副本,一共10份分片。副本分片对于可靠性和搜索性能很有益处。技术上而言,一份分片是一个目录中的文件,Lucene用这些文件存储索引数据。分片也是Elasticsearch 将数据从一个节点迁移到另一个节点的最小单位。

一个节点是一个Elasticsearch的实例。在服务器上启动Elasticsearch之后,你就拥有了一个节点。如果在另一台服务器上启动Elasticsearch, 这就是另一个节点。甚至可以通过启动多个Elasticsearch进程,在同一台服务器上拥有多个节点。

多个节点可以加入同一个集群。在多节点的集群上,同样的数据可以在多台服务器上传播。这有助于性能,因为Elasticsearch有了更多的资源;这同样有助于稳定性,如果每份分片至少有1个副本分片,那么任何一个节点都可以宕机,而Elasticsearch依然可以进行服务,并返回所有数据。

对于使用Elasticsearch的应用程序,集群中有1个还是多个节点都是透明的。默认情况下,可以连接集群中的任一节点并访问完整的数据集,就好像集群只有单独的一个节点。

Elasticsearch索引和Lucene索引的对比

一个分片就是一个Lucene的索引,也就是一个包含倒排索引的文件目录。它默认存储原始文档的内容,再加上一些额外的信息,如词条字典和词频,这些都能帮助到搜索。词条字典将每个词条和包含该词条的文档映射起来。搜索的时候,Elastisearch没有必要为了某个词条扫描所有的文档,而是根据这个字典快速地识别匹配的文档。

Elasticsearch索引被分解为多块:分片。所以一个Elasticsearch的索引由多个Lucene的索引组成。

词频使得Elasticsearch可以快速地获取某篇文档中某个词条出现的次数。这对于计算结果的相关性得分非常重要。例如,如果搜索“morris",包含多个“morris”的文档通常更为相关。Elasticsearch将给它们更高的得分,让它们出现在结果列表的更前面。

ES中的节点类型

Master-eligible nodes与 Master node

每个节点启动后,默认就是一个Master eligible节点,可以通过设置node.master:false来改变,Master-eligible节点可以参加选主流程,成为Master节点,每个节点都保存了集群的状态,但只有Master节点才能修改集群的状态信息,主节点主要负责集群方面的轻量级的动作,比如:创建或删除索引,跟踪集群中的节点,决定分片分配到哪一个节点,在集群再平衡的过程中,如何在节点间移动数据等。

Data Node

可以保存数据的节点,叫做Data Node。负责保存分片数据。在数据扩展上起到了至关重要的作用,每个节点启动后,默认就是一个Data Node节点,可以通过设置node.data:false来改变。

Ingest Node

可以在文档建立索引之前设置一些ingest pipeline的预处理逻辑,来丰富和转换文档。每个节点默认启动就是Ingest Node,可用通过node.ingest:false来禁用。

Coordinating Node

Coordinating Node负责接收Client的请求,将请求分发到合适的节点,最终把结果汇集到一起,每个节点默认都起到了 Coordinating Node的职责,当然如果把Master、Data、Ingest全部禁用,那这个节点就仅是Coordinating Node节点了。

Machine Learning Node

用于机器学习处理的节点。

主分片和副本分片

分片可以是主分片,也可以是副本分片,其中副本分片是主分片的完整副本,副本分片可以用于搜索,或者是在原有主分片丢失后成为新的主分片。写过程先写主分片,成功后再写副分片。

Elasticsearch索引由一个或多个主分片以及零个或多个副本分片构成。副本分片可以在运行的时候进行添加和移除,而主分片不可以。

可以在任何时候改变每个分片的副本分片的数量,因为副本分片总是可以被创建和移除。这并不适用于索引划分为主分片的数量,在创建索引之前,你必须决定主分片的数量。请记住,过少的分片将限制可扩展性,但是过多的分片会影响性能。默认设置主分片的数量是5份。

索引主分片的设置:

put test1
{
  "settings": {
    "index.number_of_shards": 3,
    "index.codec": "best_compression"
  }
}
输出结果:
{
  "acknowledged" : true,
  "shards_acknowledged" : true,
  "index" : "test1"
}

get test1/_settings
输出结果:
{
  "test1" : {
    "settings" : {
      "index" : {
        "codec" : "best_compression",
        "number_of_shards" : "3",
        "provided_name" : "test1",
        "creation_date" : "1637409687711",
        "number_of_replicas" : "1",
        "uuid" : "GnRgeIHeTzy8yaUianvD0g",
        "version" : {
          "created" : "7040099"
        }
      }
    }
  }
}

索引副本分片的设置:

put test1/_settings
{
  "index.number_of_replicas": 2
}
输出结果:
{
  "acknowledged" : true
}

get test1/_settings
输出结果:
{
  "test1" : {
    "settings" : {
      "index" : {
        "codec" : "best_compression",
        "number_of_shards" : "3",
        "provided_name" : "test1",
        "creation_date" : "1637409687711",
        "number_of_replicas" : "2",
        "uuid" : "GnRgeIHeTzy8yaUianvD0g",
        "version" : {
          "created" : "7040099"
        }
      }
    }
  }
}

集群的扩展

水平扩展:最简单的Elasticsearch集群只有一个节点:一台机器运行着一个Elasticsearch进程。我们安装并启动了 Elasticsearch之后,就已经建立了一个拥有单节点的集群。随着越来越多的节点被添加到同一个集群中,现有的分片将在所有的节点中自动进行负载均衡。因此,在那些分片上的索引和搜索请求都可以从额外增加的节点中获益。以这种方式进行扩展(在节点中加入更多节点)被称为水平扩展。此方式增加更多节点,然后请求被分发到这些节点上,工作负载就被分摊了。

垂直扩展:这种方式主要通过为Elasticsearch的节点增加更多硬件资源,可能是为虚拟机分配更多处理器,或是为物理机增加更多的内存。尽管垂直扩展几乎每次都能提升性能,它并非总是可行的或经济的。

索引一个文档的过程

默认情况下,当索引一篇文档的时候,系统首先根据文档ID的散列值选择一个主分片,并将文档发送到该主分片。这份主分片可能位于另一个节点,不过对于应用程序这一点是透明的。

默认地,文档在分片中均匀分布:对于每篇文档,分片是通过其ID字符串的散列决定的。每份分片拥有相同的散列范围,接收新文档的机会均等。一旦目标主分片确定,接受请求的节点将文档转发到该主分片所在的节点。随后,索引操作在该主分片的所有副本分片中进行。在所有可用副本分片完成文档的索引后,索引命令就会成功返回。这使得副本分片和主分片之间保持数据的同步。数据同步使得副本分片可以服务于搜索请求,并在原有主分片无法访问时自动升级为主分片。

搜索索引的过程

当搜索一个索引时,Elasticsearch需要在该索引的完整分片集合中进行查找。这些分片可以是主分片,也可以是副本分片,原因是对应的主分片和副本分片通常包含一样的文档。Elasticsearch在索引的主分片和副本分片中进行搜索请求的负载均衡,使得副本分片对于搜索性能和容错都有所帮助。

在搜索的时候,接受请求的节点将请求转发到一组包含所有数据的分片。Elasticsearch使用round-robin的轮询机制选择可用的分片(主分片或副本分片),并将搜索请求转发过去,Elasticsearch然后从这些分片收集结果,将其聚集为单一的回复, 然后将回复返回给客户端应用程序。在默认情况下,搜索请求通过round-robin轮询机制选中主分片和副本分片。