例子:
比如:当我们有多个线程并发去修改
POST 54288.top:9200/blog/t_article/1/_update
{
"doc": {
"title": "修改修改"
}
}
这样可能会出现 先执行的线程被后执行的线程覆盖,达不到我们预期的效果
我们之前就学习了es可以通过version这个字段来实现乐观锁,进行并发控制;
我们每次修改的时候,带着这个current version去执行修改,如果一旦发现数据已
经被别人给修改了,version号跟之前自己获取的已经不一样了; 那么必须重新获取新
的version号再次尝试修改
悲观锁----全局锁
我们可以在执行更新操作之前获取一个锁,获取到锁的线程才能进行操作,没获取到的线程就不能执行,报错。
直接来一个空数据就行
PUT /fs/lock/global/_create
{}
fs: 你要上锁的那个index
lock: 就是你指定的一个对这个index上全局锁的一个type
global: 就是你上的全局锁对应的这个doc的id
_create:强制必须是创建,如果/fs/lock/global这个doc已经存在,那么创建失败,报错
这里假定/fs/lock/global/ 是固定的。
释放锁
DELETE /fs/lock/global
全局锁的优点和缺点
优点:操作非常简单,非常容易使用,成本低
缺点:你直接就把整个index给上锁了,这个时候对index中所有的doc的操作,都会被block住,导致整个系统的并发能力很低
上锁解锁的操作不是频繁,然后每次上锁之后,执行的操作的耗时不会太长,用这种方式,方便
注意
由于es6.x版本以上一个index只能有一个type,所以这里的fs我们可以假定 blog index的锁是对应fs index
疑问
global这个到底是固定死的。使用doc的id,如果是doc的id应该不算是全局锁吧?
因为这样就话就单独锁住当前的doc,还能操作其他doc,不算直接锁住整个index吧,那如果是这样的话为什么不直接使用doc的id呢??
悲观锁----基于doc
document锁,是用脚本进行上锁
注册模板
POST 54288.top:9200/_scripts/judge-lock
{
"script": {
"lang": "painless",
"source": "if ( ctx._source.process_id != params.process_id ) { Debug.explain('已经有其他线程在操作'); } ctx.op = 'noop';"
}
}
POST 54288.top:9200/fs/lock/1/_update
{
"upsert": { "process_id": 123 },
"script": {
"id": "judge-lock",
"params": {
"process_id": 123
}
}
}
"process_id": 123 是指线程id/进程id。是你的要执行增删改操作的进程的唯一
id,比如说可以在java系统,启动的时候,给你的每个线程都用UUID自动生成一个
thread id,你的系统进程启动的时候给整个进程也分配一个UUID。process_id + thread_id
就代表了某一个进程下的某个线程的唯一标识。可以自己用UUID生成一个唯一ID
ctx.op = 'noop' 代表什么都不做
Debug.explain('已经有其他线程在操作') 抛出异常
使用upsert来实现document锁,当/fs/lock/1不存在则创建,存在则执行update的script
当创建成功的时候,代表成功获取到锁,之前没有线程持有该doc的锁,
如果已经有线程持有锁(/fs/lock/1存在了)执行script脚本,判断当前线程是不是
持有锁的线程,所有不是直接抛出异常
POST 54288.top:9200/fs/lock/1/_update
{
"upsert": { "process_id": 234 },
"script": {
"id": "judge-lock",
"params": {
"process_id": 234
}
}
}
返回
{
"error": {
"root_cause": [
{
"type": "remote_transport_exception",
"reason": "[5RvrBG2][172.18.119.64:9300][indices:data/write/update[s]]"
}
],
"type": "illegal_argument_exception",
"reason": "failed to execute script",
"caused_by": {
"type": "script_exception",
"reason": "runtime error",
"painless_class": "java.lang.String",
"to_string": "已经有其他线程在操作",
"java_class": "java.lang.String",
"script_stack": [
"Debug.explain('已经有其他线程在操作'); } ",
" ^---- HERE"
],
"script": "judge-lock",
"lang": "painless",
"caused_by": {
"type": "painless_explain_error",
"reason": null
}
}
},
"status": 400
}
如果是就什么都不操作,返回noop
POST 54288.top:9200/fs/lock/1/_update
{
"upsert": { "process_id": 123 },
"script": {
"id": "judge-lock",
"params": {
"process_id": 123
}
}
}
返回
{
"_index": "fs",
"_type": "lock",
"_id": "1",
"_version": 7,
"result": "noop",
"_shards": {
"total": 0,
"successful": 0,
"failed": 0
}
}
释放该线程的锁
现在有个线程123要将用户A将钱转给用户B,500元
那么我们有个操作要用户A的金额减500 用户B的金额+500
这样线程123就会同时对A和B上锁,这样释放的时候就要释放线程123所有的锁
POST /fs/lock/A/_update
{
"upsert": { "process_id": 123 },
"script": {
"lang": "groovy",
"file": "judge-lock",
"params": {
"process_id": 123
}
}
}
POST /fs/lock/B/_update
{
"upsert": { "process_id": 123 },
"script": {
"lang": "groovy",
"file": "judge-lock",
"params": {
"process_id": 123
}
}
}
获取所有的锁,使用scroll
POST /fs/_refresh
GET /fs/lock/_search?scroll=1m
{
"query": {
"term": {
"process_id": 123
}
}
}
PUT /fs/lock/_bulk
{ "delete": { "_id": A}}
{ "delete": { "_id": B}}
painless
painless语法官网
共享锁和排它锁
共享锁:这份数据是共享的,然后多个线程过来,都可以获取同一个数据的共享锁,然后对这个数据执行读操作
排他锁:是排他的操作,只能一个线程获取排他锁,然后执行增删改操作
共享锁和排他锁是互斥的
1.如果是读取数据的话,任何线程都可以加上共享锁,不管原来是不是已经存在共享锁
2.如果原本就有共享锁,这个时候有线程要修改数据,要加排它锁,不能加
3.如果现在有现在在修改数据,加上了排他锁,那么之后的线程不能加排它锁和共享锁
总之就是:
如果有人在读数据,就不允许别人来修改数据
如果有在修改数据,就不允许别人来修改数据,也不允许别人来读取数据
实验
1.DELETE 54288.top:9200/fs
2.注册模板
PUT 54288.top:9200/_scripts/judge-lock-2
{
"script": {
"lang": "painless",
"source": "if (ctx._source.lock_type == 'exclusive') { Debug.explain('已经有其他线程在操作'); } ctx._source.lock_count++;"
}
}
ctx._source.lock_count++;计算有几个共享锁
ctx._source.lock_type == 'exclusive' 这个逻辑处理如果加了排它锁之后要加共享锁的情况。后面有例子
3.加上共享锁,模拟读取数据
POST 54288.top:9200/fs/lock/1/_update
{
"upsert": {
"lock_type": "shared",
"lock_count": 1
},
"script": {
"id": "judge-lock-2"
}
}
查看有几个人在读取 "lock_count"等于 1
GET http://www.54288.top:9200/fs/lock/1
{
"_index": "fs",
"_type": "lock",
"_id": "1",
"_version": 1,
"found": true,
"_source": {
"lock_type": "shared",
"lock_count": 1
}
}
在加共享锁,模拟有人读取数据,重复3步,发现"lock_count"变成 2,说明共享锁
之上可以在加共享锁。。。现在2个人读取数据
4.已经有人上了共享锁,然后有人要上排他锁
PUT /fs/lock/1/_create
{ "lock_type": "exclusive" }
排他锁用的不是upsert语法,create语法,要求lock必须不能存在,直接自己是第一个上锁的人,上的是排他锁
PUT 54288.top:9200/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 [2])",
"index_uuid": "ytneJEPIS3-60q0_6jDh5g",
"shard": "3",
"index": "fs"
}
],
"type": "version_conflict_engine_exception",
"reason": "[lock][1]: version conflict, document already exists (current version [2])",
"index_uuid": "ytneJEPIS3-60q0_6jDh5g",
"shard": "3",
"index": "fs"
},
"status": 409
}
如果已经有人上了共享锁,明显/fs/lock/1是存在的,create语法去上排他锁,肯定会报错
5.解锁共享锁
注册模板
PUT 54288.top:9200/_scripts/unlock-shared
{
"script": {
"lang": "painless",
"source": "if ( --ctx._source.lock_count == 0 ) { ctx.op = 'delete';}"
}
}
先执行--ctx._source.lock_count
在判断是不是为0,是的话删除操作
解锁
POST /fs/lock/1/_update
{
"script": {
"id": "unlock-shared"
}
}
每次解锁一个共享锁,就对lock_count先减1,如果减了1之后,是0,那么说明所有的共享锁都解锁完了,此时就就将/fs/lock/1删除,就彻底解锁所有的共享锁
这里连续解锁2次,就没了
6.上排他锁,再上排他锁
PUT /fs/lock/1/_create
{ "lock_type": "exclusive" }
成功,因为已经解锁了
其他线程
PUT /fs/lock/1/_create
{ "lock_type": "exclusive" }
失败
7.上了排他锁,在上共享锁
POST /fs/lock/1/_update
{
"upsert": {
"lock_type": "shared",
"lock_count": 1
},
"script": {
"id": "judge-lock-2"
}
}
失败,已经存在/fs/lock/1,执行脚本,判断后抛出异常
if (ctx._source.lock_type == 'exclusive') { Debug.explain('已经有其他线程在操作'); }
8.解锁排他锁
DELETE /fs/lock/1