分布式锁的实现-Redisson

常见3种分布式的实现比较

  • 基于数据库实现分布式锁
  • 基于缓存实现分布式锁(redis,mc)
  • 基于Zookeeper实现分布式锁
  • 基于redis

Redission

1. 简介

redission为redis官方推荐方式翻译,github地址。redisson-quick-start

2. 实现分析

2.1 解决的问题

安全和活跃性保证

  • 安全

任何时候只有一个客户端可以获得锁

  • 活跃属性
  1. 死锁自由(即使一个客户端已经拥用了已损坏或已被分割资源的锁,但它也有可能请求其他的锁)

举例:竞态条件

客户端A在主节点获得了一个锁。
主节点挂了,而到从节点的写同步还没完成。
从节点被提升为主节点。
客户端B获得和A相同的锁。注意,锁安全性被破坏了!
  1. 容错(只要大部分Redis节点可用, 客户端就可以获得和释放锁)

3. 实现思路

3.1 单例演变

SET resource_name my_random_value NX PX 30000

解释:
设置key的值,仅当其不存在时生效(NX选项), 且设置其生存期为30000毫秒(PX选项)。和key关联的value值是"my_random_value"。这个值在所有客户端和所有加锁请求中是必须是唯一的。

  • 随机唯一
    1. 可以确保当前锁是该客户端,而防止其他客户端释放、删掉。
    2. 一般组合unix时间戳和客户端ID+随机数(/dev/urandom初始化RC4算法)
  • 锁拥有时长
    1. 一种是指锁的自动释放时长
    2. 另一种是指在另一个客户端获取锁之前某个客户端占用这个锁的时长,这被限制在从锁获取后开始的一段时间窗口内。
  • 简单实现
设置锁的超时时间
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使用及源码分析

最后编辑于 :
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

  • 译自Redis官方文档 在多线程共享临界资源的场景下,分布式锁是一种非常重要的组件。许多库使用不同的方式使用red...
    我叫刘大饼阅读 29,372评论 5赞 40
  • 锁 分布式锁 distributed locks 资源有限,争抢难免,最简单粗暴的办法是谁的拳头大谁就可以抢到最好...
    曲水流觞TechRill阅读 11,725评论 9赞 43
  • 分布式系统面临的第一个问题就是数据分布,即将数据均匀地分布到多个存储节点。另外,为了保证可靠性和可用性,需要将数据...
    olostin阅读 5,039评论 2赞 26
  • 好友就要结婚了,我翻看日历,发现那天是周末,而我的工作不允许我那天休息;怎么办?我准备提前去找她,把祝福送到; 就...
    独孤因果阅读 255评论 0赞 1
  • 听说生活就像被强奸 好想闭上眼睛享受 却总找不到快感 清冷的夜 提起忧伤的笔 写不出心碎…… 孤单的烟头忽明忽暗 ...
    帅也不思春阅读 262评论 9赞 7

友情链接更多精彩内容