学习ElasticSearch的Restful Api快速掌握ES数据的增删改查

2024-06-13 19:05:12 浏览数 (4)

在开始进行ES的读写操作之前,我们来捋一捋ES的写请求原理

1 ElasticSearch的请请求原理

ES服务器写单个文档的流程图如下(图片来自官网)

以下是写单个文档所需要的步骤:

1) 客户端向NODE1发送写请求

2) 检查active的shard数

3)NODE-1使用文档ID来确定文档属于分片0,通过集群状态中的内容路由表信息获知分片0的主分片位于NODE3,因此请求被转发到NODE3上

4)NODE3上的主分片执行写操作。如果写入成功,则它将请求并行转发到NODE1和NODE3的副分片上,等待返回结果。当所有的副分片都报告成功, NODE3 将向协调节点报告成功,协调节点再向客户端报告成功 。

在客户端收到成功响应时,意味着写操作已经在主分片和所有副分片都执行完成。

1.1 为什么要检查active的shard数?

ES中有个参数叫write.wait_for_active_shards,这个参数是Index的一个setting,也可以再请求中带上这个参数,

这个参数的含义是每次在写入前该shard至少具有的active副本数。假设我们有一个Index,每个shard有3个Replica,加上Primary总共有4个副本。如果配置write.wait_for_active_shards的数为3,那么允许最多有一个Replica挂掉。如果有两个Replica挂掉,则active的副本数不足3,此时不允许写入。

这个参数默认是1,即只要Primary在就可以写入,起不到什么作用。如果配置大于1,可以起到一种保护作用,保证写入的数据具有更高的可靠性。但是这个参数只在写入前进行检查,并不保证这些数据一定在这些这些副本上写入成功,所以并不是严格保证了写入了多少个副本。

在以前的版本中是写一致性机制,现在被替换为write.waif_for_active_shards参数

  • one:要求我们这个写操作,只要有一个primary shard是active活跃可用的,就可以执行
  • all:要求我们这个写操作,必须所有的primary shard和replica shard都是活跃的,才可以执行这个写操作
  • quorum:要求所有的shard中,必须是大部分的shard都是活跃的,可用的,才可以执行这个写操作

写一致性的默认策略是 quorum,即多数的分片(其中分片副本可以是主分片或副分片)在写入操作时处于可用状态。

代码语言:javascript复制


put /index/type/id?consistency=quorum
quroum = int( (primary   number_of_replicas) / 2 )   1

一些参数解释:

参数

简介

version

设置文档版本号,主要用于实现乐观锁

version_type

版本类型

opt_type

可设置为create,仅代表在文档不存在时才写入,如果文档已存在,则写请求失败

routing

ES默认使用文档ID进行路由,指定routing可使用routing值进行路由

wait_for_active_shards

用于控制写一致性,当指定数量的分片副本可用时才执行写入,否则重试直至超时。默认值为1,主分片可用时即执行写入

refresh

写入完毕后执行刷新,使搜索可见

timeout

请求超时时间,默认为1分钟

pipeline

指定事先创建好的pipeline名称

1.2 写入Primary完成之后,为何要等待所有的Replica响应(或连接失败)后返回?
在更早的ES版本,Primary和Replica之间使允许异步复制的,即写入Primary成功即可返回。但这种模式下,如果Primary挂掉,就有丢失数据的风险,而且从Replica读数据也很难保证读到最新的数据。所以后来ES就取消了异步模式了,改成了等Primary等Replica返回之后再返回给客户端。

因为Primary要等Replica返回后再返回给客户端,那么延迟就会收到最慢的Replicate的影响,这确实是目前ES架构的一个弊端。之前曾误认为这里是等wait_for_active_shards个副本写入成功即可返回,但是后来读源码发现是等所有Replica返回的。

如果Replica写入失败,ES会执行一些重试逻辑等,但最终并不强求一定要在多少个节点写入成功。在返回的结果中,会包含数据在多少个shard中写入成功了,多少个失败了。

