基于redis的分布式锁详解

程序员安安 2021-01-21 10:28:04
面试 php Swoft nginx redis


在使用分布式锁进行互斥资源访问时候,我们很多方案是采用redis的实现。
固然,redis的单节点锁在极端情况也是有问题的,假设你的业务允许偶尔的失效,使用单节点的redis锁方案就足够了,简单而且效率高。
redis锁失效的情况:

  1. 客户端1从master节点获取了锁
  2. master宕机了,存储锁的key还没来得及同步到slave节点上
  3. slave升级为master
  4. 客户端2从新的master上获取到同一个资源的锁

于是,客户端1和客户端2同事持有了同一个资源的锁,锁的安全性被打破。
如果我们不考虑这种极端情况,需要实现一个基于单节点redis锁的大致流程:

set cache_key random_seed NX PX 30000

上面这个set命令拆解开就是:

setnx cache_key random_seed 
expire cache_key 30

虽然这两组命令执行的效果一样,但是第二个是非原子性操作,如果执行了setnx成功,但是expire失败的话,就会造成这个key一直存在了,无法释放的情况。


redis的作者也指出,在使用单节点redis锁的时候,设置一个随机种子作为key的值是很有必要的,保证了一个客户端释放的锁必须是自己所持有的那个锁。假设获取锁时set的不是一个随机数,而是一个固定值,那么可能会出现下面的情况:

  1. 客户端1获取锁成功
  2. 客户端1在某个操作上阻塞了很长时间
  3. 过期时间到了,锁自动释放(但是在客户端1看来自己还是持有锁中)
  4. 客户端2获取到了对应同一个资源的锁
  5. 客户端1从阻塞中恢复了,释放掉自己持有的锁,也就是释放掉了客户端2持有的锁

客户端2的锁被客户端1是否,失去安全性。
释放锁的操作,很多人直接用del命令,这会有很大的问题,保证不了这个key是被加锁人锁删。这时候需要用到随机数了。释放锁的操作有三步:

  1. get 所持有锁
  2. 判断这个锁是否自己所持有
  3. 删除持有锁

所以,这三步要保证原子性。用lua脚本来执行,redis官方已经提供脚本文件。

if redis.call("get",KEYS[1]) == ARGV[1] then
return redis.call("del",KEYS[1])
else
return 0
end

这段脚本在执行的时候,需要把前面的随机数作为argv[1] 的值传进去,把cache_key作为keys[1]的值传进去。

public class RedisLockHelper {
@Resource
private R2mClusterClient r2mClusterClient;
/**
* 类似于setNx的功能,同时设置过期时间为expire毫秒
*
* @param key 加锁key
* @param value 确保在加锁时间内的唯一因子
* @param expire 过期时间的毫秒数
* @return
*/
private String setLock(String key, String value, long expire) {
return this.set(key, value, "NX", "PX", expire);
}
/**
* 删除指定key value
* 如果 r2m中 key 对应的value==value 返回 1
* 如果 r2m中 key 对应的value!=value 返回 0
*
* @param key
* @return
*/
private boolean atomDelete(String key, String value) {
List<String> values = new ArrayList<>();
values.add(value);
String sb = "if redis.call('get',KEYS[1])==ARGV[1] then " +
" return redis.call('del',KEYS[1]) " +
" else " +
" return 0" +
" end";
if (this.eval(sb, key, values) == 1) {
return true;
}
return false;
}
private Long eval(String mobel, String key, List<String> value) {
return (Long) this.r2mClusterClient.eval(mobel, key, value);
}
private String set(String key, String value, String nxxx, String expx, long time) {
return this.r2mClusterClient.set(key, value, nxxx, expx, time);
}
}

r2mClusterClient 就是jedis客户端的封装。

以上内容希望帮助到大家, 很多PHPer在进阶的时候总会遇到一些问题和瓶颈,业务代码写多了没有方向感,不知道该从那里入手去提升,对此我整理了一些资料,包括但不限于:分布式架构、高可扩展、高性能、高并发、服务器性能调优、TP6,laravel,Redis,Swoole、Swoft、Kafka、Mysql优化、shell脚本、Docker、微服务、Nginx等多个知识点高级进阶干货需要的可以免费分享给大家 ,需要戳这里     PHP进阶架构师>>>实战视频、大厂面试文档免费获取    

 

