为什么使用分布式锁:
常用的分布式锁实现方案:
mysql
Redis
zookeeper
redis分布式锁的要求
Redis分布式锁的实现
锁的重入性
看门狗
如果redis客户端加锁请求失败:
轻量级的脚本语言,C语言编写,嵌入应用程序中,为程序提供灵活的扩展和定制功能
redis调用lua脚本,通过eval命令保证redis命令的原子性,用return返回脚本执行后的结果,例如:
-- eval后面跟的是脚本, 一个redis.call()代表一个redis命令
eval " redis.call('set','test','1234') redis.call('expire','test','60') return redis.call('get','test') " 0
-- 上面的脚本是写死的,用动态参数代替后为:
eval " redis.call('set',KEYS[1],ARGV[1]) redis.call('expire',KEYS[1],'60') return redis.call('get',KEYS[1]) " 1 test 1234
lua脚本参数说明
eval "return redis.call('mset','k1','v1','k2','v2') " 0
-- 上面的脚本是写死的,用动态参数代替后为:
-- KEYS代表key, ARGV代表value,,下标都是从1开始
-- 2代表参数的个数,后面的key和value要一一对应
eval "return redis.call('mset',KEYS[1],ARGV[1],KEYS[2],ARGV[2]) " 2 k1 k2 v1 v2
-- if语法说明 ,有if一定有end,除了最后一个else都有then
if (布尔条件) then
业务代码
elseif (布尔条件) then
业务代码
else
业务代码
end
redis官网的lua脚本如下:
-- 获取key的value值,和输入的value进行比较,相同就删除key并返回删除结果,否则返回0
if redis.call("get",KEYS[1]) == ARGV[1] then
return redis.call("del",KEYS[1])
else
return 0
end
改写后为:注意单双引号
eval " if redis.call('get',KEYS[1]) == ARGV[1] then return redis.call('del',KEYS[1]) else return 0 end" 1 testLock 1234
hash类型的分布式锁 lua脚本
加锁
-- 第一个条件是锁存在,第二条件是锁的value存在
if redis.call('exists',KEYS[1])==0 or redis.call('hexists',KEYS[1],ARGV[1])==1 then
-- hincrby包含了hset, key2是uuid ,1是增长的步长值
redis.call('hincrby',KEYS[1],ARGV[1],1)
--设置过期时间
redis.call('expire',KEYS[1],ARGV[2])
return 1
else
return 0
end
-- 缩进后:
eval "if redis.call('exists',KEYS[1])==0 or redis.call('hexists',KEYS[1],ARGV[1])==1 then redis.call('hincrby',KEYS[1],ARGV[1],1) redis.call('expire',KEYS[1],ARGV[2]) return 1 else return 0 end" 1 testhashLock 1111 60
-- 锁不存在
if redis.call('hexists',KEYS[1],ARGV[1])==0 then
return nil
--减少锁的加锁次数,如果加锁的次数为0,就删除锁
elseif redis.call('hincrby',KEYS[1],ARGV[1],-1)==0 then
return redis.call('del',KEYS[1])
else
return 0
end
eval "if redis.call('hexists',KEYS[1],ARGV[1])==0 then return nil elseif redis.call('hincrby',KEYS[1],ARGV[1],-1)==0 then return redis.call('del',KEYS[1]) else return 0 end" 1 testhashLock 1111
自动续期 lua 脚本
-- 过了一段时间后,发现锁还在,就要续期
if redis.call('hexists',KEYS[1],ARGV[1])==1 then
return redis.call('expire',KEYS[1],ARGV[2])
else
return 0
end
--测试,先加一个key,设置过期时间再续期
hset testhashLock 1111 1
expire testhashLock 60
ttl testhashLock
eval "if redis.call('hexists',KEYS[1],ARGV[1])==1 then return redis.call('expire',KEYS[1],ARGV[2]) else return 0 end" 1 testhashLock 1111 60
redis的红锁方案(Redlock):
redis分布式锁,在主从情况下是不可靠的,因为redis的复制是异步的,所以即使是在集群模式下运行,如果在加锁时 redis 主节点故障,从节点还未来得及同步锁就升级为主节点,此时其他线程就可以再次加锁,这样就会导致多个线程都持有锁,可以通过红锁解决
红锁本质上就是使用多个Redis做锁,官网的示例采用的是5个节点,这些节点完全互相独立,没有主从关系,和很多分布式算法一样,红锁也采用大多数机制
在奇数台redis上加锁,一次锁的获取,会对每个请求都获取一遍,如果获取锁成功的数量超过一半,则获取锁成功,反之失败;
红锁的问题:
redisson的git地址:
https://github.com/redisson/redisson
红锁的设计理念
红锁节点个数:N = 2 X +1
redisson
示例:
@Resource
private Redisson redisson;
public BaseResultModel reduce() {
RLock lock = redisson.getLock(COMMODITY_KEY_LOCK_REDISSON);
//加redis分布式锁
lock.lock();
try {
} catch (Exception e) {
e.printStackTrace();
} finally {
//判断锁是否是当前线程的
if (lock.isLocked()&&lock.isHeldByCurrentThread()){
//释放redis分布式锁
lock.unlock();
}
}
return BaseResultModel.success("执行成功“);
}