2 Restful API

说明:以下所有操作均在本地启动ES服务器和Kibana服务器后通过登录http://localhost:5601网页的Console控制台中进行

2.1 创建空索引
代码语言:javascript复制
PUT /heshengfu
{
    "settings": {
        "number_of_shards": 2,
        "number_of_replicas": 0,
        "write.wait_for_active_shards": 1
    }
}

2.2 修改副本
代码语言:javascript复制
PUT /heshengfu/_settings
{
    "number_of_replicas": 2
}

2.1 创建索引student并插入数据
代码语言:javascript复制
POST /student/_doc/1001 
{ 
  "id":1001, 
  "name":"张三", 
  "age":20,
   "sex":"男" 
} 
//插入成功后的响应信息 
{ 
  "_index" : "student", 
  "_type" : "_doc",
   "_id" : "1001", 
   "_version" : 1, 
   "result" : "created", 
   "_shards" : 
     { "total" : 2, 
       "successful" : 1, 
       "failed" : 0 
       }, 
       "_seq_no" : 0, 
       "_primary_term" : 1
     }
 //不指定id,ES帮我们自动生成
POST /student/_doc
{
  "id": 1002,
  "name": "李四",
  "age":21,
  "sex": "男"
}
//插入成功后响应信息
{
  "_index" : "student",
  "_type" : "_doc",
  "_id" : "I5KOOXQBvGaqBPqBTsaI",
  "_version" : 1,
  "result" : "created",
  "_shards" : {
    "total" : 2,
    "successful" : 1,
    "failed" : 0
  },
  "_seq_no" : 1,
  "_primary_term" : 1
}
//可以看到ES给这个文档分配的Id为"I5KOOXQBvGaqBPqBTsaI",而请求json体中的id参数是失效的
//再添加一条数据
POST /student/_doc
{
  "name": "晓雪",
  "age": 20,
  "sex": "女"
}
//返回信息
{
  "_index" : "student",
  "_type" : "_doc",
  "_id" : "JpLOOnQBvGaqBPqBU8aa",
  "_version" : 1,
  "result" : "created",
  "_shards" : {
    "total" : 2,
    "successful" : 1,
    "failed" : 0
  },
  "_seq_no" : 8,
  "_primary_term" : 1
}  
代码语言:javascript复制
2.3 更新数据

在Elasticsearch中,是不允许直接修改文档数据的,但是可以通过覆盖的方式进行更新

代码语言:javascript复制
//通过下面这种方式来更新更新
POST /student/_update/JpLOOnQBvGaqBPqBU8aa

  "doc":{
     "age": 22
  }
}

替换和更新的不同:替换是每次都会去替换,更新是有新的东西就更新,没有新的修改就不更新,更新比替换的性能好

3 删除操作

代码语言:javascript复制
3.1 删除索引

