Elasticsearch并发问题
最近在项目中,遇到个要统计文章的访问量的问题。刚好文章的访问量放在ElasticSearch中,然后就碰到了并发冲突。当文章访问量为100时,当我们同时点击同一篇文章时,该篇文章的访问量按理说要+2,但是,现实并没有+2,出现并发冲突问题。es中没有像mysql中有行锁等控制并发问题。
文章访问量图解:
2.es内部如何解决并发冲突问题
先了解下两个概念:
悲观锁:当用户A和用户B同时去访问同一篇文章时,用户A先读取初始值100后,在这过程中直接加锁,然后去更新访问量,并写入。在这过程中,用户B也会去访问该篇文章的访问量,但是这时会被锁住。当等用户A更新完成后,释放锁后,B才后读取被A更新的值101,在这基础上+1.这种方法跟java中在方法上加上同步synchronized关键字,这种方法比较容易理解和控制,但是并发能力很低。
乐观锁:es中采用就是乐观锁的机制。乐观锁不加锁。假如用户A和用户B同时去点击同一篇文章,该篇文章的访问量要+2。es中引入一个版本号的概念。用户A会先去获取es中的访问量值,拿到版本号,判断当前版本号是否一致,如果版本号一致,更新,并且版本号+1,es的访问量变为101,version=2,用户B一开始也是version=1,当A更新后version=2,与判断和自己的版本号不一致,这时es不会去重新更新es的值,而是重新获取(GET)当前document的version,然后在此版本号上再去重新更新数据。
_version的版本控制流程
不管更新,创建,删除文档,都只是在此版本号上加 1,删除时也只是逻辑删除,version+1,不是物理删除
3.1 先用GET 操作去获取document的版本号 GET /cms/article/2
3.2 PUT更新文章带上version PUT /cms/article/2?version=1
3.3 判断当前版本号和在重新获取的版本号(有可能别人已经更新版本号已经更改)是否一致
3.4 一致更新,不一致,说明当前数据已经被更新,重新获取文章数据,在带上版本号更新,在判断,直到版本号一致才更新数据
这样处理并发量比较高,但是内部要不断去判断获取版本号,去判断是否一致。
external version 乐观锁的并发控制
PUT /cms/article/1?version=1
{
"id":1,
"title":"啥是佩奇",
"date":"2019-01-21"
}
GET /cms/article/1
{
"_index": "cms",
"_type": "article",
"_id": "1",
"_version": 2,
"found": true,
"_source": {
"id": 1,
"title": "啥是佩奇",
"date": "2019-01-21"
}
}
?version=2 =>提供version和es的version一样才能更新
?version=2&version_type=external 当你提供的version比es中version大的时候,才嫩修改
PUT /cms/article/1?version=2&version_type=external
{
"title":"external文章更改",
"id": 1,
"date": "2019-01-22"
}
----------报错。。。。
{
"error": {
"root_cause": [
{
"type": "version_conflict_engine_exception",
"reason": "[article][1]: version conflict, current version [2] is higher or equal to the one provided [2]",
"index_uuid": "0JfXdrUNSqemoZANcAFkGg",
"shard": "3",
"index": "cms"
}
],
"type": "version_conflict_engine_exception",
"reason": "[article][1]: version conflict, current version [2] is higher or equal to the one provided [2]",
"index_uuid": "0JfXdrUNSqemoZANcAFkGg",
"shard": "3",
"index": "cms"
},
"status": 409
}
-------------------
PUT /cms/article/1?version=3&version_type=external
{
"title":"external文章更改",
"id": 1,
"date": "2019-01-22"
}
----
{
"_index": "cms",
"_type": "article",
"_id": "1",
"_version": 3,
"result": "updated",
"_shards": {
"total": 2,
"successful": 1,
"failed": 0
},
"_seq_no": 2,
"_primary_term": 5
}
部分更新 partial update
PUT /cms/article/1
{
"doc":{
"title": "文章更新11"
}
}
es中数据的更新方式为:把旧数据设置标志位deleted,重新创建新的document,进行全量替换,当deleted太多时,会进行清除。
partial update:获取document进行更新,在用新的document替换
partial update 乐观锁
POST /cms/article/1/_update?retry_on_conflict=5
{
"doc": {
"title":"+++666"
}
}
不能加version
7. script脚本更新
POST /cms/article/2/_update?retry_on_conflict=5
{
"script": "ctx._source.num +=1"
}
POST /cms/article/11/_update?retry_on_conflict=5
{
"script": "ctx._source.num +=1",
"upsert": {
"num": 0,
"tags": "new—tag"
}
}
---
{
"_index": "cms",
"_type": "article",
"_id": "11",
"_version": 1,
"found": true,
"_source": {
"num": 0,
"tags": "new—tag"
}
}
---
POST /cms/article/2/_update?retry_on_conflict=5
{
"script": "ctx._source.num +=1",
"upsert": {
"num": 0,
"tags": "new—tag"
}
}
----num+1 了
{
"_index": "cms",
"_type": "article",
"_id": "2",
"_version": 3,
"found": true,
"_source": {
"id": 2,
"title": "啥是佩奇",
"date": "2019-01-21",
"num": 2
}
}
"upsert":如果指定的document不存在,执行upsert里面的初始化语句,存在执行doc或者script语句.
---学习至中华石杉