文档 ID 生成
手动生成
- 当我们从外界导入数据到 ES 时,我们并不希望改变原本数据的主键,就需要手动设置 ID
- 手动设置的方式在上面已经学习
- 方式如下:
PUT /index/type/id
自动生成
- 语法如下:
POST /index/type
- 自动生成的 ID,长度是 20 个字符的 GUID,分布式全局唯一
查询指定字段
- 在我们操作 MySQL 时,想必大部分的公司都是禁止使用
select *
的,而要求用到什么列就查询什么列- 而我们上面使用 ES 的方式,就相当于在写
select *
,这是不推荐的,我们在实际使用的时候,可以只查询指定的字段- 语法如下:
GET /index/type/id?_source_includes=字段名,多个字段逗号隔开
article/_doc/3?_source_includes=title,red
强制创建
在上面的学习中,我们发现,替换文档和创建文档的 API 是一样的,这就意味着,我们在创建文档的过程中,可能这个文档其实已经存在了,但是我们不知道,通过创建的 API 直接把该文档进行了替换,这不是我们想要的结果,因此需要使用强制创建
- 强制创建限定这个 API 只能是创建文档,如果指定 ID 的文档已经存在了,就抛出异常
- 语法如下:
PUT /index/type/id/_create
article/_doc/4/_create
Lazy Delete
- ES 在替换文档、修改文档的时候,本质上并不是直接替换,而是先将旧文档标记为 deleted,然后再创建新的文档
- 此时旧的文档实际上还是存在于索引库里的,并不会立即删除,集群会找个合适的时机,一并删除所有标记deleted 的文档
- 同样,删除的 API 也只是把文档标记了 deleted,并不是立即删除
- 如果删除一条数据立马删除的话,所有分片和副本都要立马删除,对 es 集群压力太大
手动设置版本号
- 我们的数据库中可能已经维护了版本号,现在需要同步这些数据,同步的时候需要手动设置版本号
- 可以通过使用
external version
控制,设置的时候版本号必须大于当前文档的版本号- 语法如下:
PUT /index/type/id?version=要修改的版本号&version_type=external
article/_doc/4/?version=2&version_type=external
- 两个客户端同时修改,只会有一个会修改成功
批量操作
批量查询
- 当我们要根据 id 查询的时候,如果一条一条查,性能损耗和网络开销大,可以使用批量查询
- 语法如下:
GET /_mget
-
_mget
是全查询,操作起来并不是那么方便,并且容易报出request body or source parameter is required
异常,因此使用率较少
批量增删改
-
_bulk
操作将文档的增删改查一系列操作,通过一次请求全部做完,减少网络传输次数
POST /_bulk
- 注意,
_bulk
操作的形式是多个 JSON,每个 JSON 写完必须换行,而在 JSON 内则不可以换行- 多个 JSON 之间操作互不影响,即使前面的报错了,其他行也可以正常执行
{"delete": {"_index": "article", "_id": 6}}
{"create": {"_index": "article", "_id": 7}}
{"title": "我是批量操作中创建的数据,ID是7 "}
{"update": {"_index": "article", "_id": 5}}
{"doc": {"content": "我在批量操作中进行了修改,但是PHP依然是最好的语言"}}
-
delete
:删除一个文档,只要 1 个 JSON 串就可以了-
create
:相当于强制创建,就是:PUT /index/type/id/_create-
index
:普通的 put 操作,可以是创建文档,也可以是全量替换文档-
update
:执行的是局部更新partial update
操作
- 使用 JSON 格式发送数据
ES的并发问题
- 通常情况下,ES 在多线程操作的时候,会产生并发问题
- 举个例子,我跟你在淘宝在同一时间下单买了同一本书,两个线程同时去 es 扣这本书的库存,库存有 100 本书,正常情况扣完库存后应该变成 98 本,但如果两个线程同时扣减就会有并发冲突,就会变成这样如下例子
- 可以看到库存的值变成了 97 本,与我们期望中的 94 本不符
- 这一现象也叫
超卖
,对数据库的库存扣减的时候也会出现这种并发冲突的情况
- 为了控制并发问题,我们通常采用
锁
机制- 分为
悲观锁
和乐观锁
两种机制
悲观锁
悲观锁的思路是在线程1读到库存是 100 的时候就把 es 的这条库存给锁上,阻止线程2去读库存,线程1扣完库存并把新的库存量 97 写入 es 后,才允许线程2去读取库存,这时线程2读取出来的库存是 99 而不是100,扣减完变成 98 再写入es
这种思路实际是把并发的线程转成串行执行,非常方便,直接加锁就行,对程序来说不需要做额外的操作,但是并发能力低,同一时间只能一条线程去扣减库存
乐观锁
乐观锁的思路是给 es 的库存附加一个版本号,并发冲突的情况下,线程1读取库存库存 100 (版本号是1)线程2 读取库存 100(版本号1)线程1扣减库存后变成 99,线程2扣减后变成 99,线程1写入库存 99 到 es 前对比库存版本号(线程1读取的库存版本号为1,当前 es 的库存版本号为1)发现一致,于是写入库存 99 到 es 并更新库存版本号为 2,线程2写入库存 99 到 es 前对比库存版本号(线程2读取的库存版本号为1,当前 es 的库存版本号为2)发现不一致,线程2写入失败,线程2重新读取库存 99(版本号2)线程2扣减后变成 98,线程2写入库存98 到 es 前对比库存版本号(都是2)发现一致,于是写入库存 98 到 es 并更新库存版本号为3
这种方式只是在把库存写入 es 那一刻检查一下版本号判断是否可以写入就行了,不需要把库存锁上,因此并发能力很高,但这种方式编码的时候比较麻烦,每次更新库存都要去比对版本号和更新版本号,版本号对不上的时候还需要重新读取库存并扣库存
ES的乐观锁
ES 对文档的增删改都是基于版本号,也就是 _version
字段,新版本基于 _seq_no
字段
- 多次新增同一个文档,发现版本号递增
- 删除文档再新增,发现版本号依然递增,验证了延迟删除策略
乐观锁演示
- 查询数据当前的版本号
GET /article/_doc/7
article/_doc/4
- 客户端1更新,带版本号
PUT /article/_doc/4?if_seq_no=7&if_primary_term=1
- 客户端2更新,带版本号,报错
PUT /article/_doc/4?if_seq_no=7&if_primary_term=1
- 客户端2重新查询版本号
article/_doc/4
- 客户端2更新,带版本号
article/_doc/4?if_seq_no=10&if_primary_term=1
更新时的重试机制
- 更新时的重试机制(retry_on_conflict)
- 有时候我们在更新时,并不希望失败了直接报错,而是想让程序有一定的重试机制,重试一定次数后如果还是失败,就报错,这时候可以使用retry_on_conflict指定重试次数
- 语法如下:
POST /index/type/4/_update?retry_on_conflict=次数
article/_doc/4/_update?retry_on_conflict=2
- 还可以和
_version
一起使用 - 使用之前你可以先查看一下版本号
POST /index/type/4/_update?retry_on_conflict=3&version=4&version_type=external