DELETE /stuent
//慎用,业务中需要时才执行此操作
3.2 删除数据
代码语言:javascript复制
//先插入一条数据,然后再执行删除
POST /student/_doc
{
  "name": "王五",
  "age": 25,
  "sex": "男"
}
//返回文档的id为JZKsOXQBvGaqBPqBj8b9
//执行删除操作
DELETE /student/_doc/JZKsOXQBvGaqBPqBj8b9
//返回信息如下
{
  "_index" : "student",
  "_type" : "_doc",
  "_id" : "JZKsOXQBvGaqBPqBj8b9",
  "_version" : 2,
  "result" : "deleted",
  "_shards" : {
    "total" : 2,
    "successful" : 1,
    "failed" : 0
  },
  "_seq_no" : 5,
  "_primary_term" : 1
}
4 查询数据
4.1 根据ID搜索数据
代码语言:javascript复制
GET /student/_doc/1001
//响应信息
{
  "_index" : "student",
  "_type" : "_doc",
  "_id" : "1001",
  "_version" : 1,
  "_seq_no" : 0,
  "_primary_term" : 1,
  "found" : true,
  "_source" : {
    "id" : 1001,
    "name" : "张三",
    "age" : 20,
    "sex" : "男"
  }
}
4.2 查询全部数据
代码语言:javascript复制
GET /student/_search  //默认最多返回10条数据
//响应信息如下:
{
  "took" : 0,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 3,
      "relation" : "eq"
    },
    "max_score" : 1.0,
    "hits" : [
      {
        "_index" : "student",
        "_type" : "_doc",
        "_id" : "1001",
        "_score" : 1.0,
        "_source" : {
          "id" : 1001,
          "name" : "张三",
          "age" : 20,
          "sex" : "男"
        }
      },
      {
        "_index" : "student",
        "_type" : "_doc",
        "_id" : "I5KOOXQBvGaqBPqBTsaI",
        "_score" : 1.0,
        "_source" : {
          "id" : 1002,
          "name" : "李四",
          "age" : 21,
          "sex" : "男"
        }
      },
      {
        "_index" : "student",
        "_type" : "_doc",
        "_id" : "JpLOOnQBvGaqBPqBU8aa",
        "_score" : 1.0,
        "_source" : {
          "name" : "晓雪",
          "age" : 22,
          "sex" : "女"
        }
      }
    ]
  }
}

代码语言:javascript复制
took:    ElasticSearch运行查询耗时(单位ms) 
timed_out: 搜索请求是否超时 
_shards: 搜索了多少碎片,并对多少碎片成功、失败或跳过进行了细分 
max_score: 找到最相关文档的得分 
hits.total.value: 找到了多少匹配的文档 
hits.sort: 文档的排序位置 
hits._sort: 文档的相关性评分(在使用match_all时不适用)
代码语言:javascript复制

4.4 DSL查询

Elasticsearch提供丰富且灵活的查询领域特点语言查询叫做DSL查询(Query DSL),它允许你构建
更加复杂、强大的查询

//搜索年龄为21岁的学生
POST /student/_search
{
  "query":{
    "match":{
      "age": 21
    }
  }
}
//条件匹配,match中的字段为需要匹配的属性,
//其中的属性可以是student索引中document中的任何属性

对查询结果进行排序

代码语言:javascript复制
POST /student/_search
{
  "query":{
    "match_all": {}
  },
  "sort": {
      "age": {
        "order": "desc"
      }
    }
}
//查询结果
{
  "took" : 1,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 3,
      "relation" : "eq"
    },
    "max_score" : null,
    "hits" : [
      {
        "_index" : "student",
        "_type" : "_doc",
        "_id" : "JpLOOnQBvGaqBPqBU8aa",
        "_score" : null,
        "_source" : {
          "name" : "晓雪",
          "age" : 22,
          "sex" : "女"
        },
        "sort" : [
          22
        ]
      },
      {
        "_index" : "student",
        "_type" : "_doc",
        "_id" : "I5KOOXQBvGaqBPqBTsaI",
        "_score" : null,
        "_source" : {
          "id" : 1002,
          "name" : "李四",
          "age" : 21,
          "sex" : "男"
        },
        "sort" : [
          21
        ]
      },
      {
        "_index" : "student",
        "_type" : "_doc",
        "_id" : "1001",
        "_score" : null,
        "_source" : {
          "id" : 1001,
          "name" : "张三",
          "age" : 20,
          "sex" : "男"
        },
        "sort" : [
          20
        ]
      }
    ]
  }
}

对范围进行过滤

给student索引中每一个文档都加上enter_date字段

代码语言:javascript复制
POST /student/_update/1001
{
  "doc": {
     "enter_date": "2018-10-01"
   }
}

POST /student/_update/I5KOOXQBvGaqBPqBTsaI
{
  "doc": {
     "enter_date": "2019-10-01"
   }
}

POST /student/_update/JpLOOnQBvGaqBPqBU8aa
{
  "doc": {
    "enter_date": "2017-10-01"
 }
}

