背景
工作中遇到这么一个场景:
在并发场景下,分布式下多个线程都想对数据库(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();
}












网友评论