Elasticsearch系列---增量更新原理及优势

本篇主要介绍增量更新(partial update,也叫局部更新)的核心原理,介绍6.3.1版本的Elasticsearch脚本使用实例和增量更新的优势

增量更新过程与原理 简单回顾

前文我们有简单介绍过增量的语法,简单回顾一下请求示例:

POST /music/children/1/_update { "doc": { "length": "76" } }

一般从客户端到Elasticsearch,完整的应用请求流程基本是这样的:

客户端先发起GET请求,获取到document信息,展现到前端页面上,供用户进行编辑。

用户编辑完数据后,点击提交。

后台系统处理修改后的数据,并组装好完整的document报文。

发送PUT请求到ES,进行全量替换。

ES将老的document标记为deleted,然后重新创建一个新的document。

Elasticsearch的document是基于不可变模式设计的,所有的document更新,其实都创建了一个新的document出来,再把老的document标记为deleted,增量更新也不例外,只是GET全量document数据,整合新的document,替换老的document这三步操作全在一个shard里完成,毫秒级完成。

增量更新分片之间的交互

12

增量更新document的步骤:

Java客户端向ES集群发送更新请求。

Coodinate Node收到请求,但该document不在当前node上,它将请求转发到Node2节点的P0 shard上。

Node 2检索document,修改_source下的JSON,并且重新索引该document,如果有其他线程修改过该document,有版本冲突的话,会重新尝试更新document,最大重试retry_on_conflict次,超出重试次数后放弃。

如果步骤3操作成功,Node2会将该document的完整内容异步转发到Node1和Node3的replica shard,重新建立索引。一旦所有replica都返回成功,Node2返回成功消息给Coodinate Node。

Coodinate Node响应更新成功消息给客户端,此时ES集群内primary shard和replica shard都已经更新完成。

注意几点:

primary shard向replica shard进行document数据同步时,发送的是document的完整信息,因为异步请求不保证有序,如果发增量信息的话,顺序错乱会导致document内容错误。

只要Coodinate Node向Java客户端响应成功,就表示所有的primary shard向replica shard都完成了更新操作,此时ES集群内的数据是一致的,更新是安全的。

retry策略,ES再次获取document数据和最新版本号,成功就更新,失败再试,最大次数可以设置,如5次:retry_on_conflict=5

retry策略在增量操作无关顺序的场景更适用,比如说计数操作,谁先执行谁后执行,关系不大,最终结果是对的就行。其他的一些场景,如库存的变化,账户余额的变化,直接更新成指定数值的,肯定不能使用retry策略,但可以转化成加减法,如下单时由直接更新库存数量的逻辑改成“当前可用库存数量=库存数量-订单商品数量”,账户余额的更新加减变化的金额,这样可以在一定程度上,把顺序有关转化成顺序无关,就可以更方便的使用retry策略解决冲突的问题。

增量更新的优点

所有的查询、修改和回写操作,都是在ES内部完成的,减小了网络数据传输开销(2次),提升了性能。

相比全量替换的时间间隔(秒级以上),缩短了查询和修改的时间间隔(毫秒级),可以有效降低并发冲突的情况。

使用脚本实现增量更新

Elasticsearch支持使用脚本实现更为灵活的逻辑,6.0版本以后,默认支持的脚本是painless,并且不再支持Groovy,因为Groovy编译有一定概率会出现内存不释放,最终导致Full GC的问题。

我们以英文儿歌的案例为背景,假设document的数据是这样:

{ "_index": "music", "_type": "children", "_id": "2", "_version": 6, "found": true, "_source": { "name": "wake me, shark me", "content": "don't let me sleep too late, gonna get up brightly early in the morning", "language": "english", "length": "55", "likes": 0 } } 内置脚本

现在有这样一个需求:每当有人点击播放一次歌曲时,该document的likes field就自增1,我们可以用简单的脚本来实现:

POST /music/children/2/_update { "script" : "ctx._source.likes++" }

执行一次后,再查询该document,发现likes变成1,每执行一次,likes都自增1,结果符合预期。

外部脚本

对刚刚那个自增需求做一些改动,支持批量更新播放量,自增的数量由参数传入,脚本也可以通过导入的方式,预先编译存储在ES中,使用的时候调用即可。

创建脚本 POST _scripts/music-likes { "script": { "lang": "painless", "source": "ctx._source.likes += params.new_likes" } }

脚本ID为music-likes,参数为new_likes,是可以在调用时传入的。

使用脚本

我们更新时,执行如下请求,就可以调用刚刚创建的脚本

POST /music/children/2/_update { "script": { "id": "music-likes", "params": { "new_likes": 2 } } }

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/zyzfzz.html