对学生的入校日期进行过滤,且必须性别为男
GET /student/_search
{
  "query":{
    "bool":{
      "filter":{
        "range": {
          "enter_date": {
             "gt": "2019-06-01"
          }
        }
      },
      "must":{
        "match": {
          "sex": "男"
        }
      }
    }
  }
}

//查询结果如下:
{
  "took" : 1,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 1,
      "relation" : "eq"
    },
    "max_score" : 0.47000363,
    "hits" : [
      {
        "_index" : "student",
        "_type" : "_doc",
        "_id" : "I5KOOXQBvGaqBPqBTsaI",
        "_score" : 0.47000363,
        "_source" : {
          "id" : 1002,
          "name" : "李四",
          "age" : 21,
          "sex" : "男",
          "enter_date" : "2019-10-01"
        }
      }
    ]
  }
}

  • gt :: 大于
  • gte :: 大于等于
  • lt :: 小于
  • lte :: 小于等于

短语查询

给每个学生添加一个address字段

代码语言:javascript复制
POST /student/_update/1001
{
  "doc": {
    "address": "湖南长沙"
  }
}

POST /student/_updateI/5KOOXQBvGaqBPqBTsaI
{
  "doc": {
    "address": "湖南长沙"
  }
}

POST /student/_update/JpLOOnQBvGaqBPqBU8aa
{
  "doc": {
    "address": "广东广州"
  }
}
// 然后进行短语匹配查询
 GET /student/_search
  { 
    "query": 
      { 
        "match_phrase": { "address": "湖南长沙" } 
       } 
   } 
   //查询结果如下:
{
  "took" : 63,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 1,
      "relation" : "eq"
    },
    "max_score" : 3.9233167,
    "hits" : [
      {
        "_index" : "student",
        "_type" : "_doc",
        "_id" : "I5KOOXQBvGaqBPqBTsaI",
        "_score" : 3.9233167,
        "_source" : {
          "id" : 1002,
          "name" : "李四",
          "age" : 21,
          "sex" : "男",
          "birth_day" : "1999-06-01",
          "enter_date" : "2019-10-01",
          "address" : "湖南长沙"
        }
      }
    ]
  }
}

注意:

  • match 中如果加空格,那么会被认为两个单词,包含任意一个单词将被查询到
  • match_parase 将忽略空格,将该字符认为一个整体,会在索引中匹配包含这个整体的文档

高亮显示

代码语言:javascript复制
GET /student/_search
{
  "query": {
    "match": {
      "name": "晓雪"
    }
  },
  
  "highlight":{
      "fields": {
        "name":{}
      }
    }
}
//搜索结果如下:
{
  "took" : 75,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 1,
      "relation" : "eq"
    },
    "max_score" : 1.9616584,
    "hits" : [
      {
        "_index" : "student",
        "_type" : "_doc",
        "_id" : "JpLOOnQBvGaqBPqBU8aa",
        "_score" : 1.9616584,
        "_source" : {
          "name" : "晓雪",
          "age" : 22,
          "sex" : "女",
          "birth_day" : "1998-06-01",
          "enter_date" : "2017-10-01",
          "address" : "广东广州"
        },
        "highlight" : {
          "name" : [
            "<em>晓</em><em>雪</em>"
          ]
        }
      }
    ]
  }
}

搜索时使用highlights.fields.field对需要的字段进行高亮显示

5 小结

本文对使用ElesticSearch的Restful API进行简单的增删改查进行了详细地示例演示,并涉及简单的根据ID搜索、全量搜素和稍微复杂的DSL搜索,希望读者看完本文都能有切实的收获。下一篇文章,笔者将介绍使用Restful API对数据进行批量和聚合操作,敬请期待

参考阅读

鲁班学院分布式系统ElesticSearch学习笔记文档

https://www.yuque.com/books/share/9f4576fb-9aa9-4965-abf3-b3a36433faa6/ice1ww

--END--

0 人点赞