常见3种分布式的实现比较
- 基于数据库实现分布式锁
- 基于缓存实现分布式锁(redis,mc)
- 基于Zookeeper实现分布式锁
- 基于redis
Redission
1. 简介
redission为redis官方推荐方式翻译,github地址。redisson-quick-start
2. 实现分析
2.1 解决的问题
安全和活跃性保证
- 安全
任何时候只有一个客户端可以获得锁
- 活跃属性
- 死锁自由(即使一个客户端已经拥用了已损坏或已被分割资源的锁,但它也有可能请求其他的锁)
举例:竞态条件
客户端A在主节点获得了一个锁。
主节点挂了,而到从节点的写同步还没完成。
从节点被提升为主节点。
客户端B获得和A相同的锁。注意,锁安全性被破坏了!
- 容错(只要大部分Redis节点可用, 客户端就可以获得和释放锁)
3. 实现思路
3.1 单例演变
SET resource_name my_random_value NX PX 30000
解释:
设置key的值,仅当其不存在时生效(NX选项), 且设置其生存期为30000毫秒(PX选项)。和key关联的value值是"my_random_value"。这个值在所有客户端和所有加锁请求中是必须是唯一的。
- 随机唯一
- 可以确保当前锁是该客户端,而防止其他客户端释放、删掉。
- 一般组合unix时间戳和客户端ID+随机数(/dev/urandom初始化RC4算法)
- 锁拥有时长
- 一种是指锁的自动释放时长
- 另一种是指在另一个客户端获取锁之前某个客户端占用这个锁的时长,这被限制在从锁获取后开始的一段时间窗口内。
- 简单实现
设置锁的超时时间
SET resource_name my_random_value NX PX 30000
获取锁 == myKey,释放
否则:return 0
if redis.call("get",KEYS[1]) == ARGV[1] then
return redis.call("del",KEYS[1])
else
return 0
end
实现
关键点
- 锁的时效设置。避免单点故障造成死锁,影响其他客户端获取锁。但是也要保证一旦一个客户端持锁,在客户端可用时不会被其他客户端解锁。(网上很多解决方案都是其他客户端等待队列长度判断是否强制解锁,但其实在偶发情况下就不能保证一致性,也就失去了分布式锁的意义)。
- 持锁期间的check,尽量在关键节点检查锁的状态,所以要设计成可重入锁,但在客户端使用时要做好吞吐量的权衡。
- 减少获取锁的操作,尽量减少redis压力。所以需要让客户端的申请锁有一个等待时间,而不是所有申请锁的请求要循环申请锁。
- 加锁的事务或者操作尽量粒度小,减少其他客户端申请锁的等待时间,提高处理效率和并发性。
- 持锁的客户端解锁后,要能通知到其他等待锁的节点,否则其他节点只能一直等待一个预计的时间再触发申请锁。类似线程的notifyAll,要能同步锁状态给其他客户端,并且是分布式消息。
- 考虑任何执行句柄中可能出现的异常,状态的正确流转和处理。比如,不能因为一个节点解锁失败,或者锁查询失败(redis 超时或者其他运行时异常),影响整个等待的任务队列,或者任务池。
锁设计
- 加锁
// 检查是否key已经被占用,如果没有则设置超时时间和唯一标识,初始化value=1
if (redis.call('exists', key) == 0)
then
redis.call('hset', key, key-uunid, 1); //hset key field value
redis.call('pexpire', key, timeout); //设置超时时间为毫秒
return null;
end;
// 如果锁重入,需要判断锁的key field 的情况
if (redis.call('hexists', key, key-unuid) == 1)
then
redis.call('hincrby', key, key-unuid, 1);//使字段值增加指定的整数
redis.call('pexpire', key, timeout);//锁重入重新设置超时时间
return null;
end;
// 返回剩余的过期时间
return redis.call('pttl', key);
- 解锁
// 如果key已经不存在,说明已经被解锁,直接发布(publihs)redis消息
if (redis.call('exists', key) == 0)
then
redis.call('publish', channelName, ARGV[1]);
return 1;
end;
// key和field不匹配,说明当前客户端线程没有持有锁,不能主动解锁。
if (redis.call('hexists', key, key-uuid) == 0)
then
return null;
end;
// 将value减1
local counter = redis.call('hincrby', key, key-uuid, -1);
// 如果counter>0说明锁在重入,不能删除key
if (counter > 0)
then
redis.call('pexpire', key, timeout);
return 0;
else
// 删除key并且publish 解锁消息
redis.call('del', key);
redis.call('publish', channelName, ARGV[1]);
return 1;
end;
return null;
Redisson源码解析
相关资料
分布式锁的几种实现方式
Redis实现分布式锁全局锁—Redis客户端Redisson中分布式锁RLock实现
基于Redis实现分布式锁,Redisson使用及源码分析







网友评论