乐趣区

关于elasticsearch:ElasticSearch-文档并发处理以及文档路由

@[toc]
ElasticSearch 系列教程咱们后面曾经连着发了两篇了,明天第三篇,咱们来聊一聊 Es 中的文档并发解决和文档路由问题。

本文是松哥所录视频教程的一个笔记,笔记简明扼要,残缺内容小伙伴们能够参考视频,视频下载链接:https://pan.baidu.com/s/1TwyO… 提取码: aee2

1. ElasticSearch 文档基本操作

1.1 新建文档

首先新建一个索引。

而后向索引中增加一个文档:

PUT blog/_doc/1
{
  "title":"6. ElasticSearch 文档基本操作",
  "date":"2020-11-05",
  "content":"微信公众号 ** 江南一点雨 ** 后盾回复 **elasticsearch06** 下载本笔记。首先新建一个索引。"
}

1 示意新建文档的 id。

增加胜利后,响应的 json 如下:

{
  "_index" : "blog",
  "_type" : "_doc",
  "_id" : "1",
  "_version" : 1,
  "result" : "created",
  "_shards" : {
    "total" : 2,
    "successful" : 2,
    "failed" : 0
  },
  "_seq_no" : 0,
  "_primary_term" : 1
}
  • _index 示意文档索引。
  • _type 示意文档的类型。
  • _id 示意文档的 id。
  • _version 示意文档的版本(更新文档,版本会主动加 1,针对一个文档的)。
  • result 示意执行后果。
  • _shards 示意分片信息。
  • _seq_no_primary_term 这两个也是版本控制用的(针对以后 index)。

增加胜利后,能够查看增加的文档:

当然,增加文档时,也能够不指定 id,此时零碎会默认给出一个 id,如果不指定 id,则须要应用 POST 申请,而不能应用 PUT 申请。

POST blog/_doc
{
  "title":"666",
  "date":"2020-11-05",
  "content":"微信公众号 ** 江南一点雨 ** 后盾回复 **elasticsearch06** 下载本笔记。首先新建一个索引。"
}

1.2 获取文档

Es 中提供了 GET API 来查看存储在 es 中的文档。应用形式如下:

GET blog/_doc/RuWrl3UByGJWB5WucKtP

下面这个命令示意获取一个 id 为 RuWrl3UByGJWB5WucKtP 的文档。

如果获取不存在的文档,会返回如下信息:

{
  "_index" : "blog",
  "_type" : "_doc",
  "_id" : "2",
  "found" : false
}

如果仅仅只是想探测某一个文档是否存在,能够应用 head 申请:

如果文档不存在,响应如下:

如果文档存在,响应如下:

当然也能够批量获取文档。

GET blog/_mget
{"ids":["1","RuWrl3UByGJWB5WucKtP"]
}

这里可能有小伙伴有疑难,GET 申请居然能够携带申请体?

某些特定的语言,例如 JavaScript 的 HTTP 申请库是不容许 GET 申请有申请体的,实际上在 RFC7231 文档中,并没有规定 GET 申请的申请体该如何解决,这样造成了肯定水平的凌乱,有的 HTTP 服务器反对 GET 申请携带申请体,有的 HTTP 服务器则不反对。尽管 es 工程师偏向于应用 GET 做查问,然而为了保障兼容性,es 同时也反对应用 POST 查问。例如下面的批量查问案例,也能够应用 POST 申请。

1.3 文档更新

1.3.1 一般更新

留神,文档更新一次,version 就会自增 1。

能够间接更新整个文档:

PUT blog/_doc/RuWrl3UByGJWB5WucKtP
{"title":"666"}

这种形式,更新的文档会笼罩掉原文档。

大多数时候,咱们只是想更新文档字段,这个能够通过脚本来实现。

POST blog/_update/1
{
  "script": {
    "lang": "painless",
    "source":"ctx._source.title=params.title",
    "params": {"title":"666666"}
  }
}

