Elasticsearch高手进阶篇(64)
数据建模实战_基于全局锁实现悲观锁并发控制
悲观锁的简要说明
基于version的乐观锁并发控制
在数据建模,结合文件系统建模的这个案例,把悲观锁的并发控制,3种锁粒度,都给大家仔细讲解一下
最粗的一个粒度,全局锁
/workspace/projects/helloworld
如果多个线程,都过来,要并发地给/workspace/projects/helloworld下的README.txt修改文件名
实际上要进行并发的控制,避免出现多线程的并发安全问题,比如多个线程修改,纯并发,先执行的修改操作被后执行的修改操作给覆盖了
get current version
带着这个current version去执行修改,如果一旦发现数据已经被别人给修改了,version号跟之前自己获取的已经不一样了; 那么必须重新获取新的version号再次尝试修改
上来就尝试给这条数据加个锁,然后呢,此时就只有你能执行各种各样的操作了,其他人不能执行操作
第一种锁:全局锁,直接锁掉整个waws_fs index
全局锁的上锁实验
PUT /waws_fs/lock/global/_create
{}
- fs: 你要上锁的那个index
- lock: 就是你指定的一个对这个index上全局锁的一个type
- global: 就是你上的全局锁对应的这个doc的id
- _create:强制必须是创建,如果/waws_fs/lock/global这个doc已经存在,那么创建失败,报错
利用了doc来进行上锁
/waws_fs/lock/global /index/type/id --> doc
{
"_index": "waws_fs",
"_type": "lock",
"_id": "global",
"_version": 1,
"result": "created",
"_shards": {
"total": 2,
"successful": 1,
"failed": 0
},
"created": true
}
另外一个线程(开两个客户端,前面提到过)同时尝试上锁
PUT /fs/lock/global/_create
{}
{
"error": {
"root_cause": [
{
"type": "version_conflict_engine_exception",
"reason": "[lock][global]: version conflict, document already exists (current version [1])",
"index_uuid": "V98PfLHrRjKrnIkQU5IbPA",
"shard": "2",
"index": "waws_fs"
}
],
"type": "version_conflict_engine_exception",
"reason": "[lock][global]: version conflict, document already exists (current version [1])",
"index_uuid": "V98PfLHrRjKrnIkQU5IbPA",
"shard": "2",
"index": "waws_fs"
},
"status": 409
}
如果失败,就再次重复尝试上锁
执行各种操作。。。
POST /waws_fs/file/1/_update
{
"doc": {
"name": "README1.txt"
}
}
{
"_index": "waws_fs",
"_type": "file",
"_id": "1",
"_version": 2,
"result": "updated",
"_shards": {
"total": 2,
"successful": 1,
"failed": 0
}
}
DELETE /waws_fs/lock/global
{
"found": true,
"_index": "waws_fs",
"_type": "lock",
"_id": "global",
"_version": 2,
"result": "deleted",
"_shards": {
"total": 2,
"successful": 1,
"failed": 0
}
}
另外一个线程,因为之前发现上锁失败,反复尝试重新上锁,终于上锁成功了,因为之前获取到全局锁的那个线程已经delete /waws_fs/lock/global全局锁了
PUT /waws_fs/lock/global/_create
{}
{
"_index": "waws_fs",
"_type": "lock",
"_id": "global",
"_version": 3,
"result": "created",
"_shards": {
"total": 2,
"successful": 1,
"failed": 0
},
"created": true
}
POST /waws_fs/file/1/_update
{
"doc": {
"name": "README.txt"
}
}
{
"_index": "waws_fs",
"_type": "file",
"_id": "1",
"_version": 3,
"result": "updated",
"_shards": {
"total": 2,
"successful": 1,
"failed": 0
}
}
DELETE /waws_fs/lock/global
全局锁的优点和缺点
- 优点:操作非常简单,非常容易使用,成本低
- 缺点:你直接就把整个index给上锁了,这个时候对index中所有的doc的操作,都会被block住,导致整个系统的并发能力很低
上锁解锁的操作不是频繁,然后每次上锁之后,执行的操作的耗时不会太长,用这种方式,方便
Elasticsearch高手进阶篇(65)
数据建模实战_基于document锁实现悲观锁并发控制(重要)
对document level锁,详细的讲解
-
全局锁
- 粗粒度的一个锁
- 一次性就锁整个index,对这个index的所有增删改操作都会被block住,如果上锁不频繁,还可以,比较简单
-
document锁
- 细粒度的一个锁
- 顾名思义,每次就锁你要操作的,你要执行增删改的那些doc,doc锁了,其他线程就不能对这些doc执行增删改操作了,但是你只是锁了部分doc,其他线程对其他的doc还是可以上锁和执行增删改操作的
document锁,是用脚本进行上锁
POST /waws_fs/lock/1/_update
{
"upsert": { "process_id": 123 },
"script": "if ( ctx._source.process_id != process_id ) { assert false }; ctx.op = 'noop';"
"params": {
"process_id": 123
}
}
-
/waws_fs/lock,是固定的,就是说waws_fs下的lock type,专门用于进行上锁 -
/waws_fs/lock/id,比如1,id其实就是你要上锁的那个doc的id,代表了某个doc数据对应的lock(也是一个doc) -
_update+ upsert:执行upsert操作 -
params,里面有个process_id,process_id,是你的要执行增删改操作的进程的唯一id,比如说可以在java系统,启动的时候,给你的每个线程都用UUID自动生成一个thread id,你的系统进程启动的时候给整个进程也分配一个UUID。process_id + thread_id就代表了某一个进程下的某个线程的唯一标识。可以自己用UUID生成一个唯一IDprocess_id很重要,会在lock中,设置对对应的doc加锁的进程的id,这样其他进程过来的时候,才知道,这条数据已经被别人给锁了
assert false,不是当前进程加锁的话,则抛出异常
ctx.op='noop',不做任何修改
-
如果该document之前没有被锁
- /waws_fs/lock/1之前不存在,也就是doc id=1没有被别人上过锁; upsert的语法,那么执行index操作,创建一个/waws_fs/lock/id这条数据,而且用params中的数据作为这个lock的数据。process_id被设置为123,script不执行。这个时候象征着process_id=123的进程已经锁了一个doc了。
-
如果document被锁了
- 就是说/waws_fs/lock/1已经存在了,代表doc id=1已经被某个进程给锁了。那么执行update操作,script,此时会比对process_id,如果相同,就是说,某个进程,之前锁了这个doc,然后这次又过来,就可以直接对这个doc执行操作,说明是该进程之前锁的doc,则不报错,不执行任何操作,返回success; 如果process_id比对不上,说明doc被其他doc给锁了,此时报错
/waws_fs/lock/1
{
"process_id": 123
}
POST /waws_fs/lock/1/_update
{
"upsert": { "process_id": 123 },
"script": "if ( ctx._source.process_id != process_id ) { assert false }; ctx.op = 'noop';"
"params": {
"process_id": 123
}
}
-
script:ctx._source.process_id,123
-
process_id:加锁的upsert请求中带过来额proess_id
- 如果两个process_id相同,说明是一个进程先加锁,然后又过来尝试加锁,可能是要执行另外一个操作,此时就不会block,对同一个process_id是不会block,ctx.op= 'noop' ,什么都不做,返回一个success
如果说已经有一个进程加了锁了
/waws_fs/lock/1
{
"process_id": 123
}
POST /fs/lock/1/_update
{
"upsert": { "process_id": 123 },
"script": "if ( ctx._source.process_id != process_id ) { assert false }; ctx.op = 'noop';"
"params": {
"process_id": 234
}
}
"script": "if ( ctx.source.process_id != process_id ) { assert false }; ctx.op = 'noop';"
ctx._source.process_id:123
process_id: 234
- process_id不相等,说明这个doc之前已经被别人上锁了,process_id=123上锁了; process_id=234过来再次尝试上锁,失败,assert false,就会报错
此时遇到报错的process,就应该尝试重新上锁,直到上锁成功
有报错的话,如果有些doc被锁了,那么需要重试
直到所有锁定都成功,执行自己的操作。。。
释放所有的锁
上document锁的完整实验过程
建立脚本文件scripts/judge-lock.groovy:
- if ( ctx._source.process_id != process_id ) { assert false }; ctx.op = 'noop';
POST /waws_fs/lock/1/_update
{
"upsert": { "process_id": 123 },
"script": {
"lang": "groovy",
"file": "judge-lock",
"params": {
"process_id": 123
}
}
}
{
"_index": "waws_fs",
"_type": "lock",
"_id": "1",
"_version": 1,
"result": "created",
"_shards": {
"total": 2,
"successful": 1,
"failed": 0
}
}
- 获取锁的信息
GET /waws_fs/lock/1
{
"_index": "waws_fs",
"_type": "lock",
"_id": "1",
"_version": 1,
"found": true,
"_source": {
"process_id": 123
}
}
- process_id:234去修改process_id:123更新
POST /waws_fs/lock/1/_update
{
"upsert": { "process_id": 234 },
"script": {
"lang": "groovy",
"file": "judge-lock",
"params": {
"process_id": 234
}
}
}
{
"error": {
"root_cause": [
{
"type": "remote_transport_exception",
"reason": "[w85Pu0f][127.0.0.1:9300][indices:data/write/update[s]]"
}
],
"type": "illegal_argument_exception",
"reason": "failed to execute script",
"caused_by": {
"type": "illegal_argument_exception",
"reason": "Unable to find on disk file script [judge-lock] using lang [groovy]"
}
},
"status": 400
}
- process_id:123去修改process_id:123更新
POST /waws_fs/lock/1/_update
{
"upsert": { "process_id": 123 },
"script": {
"lang": "groovy",
"file": "judge-lock",
"params": {
"process_id": 123
}
}
}
{
"_index": "waws_fs",
"_type": "lock",
"_id": "1",
"_version": 1,
"result": "noop",
"_shards": {
"total": 0,
"successful": 0,
"failed": 0
}
}
- 更新数据
POST /waws_fs/file/1/_update
{
"doc": {
"name": "README1.txt"
}
}
{
"_index": "waws_fs",
"_type": "file",
"_id": "1",
"_version": 4,
"result": "updated",
"_shards": {
"total": 2,
"successful": 1,
"failed": 0
}
}
- refresh
POST /waws_fs/_refresh
{
"_shards": {
"total": 10,
"successful": 5,
"failed": 0
}
}
- scroll的数据获取
GET /waws_fs/lock/_search?scroll=1m
{
"query": {
"term": {
"process_id": 123
}
}
}
{
"_scroll_id": "DnF1ZXJ5VGhlbkZldGNoBQAAAAAAAAkmFnc4NVB1MGZ0UzctdnJaT3dtZEFFOWcAAAAAAAAJJRZ3ODVQdTBmdFM3LXZyWk93bWRBRTlnAAAAAAAACScWdzg1UHUwZnRTNy12clpPd21kQUU5ZwAAAAAAAAkoFnc4NVB1MGZ0UzctdnJaT3dtZEFFOWcAAAAAAAAJKRZ3ODVQdTBmdFM3LXZyWk93bWRBRTln",
"took": 51,
"timed_out": false,
"_shards": {
"total": 5,
"successful": 5,
"failed": 0
},
"hits": {
"total": 1,
"max_score": 1,
"hits": [
{
"_index": "waws_fs",
"_type": "lock",
"_id": "1",
"_score": 1,
"_source": {
"process_id": 123
}
}
]
}
}
- bulk操作(将lock/1释放掉)
PUT /waws_fs/lock/_bulk
{ "delete": { "_id": 1}}
{
"took": 73,
"errors": false,
"items": [
{
"delete": {
"found": true,
"_index": "waws_fs",
"_type": "lock",
"_id": "1",
"_version": 2,
"result": "deleted",
"_shards": {
"total": 2,
"successful": 1,
"failed": 0
},
"status": 200
}
}
]
}
- 换process_id:234
POST /waws_fs/lock/1/_update
{
"upsert": { "process_id": 234 },
"script": {
"lang": "groovy",
"file": "judge-lock",
"params": {
"process_id": 234
}
}
}
{
"_index": "waws_fs",
"_type": "lock",
"_id": "1",
"_version": 3,
"result": "created",
"_shards": {
"total": 2,
"successful": 1,
"failed": 0
}
}
process_id=234上锁就成功了