Elasticsearch系列文章目录
- Elasticsearch学习笔记(一)Elasticsearch2.4.2安装
- Elasticsearch学习笔记(二)Elasticsearch入门
- Elasticsearch学习笔记(三)Elasticsearch5.1.2安装
- Elasticsearch学习笔记(四)版本控制[并发安全]
- Elasticsearch学习笔记(五)批量操作
- Elasticsearch学习笔记(六)Elasticsearch分布式集群值helloWord
- Elasticsearch学习笔记(七)Elasticsearch分布式集群工作原理简介
乐观锁
我们知道锁有悲观锁和乐观锁之分,数据库中的事务就是悲观锁,CAS就是属于乐观锁,关于CAS乐观锁的概念请参考本人另一篇文章: java高并发:CAS无锁原理及广泛应用
一个CAS方法包含三个参数CAS(V,E,N)
。V表示要更新的变量,E表示预期的值,N表示新值。只有当V的值等于E时,才会将V的值修改为N。如果V的值不等于E,说明已经被其他线程修改了,当前线程可以放弃此操作,也可以再次尝试次操作直至修改成功。基于这样的算法,CAS操作即使没有锁,也可以发现其他线程对当前线程的干扰(临界区值的修改),并进行恰当的处理。
ES中的乐观锁
使用ES自带的版本控制
我们get请求id=2的员工信息megacorp/employee/2
:
{
"_index": "megacorp",
"_type": "employee",
"_id": "2",
"_version": 2,
"found": true,
"_source": {
"first_name": "Jane",
"last_name": "Smith",
"age": 33,
"about": "I like to collect rock albums",
"interests": ["music"]
}
}
注意,这里_version
是2,我们从ES中查询到一条记录,修改完成以后重新PUT建立索引,这时候我们要把_version
作为一个参数传递过去,ES会用该参数和实际的数据进行比对,如果相同则进行重建索引,如果不同则返回错误信息,具体如何处理这个错误就看业务需要了,比如你可以借鉴atomicInteger
循环重试等等。
我们将年龄改为80:
请求URL:megacorp/employee/2?version=2
请求参数:
{
"first_name": "Jane",
"last_name": "Smith",
"age": 80,
"about": "I like to collect rock albums",
"interests": [
"music"
]
}
结果:
{
"_index": "megacorp",
"_type": "employee",
"_id": "2",
"_version": 3,
"_shards": {
"total": 2,
"successful": 2,
"failed": 0
},
"created": false
}
修改成功,此时_version
变为了3。
我们在重新执行一遍上面的程序,_version
依旧传2,按照CAS理论应该修改不成功。实验结果:
{
"error": {
"root_cause": [{
"type": "version_conflict_engine_exception",
"reason": "[employee][2]: version conflict, current [3], provided [2]",
"index": "megacorp",
"shard": "2"
}],
"type": "version_conflict_engine_exception",
"reason": "[employee][2]: version conflict, current [3], provided [2]",
"index": "megacorp",
"shard": "2"
},
"status": 409
}
结果和我们预想的一样,错误原因写的很明确了,当前版本是3,但是传递过来的版本是2,与current不一致。
使用外部版本控制系统
一种常见的结构是使用一些其他的数据库做为主数据库,然后使用Elasticsearch搜索数据,这意味着所有主数据库发生变化,就要将其拷贝到Elasticsearch中。如果有多个进程负责这些数据的同步,就会遇到上面提到的并发问题。
如果主数据库有版本字段——或一些类似于timestamp
等可以用于版本控制的字段——是你就可以在Elasticsearch的查询字符串后面添加version_type=external
来使用这些版本号。版本号必须是整数,大于零小于9.2e+18
——Java中的正的long
。
外部版本号与之前说的内部版本号在处理的时候有些不同。它不再检查_version
是否与请求中指定的一致,而是检查是否小于指定的版本。如果请求成功,外部版本号就会被存储到_version
中。
外部版本号不仅在索引和删除请求中指定,也可以在**创建(create)**新文档中指定。
例如,创建一个包含外部版本号5
的新博客,我们可以这样做:
PUT /website/blog/2?version=5&version_type=external
{
"title": "My first external blog entry",
"text": "Starting to get the hang of this..."
}
在响应中,我们能看到当前的_version
号码是5
:
{
"_index": "website",
"_type": "blog",
"_id": "2",
"_version": 5,
"created": true
}
现在我们更新这个文档,指定一个新version
号码为10
:
PUT /website/blog/2?version=10&version_type=external
{
"title": "My first external blog entry",
"text": "This is a piece of cake..."
}
请求成功的设置了当前_version
为10
:
{
"_index": "website",
"_type": "blog",
"_id": "2",
"_version": 10,
"created": false
}
如果你重新运行这个请求,就会返回一个像之前一样的冲突错误,因为指定的外部版本号不大于当前在Elasticsearch中的版本。