更新的申请格局:POST {index}/_update/{id}

在脚本中,lang 示意脚本语言,painless 是 es 内置的一种脚本语言。source 示意具体执行的脚本,ctx 是一个上下文对象,通过 ctx 能够拜访到 _source_title 等。

也能够向文档中增加字段:

POST blog/_update/1
{
  "script": {
    "lang": "painless",
    "source":"ctx._source.tags=[\"java\",\"php\"]"
  }
}

增加胜利后的文档如下:

通过脚本语言,也能够批改数组。例如再减少一个 tag:

POST blog/_update/1
{
  "script":{
    "lang": "painless",
    "source":"ctx._source.tags.add(\"js\")"
  }
}

当然,也能够应用 if else 结构略微简单一点的逻辑。

POST blog/_update/1
{
  "script": {
    "lang": "painless",
    "source": "if (ctx._source.tags.contains(\"java\")){ctx.op=\"delete\"}else{ctx.op=\"none\"}"
  }
}

1.3.2 查问更新

通过条件查问找到文档,而后再去更新。

例如将 title 中蕴含 666 的文档的 content 批改为 888。

POST blog/_update_by_query
{
  "script": {"source": "ctx._source.content=\"888\"","lang":"painless"},"query": {"term": {"title":"666"}
  }
}

1.4 删除文档

1.4.1 依据 id 删除

从索引中删除一个文档。

删除一个 id 为 TuUpmHUByGJWB5WuMasV 的文档。

DELETE blog/_doc/TuUpmHUByGJWB5WuMasV

如果在增加文档时指定了路由,则删除文档时也须要指定路由,否则删除失败。

1.4.2 查问删除

查问删除是 POST 申请。

例如删除 title 中蕴含 666 的文档:

POST blog/_delete_by_query
{
  "query":{
    "term":{"title":"666"}
  }
}

也能够删除某一个索引下的所有文档:

POST blog/_delete_by_query
{
  "query":{"match_all":{}
  }
}

1.5 批量操作

es 中通过 Bulk API 能够执行批量索引、批量删除、批量更新等操作。

首先须要将所有的批量操作写入一个 JSON 文件中,而后通过 POST 申请将该 JSON 文件上传并执行。

例如新建一个名为 aaa.json 的文件,内容如下:

首先第一行:index 示意要执行一个索引操作(这个示意一个 action,其余的 action 还有 create,delete,update)。_index 定义了索引名称,这里示意要创立一个名为 user 的索引,_id 示意新建文档的 id 为 666。

第二行是第一行操作的参数。

第三行的 update 则示意要更新。

第四行是第三行的参数。

留神,结尾要空出一行。

aaa.json 文件创建胜利后,在该目录下,执行申请命令,如下:

curl -XPOST "http://localhost:9200/user/_bulk" -H "content-type:application/json" --data-binary @aaa.json

执行实现后,就会创立一个名为 user 的索引,同时向该索引中增加一条记录,再批改该记录,最终后果如下:

2. ElasticSearch 文档路由

es 是一个分布式系统,当咱们存储一个文档到 es 上之后,这个文档实际上是被存储到 master 节点中的某一个主分片上。

例如新建一个索引,该索引有两个分片,0 个正本,如下:

接下来,向该索引中保留一个文档:

PUT blog/_doc/a
{"title":"a"}

文档保留胜利后,能够查看该文档被保留到哪个分片中去了:

GET _cat/shards/blog?v

查看后果如下:

index shard prirep state   docs store ip        node
blog  1     p      STARTED    0  208b 127.0.0.1 slave01
blog  0     p      STARTED    1 3.6kb 127.0.0.1 master

从这个后果中,能够看出,文档被保留到分片 0 中。

那么 es 中到底是依照什么样的规定去调配分片的?

es 中的路由机制是通过哈希算法,将具备雷同哈希值的文档放到一个主分片中,分片地位的计算形式如下:

