ElasticSearch之共享锁和排它锁

例子:
比如:当我们有多个线程并发去修改
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
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值