Elasticsearch系列---实现分布式锁

本文介绍了Elasticsearch如何实现分布式锁,包括乐观锁、悲观锁的概念和操作步骤。详细讲解了全局锁、document level级别的锁以及共享锁与排他锁的原理,并通过实例展示了在Elasticsearch中加锁、解锁的过程及其优缺点。
摘要由CSDN通过智能技术生成

概要

Elasticsearch在文档更新时默认使用的是乐观锁方案,而Elasticsearch利用文档的一些create限制条件,也能达到悲观锁的效果,我们一起来看一看。

乐观锁与悲观锁

乐观锁

ES默认实现乐观锁,所有的数据更新默认使用乐观锁机制。document更新时,必须要带上currenct version,更新时与document的version进行比较,如果相同进行更新操作,不相同表示已经被别的线程更新过了,此时更新失败,并且重新获取新的version再尝试更新。

悲观锁

我们举一个这样的例子:Elasticsearch存储文件系统的目录、文件名信息,有多个线程需要对/home/workspace/ReadMe.txt进行追加修改,而且是并发执行的,有先后顺序之分,跟之前的库存更新案例有点不一样,此时单纯使用乐观锁,可能会出现乱序的问题。

这种场景就需要使用悲观锁控制,保证线程的执行顺序,有一个线程在修改,其他的线程只能挂起等待。悲观锁通过/index/lock/实现,只有一个线程能做修改操作,其他线程block掉。

悲观锁有三种,分别对应三种粒度,由粗到细可为分:

  • 全局锁:最粗的锁,直接锁整个索引
  • document锁:指定id加锁,只锁一条数据,类似于数据库的行锁
  • 共享锁和排他锁:也叫读写锁,针对一条数据分读和写两种操作,一般共享锁允许多个线程对同一条数据进行加锁,排他锁只允许一个线程对数据加锁,并且排他锁和共享锁互斥。
锁的基本操作步骤

我们使用锁的基本步骤都是一样的,无论是关系型数据库、Redis/Memcache/Zookeeper分布式锁,还是今天介绍的Elasticsearch实现的锁机制,都有如下三步:

  • 上锁
  • 执行事务方法
  • 解锁

全局锁

假定有两个线程,线程1和线程2

  1. 线程1上锁命令:
PUT /files/file/global/_create
{
   }
  • files表示索引名称。
  • file为type,6.3.1一个索引只允许有一个type,选用file作用type名称。
  • global:即document的id,固定写为global表示全局锁,或者使用专门的索引进行加锁操作。
  • _create: 强制必须是创建,如果已经存在,那么创建失败,报错。
  1. 线程1执行事务方法:更新文件名
POST /files/file/global/_update
{
   
  "doc": {
   
    "name":"ReadMe.txt"
  }
}
  1. 线程2尝试加锁,失败,此时程序进行重试阶段,直到线程1释放锁
# 请求:
PUT /files/file/global/_create
{
   }

# 响应:
{
   
  "error": {
   
    "root_cause": [
      {
   
        "type": "version_conflict_engine_exception",
        "reason": "[file][global]: version conflict, document already exists (current version [1])",
        "index_uuid": "_6E1d7BLQmy9-7gJptVp7A",
        "shard": "2",
        "index": "files"
      }
    ],
    "type": "version_conflict_engine_exception",
    "reason": "[file][global]: version conflict, document already exists (current version [1])",
    "index_uuid": "_6E1d7BLQmy9-7gJptVp7A",
    "shard": "2",
    "index": "files"
  },
  "status": 409
}
  1. 线程1释放锁

DELETE files/file/global

  1. 线程2加锁
PUT /files/file/global/_create
{
   }

响应

{
  "_index": "files",
  "_type": "file",
  "_id": "global",
  "_version": 3,
  "result": "created",
  "_shards": {
    "total": 2,
    "successful": 1,
    "failed": 0
  },
  "_seq_no": 2,
  "_primary_term": 1
}
  1. 加锁成功,然后执行事务方法。
优缺点

全局锁本质上是所有线程都用_create语法来创建id为global的文档,利用Elasticsearch对_create语法的校验来实现锁的目的。

  • 优点:操作简单,容易使用,成本低。

  • 缺点:直接锁住整个索引,除了加锁的那个线程,其他所有对此索引的线程都block住了,并发量较低。

  • 适用场景:读多写少的数据,并且加解锁的时间非常短,类似于数据库的表锁。

注意事项:加锁解锁的控制必须严格在程序里定义,因为单纯基于doc的锁控制,如果id固定使用global,在有锁的情况,任何线程执行delete操作都是可以成功的,因为大家都知道id。

document level级别的锁

document level级别的锁是更细粒度的锁,以文档为单位进行锁控制。

我们新建一个索引专门用于加锁操作:

PUT /files-lock/_mapping/lock
{
   
  "properties": {
   
    
  }
}

我们先创建一个script脚本,ES6.0以后默认使用painless脚本:

POST _scripts
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值