shard=hash(routing) % number_of_primary_shards

routing 能够是一个任意字符串,es 默认是将文档的 id 作为 routing 值,通过哈希函数依据 routing 生成一个数字,而后将该数字和分片数取余,取余的后果就是分片的地位。

默认的这种路由模式,最大的劣势在于负载平衡,这种形式能够保证数据平均分配在不同的分片上。然而他有一个很大的劣势,就是查问时候无奈确定文档的地位,此时它会将申请播送到所有的分片下来执行。另一方面,应用默认的路由模式,前期批改分片数量不不便。

当然开发者也能够自定义 routing 的值,形式如下:

PUT blog/_doc/d?routing=javaboy
{"title":"d"}

如果文档在增加时指定了 routing,则查问、删除、更新时也须要指定 routing。

GET blog/_doc/d?routing=javaboy

自定义 routing 有可能会导致负载不平衡,这个还是要结合实际状况抉择。

典型场景:

对于用户数据,咱们能够将 userid 作为 routing,这样就能保障同一个用户的数据保留在同一个分片中,检索时,同样应用 userid 作为 routing,这样就能够精准的从某一个分片中获取数据。

3. ElasticSearch 版本控制

当咱们应用 es 的 API 去进行文档更新时,它首先读取原文档进去,而后对原文档进行更新,更新实现后再从新索引整个文档。不管你执行多少次更新,最终保留在 es 中的是最初一次更新的文档。然而如果有两个线程同时去更新,就有可能出问题。

要解决问题,就是锁。

3.1 锁

乐观锁

很乐观,每一次去读取数据的时候,都认为他人可能会批改数据,所以屏蔽所有可能毁坏数据完整性的操作。关系型数据库中,乐观锁应用较多,例如行锁、表锁等等。

乐观锁

很乐观,每次读取数据时,都认为他人不会批改数据,因而也不锁定数据,只有在提交数据时,才会查看数据完整性。这种形式能够省去锁的开销,进而进步吞吐量。

在 es 中,实际上应用的就是乐观锁。

3.2 版本控制

es6.7 之前

在 es6.7 之前,应用 version+version_type 来进行乐观并发管制。依据后面的介绍,文档每被批改一个,version 就会自增一次,es 通过 version 字段来确保所有的操作都有序进行。

version 分为外部版本控制和内部版本控制。

3.2.1 外部版本

es 本人保护的就是外部版本,当创立一个文档时,es 会给文档的版本赋值为 1。

每当用户批改一次文档,版本号就回自增 1。

如果应用外部版本,es 要求 version 参数的值必须和 es 文档中 version 的值相当,能力操作胜利。

3.2.2 内部版本

也能够保护内部版本。

在增加文档时,就指定版本号:

PUT blog/_doc/1?version=200&version_type=external
{"title":"2222"}

当前更新的时候,版本要大于已有的版本号。

  • vertion_type=external 或者 vertion_type=external_gt 示意当前更新的时候,版本要大于已有的版本号。
  • vertion_type=external_gte 示意当前更新的时候,版本要大于等于已有的版本号。

3.2.3 最新计划(Es6.7 之后)

当初应用 if_seq_noif_primary_term 两个参数来做并发管制。

seq_no 不属于某一个文档,它是属于整个索引的(version 则是属于某一个文档的,每个文档的 version 互不影响)。当初更新文档时,应用 seq_no 来做并发。因为 seq_no 是属于整个 index 的,所以任何文档的批改或者新增,seq_no 都会自增。

当初就能够通过 seq_noprimary_term 来做乐观并发管制。

PUT blog/_doc/2?if_seq_no=5&if_primary_term=1
{"title":"6666"}

最初,松哥还收集了 50+ 个我的项目需要文档,想做个我的项目练练手的小伙伴无妨看看哦~



需要文档地址:https://github.com/lenve/javadoc

退出移动版