# 基于共享锁和排他锁实现悲观锁并发控制

# 读写锁概念

  • 共享锁:所有线程都可获得该锁,用于读取数据
  • 排他锁:只能有一个线程获得该锁,用于写数据

共享锁和排他锁的作用是读写分离,但是共享锁和排他锁是互斥的:

  • 有共享锁则排他锁不能被获取
  • 有排他锁则共享锁不能被获取

简单说就是读写操作只能存在一种

TIP

在我的认知里面读写分离,是为了解决写数据并发竞争,提高读并发性能,因为适合于读大于写的场景;

但是有一个问题,有可能一直不释放造成写饥饿?

下面来实现读写锁实验

# 共享锁/读锁获取

POST /fs/lock/1/_update
{
  "upsert": {
    "lock_type": "shared",
    "lock_count": 1
  },
  "script": {
     "lang": "groovy",
     "inline": "if ( ctx._source.lock_type == 'exclusive' ) { assert false }; ctx._source.lock_count++;"
  }
}

多次获取共享锁结果
{
  "_index": "fs",
  "_type": "lock",
  "_id": "1",
  "_version": 6,
  "found": true,
  "_source": {
    "lock_type": "shared",
    "lock_count": 2
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
  • lock_type:shared 共享锁/读锁、exclusive 排他锁/写锁
  • lock_count:锁持有数量,用于共享锁的统计
  • script:当时共享锁的时候,将 lock_count 自增 1

# 获取排他锁

利用 create 语法来创建,当该 doc 存在的时候,就会报错

PUT /fs/lock/1/_create
{ "lock_type": "exclusive" }

报错如下
{
  "error": {
    "root_cause": [
      {
        "type": "version_conflict_engine_exception",
        "reason": "[lock][1]: version conflict, document already exists (current version [6])",
        "index_uuid": "ksKH094bQQuh7PaGY8Jb7w",
        "shard": "3",
        "index": "fs"
      }
    ],
    "type": "version_conflict_engine_exception",
    "reason": "[lock][1]: version conflict, document already exists (current version [6])",
    "index_uuid": "ksKH094bQQuh7PaGY8Jb7w",
    "shard": "3",
    "index": "fs"
  },
  "status": 409
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

有共享锁在的话,就需要读线程在操作完成之后释放自己的共享锁

# 释放共享锁

每次执行该操作,就将锁数量自减 1,当等于 0 的时候就删除自己。这样排他锁就能上锁成功了













 







POST /fs/lock/1/_update
{
  "script": "if(--ctx._source.lock_count == 0){ ctx.op = 'delete'}"
}

被删除时返回的数据:result=deleted

{
  "_index": "fs",
  "_type": "lock",
  "_id": "1",
  "_version": 8,
  "result": "deleted",
  "_shards": {
    "total": 2,
    "successful": 1,
    "failed": 0
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

# 释放排他锁

通过删除 doc 来达到释放

DELETE /fs/lock/1
1

# 小结

以自己的观点来看,都是利用了 es 的 doc 创建报错语法和脚本判定等语法来保证数据的竞争上锁的。