版权声明
本文为[程序员安安]所创,转载请带上原文链接,感谢
https://my.oschina.net/u/4635223/blog/4917300

  1. 【计算机网络 12(1),尚学堂马士兵Java视频教程
  2. 【程序猿历程,史上最全的Java面试题集锦在这里
  3. 【程序猿历程(1),Javaweb视频教程百度云
  4. Notes on MySQL 45 lectures (1-7)
  5. [computer network 12 (1), Shang Xuetang Ma soldier java video tutorial
  6. The most complete collection of Java interview questions in history is here
  7. [process of program ape (1), JavaWeb video tutorial, baidu cloud
  8. Notes on MySQL 45 lectures (1-7)
  9. 精进 Spring Boot 03:Spring Boot 的配置文件和配置管理,以及用三种方式读取配置文件
  10. Refined spring boot 03: spring boot configuration files and configuration management, and reading configuration files in three ways
  11. 精进 Spring Boot 03:Spring Boot 的配置文件和配置管理,以及用三种方式读取配置文件
  12. Refined spring boot 03: spring boot configuration files and configuration management, and reading configuration files in three ways
  13. 【递归,Java传智播客笔记
  14. [recursion, Java intelligence podcast notes
  15. [adhere to painting for 386 days] the beginning of spring of 24 solar terms
  16. K8S系列第八篇(Service、EndPoints以及高可用kubeadm部署)
  17. K8s Series Part 8 (service, endpoints and high availability kubeadm deployment)
  18. 【重识 HTML (3),350道Java面试真题分享
  19. 【重识 HTML (2),Java并发编程必会的多线程你竟然还不会
  20. 【重识 HTML (1),二本Java小菜鸟4面字节跳动被秒成渣渣
  21. [re recognize HTML (3) and share 350 real Java interview questions
  22. [re recognize HTML (2). Multithreading is a must for Java Concurrent Programming. How dare you not
  23. [re recognize HTML (1), two Java rookies' 4-sided bytes beat and become slag in seconds
  24. 造轮子系列之RPC 1:如何从零开始开发RPC框架
  25. RPC 1: how to develop RPC framework from scratch
  26. 造轮子系列之RPC 1:如何从零开始开发RPC框架
  27. RPC 1: how to develop RPC framework from scratch
  28. 一次性捋清楚吧,对乱糟糟的,Spring事务扩展机制
  29. 一文彻底弄懂如何选择抽象类还是接口,连续四年百度Java岗必问面试题
  30. Redis常用命令
  31. 一双拖鞋引发的血案,狂神说Java系列笔记
  32. 一、mysql基础安装
  33. 一位程序员的独白:尽管我一生坎坷,Java框架面试基础
  34. Clear it all at once. For the messy, spring transaction extension mechanism
  35. A thorough understanding of how to choose abstract classes or interfaces, baidu Java post must ask interview questions for four consecutive years
  36. Redis common commands
  37. A pair of slippers triggered the murder, crazy God said java series notes
  38. 1、 MySQL basic installation
  39. Monologue of a programmer: despite my ups and downs in my life, Java framework is the foundation of interview
  40. 【大厂面试】三面三问Spring循环依赖,请一定要把这篇看完(建议收藏)
  41. 一线互联网企业中,springboot入门项目
  42. 一篇文带你入门SSM框架Spring开发,帮你快速拿Offer
  43. 【面试资料】Java全集、微服务、大数据、数据结构与算法、机器学习知识最全总结,283页pdf
  44. 【leetcode刷题】24.数组中重复的数字——Java版
  45. 【leetcode刷题】23.对称二叉树——Java版
  46. 【leetcode刷题】22.二叉树的中序遍历——Java版
  47. 【leetcode刷题】21.三数之和——Java版
  48. 【leetcode刷题】20.最长回文子串——Java版
  49. 【leetcode刷题】19.回文链表——Java版
  50. 【leetcode刷题】18.反转链表——Java版
  51. 【leetcode刷题】17.相交链表——Java&python版
  52. 【leetcode刷题】16.环形链表——Java版
  53. 【leetcode刷题】15.汉明距离——Java版
  54. 【leetcode刷题】14.找到所有数组中消失的数字——Java版
  55. 【leetcode刷题】13.比特位计数——Java版
  56. oracle控制用户权限命令
  57. 三年Java开发,继阿里,鲁班二期Java架构师
  58. Oracle必须要启动的服务
  59. 万字长文!深入剖析HashMap,Java基础笔试题大全带答案
  60. 一问Kafka就心慌?我却凭着这份,图灵学院vip课程百度云