背景
工作中遇到这么一个场景:
在并发
场景下,分布式下多个线程都想对数据库(MongoDB
)里的一个对象 进行操作
分支A
:如果这个对象存在,就进行字段更新
分支B
:如果这个对象不存在,创建之后,再用_id进行字段更新(多个线程都想拿到数据库里的一个不存在
对象的_id
, 进行操作)
针对A
分支,没什么说的,无脑操作就行
针对B
分支,大家实现起来就各显神通了
锁加自旋
我看到过一种实现是这样的
if(exist) {
doSomthing();
} else {
if(redis.lock()) {
// 拿到锁
create();
} else {
while(true) {
sleep(x);
if(exist) {
break;
}
}
}
doSomthing();
}
getOrSet
因为MongoDB
的document
可以自行设置 _id
, 再加上 upsert
并发也不丢字段(必须是幂等操作
), 所以我觉得可以优化掉 sleep
第一个问题就是:分布式下多个线程都想拿到数据库里的一个不存在
对象的_id
, 进行操作,这个 _id 怎么互相感知
想法思路是: 大家到同一个地方去get
某个东西,有的话就取出来,当成_id
使用;没有的话,就生成一个设置进去
日常工作中,有分布式能力且唾手可得的中间件就是 redis
了
那么redis
原生有没有这个实现呢?
首先想到了 getset
但是仔细一看文档,作用原来是这样的Getset 命令用于设置指定 key 的值,并返回 key 的旧值
值每次都没修改了,无法达到分布式统一的目的
setnx
有这个效果,但是返回的是数字,想要获取,还需要再get
一下,有点繁琐
那么能不能把setnx
和get
合并起来写成lua
脚本呢?
可以的,经过测试,以下lua
脚本可行
local key_name = KEYS[1]
local value = ARGV[1]
local exp_second = ARGV[2]
local redis_value = redis.call('get', key_name)
if redis_value then
return redis_value
end
redis.call('set', key_name, value, 'EX', exp_second)
return value
用的时候就这样用
eval script 1 key value second
java伪代码优化为如下
if(exist) {
doSomthing();
} else {
_id = new ObjectId();
redisId = redis.getOrSet(_id)
if(redisId == _id)) {
// 说明是当前线程设置进去的,负责创建
create();
} else {
upsert(redisId)
}
doSomthing();
}
网友评论