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--