经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » 数据库/运维 » Redis » 查看文章
Redis分布式非公平锁的使用
来源:jb51  时间:2021/8/4 11:26:49  对本文有异议

前言

看了很多博客,和资料,这里只针对redis做分布式锁做一下深入探讨,希望对你们有帮助。网上提供了很多分布式锁的操作,这里逐一举例然后评论优缺点及改进方案,希望这样子能让当家更好的理解redis分布式锁。

redis分布式锁第一版

大家应该都知道Redis做分布式锁无非就是INCR命令或者是SetNx命令,这里我们采用setnx命令。
操作:setnx key 如果操作成功则代表拿到锁,如果没有操作成功则代表没有拿到锁。

缺点:如果这个人拿到锁后宕机了怎么办,那么这个锁就再也不能释放了。

改进:给这个锁增加一个过期时间,这样如果有效期过了,那么这个锁就会自动释放了。

redis分布式锁第二版

通过上面所说我们应该对redis分布式进行改进。
操作: 使用setnx 命令,之后,在EXPIREAT key 30000 这条命令设置key的有效期为30秒。
这里我们可能会发现,如果要是刚setnx结束之后,要是宕机了。怎么办?那么我们为了保证原子性,所以jedis提供了一个原子操作,set(key,value,nx,30,时间单位)这样便解决了。
缺点:如果这个锁的时间不够用怎么办,那么就会导致这个功能锁不住。假设:A拿到锁了,但是A还没有执行结束,B又拿到锁了,那么A执行结束的时候是不是会把B的这个锁给删除掉。这样就导致了锁不住的效果。
改进:我们可以学习乐观所,给锁的value值是一个唯一的编号,或者版本号,我们每次对锁进行操作的时候,就会去验证这个版本号,还是不是自己的版本号。如果不是了就不允许操作了。

redis分布式锁第三版

通过上面的总结这第三版想必也很简单了。知识多了一个唯一值而已。但是加了唯一值还是改变不了锁不住的结果,只是解决了帮其他的线程解锁的问题,那么要怎么样才能锁得住呢?当时我想到的是给他 时间久一点,后来发现其实再久,也一样会出现锁不住的时候,而且太久了如果宕机了,就会有很长时间机器无法工作,很容易造成线程堆积。

redis分布式锁最终版

由上面我们发现一般简单实用redis做锁其实是有很多漏洞和bug的,但是有没有能够解决这些的呢?当然是有的。
模仿AQS锁, lock方法执行完之后,执行下面代码是被锁的,unlock执行完,释放锁。其他线程等待,而不是直接返回错误结果。

最终版还是打算先上代码再说,为了方便我把所有的实现都写在了一个类里面。

  1. @Autowired
  2. private RedisTemplate redisTemplate;
  3.  
  4. @Autowired
  5. private RedisUtils redisUtils;
  6.  
  7. @Autowired(required = false)
  8. private ThreadPoolTaskScheduler threadPoolTaskScheduler;
  9.  
  10. public final String LOCK_PREFIX = "REDIS_LOCK";
  11.  
  12. private final Long LOCK_EXPIRE = 30 * 1000L;
  13.  
  14. private final Long OVER_TIME = 10L;
  15.  
  16. private Map<String,ScheduledFuture<?> > futureMap = new ConcurrentHashMap<>();
  17.  
  18. private Jedis jedis;
  19.  
  20. public Lock() {
  21. }
  22.  
  23. private ReentrantLock reentrantLock;
  24.  
  25. /**
  26. * 给线程枷锁
  27. *
  28. * @param key
  29. */
  30. public void lock(String key) {
  31. //自旋获取锁
  32. while (true) {
  33. if (setLock(key)) {//拿锁成功
  34. //获取锁后开启任务
  35. threadPoolTaskScheduler.schedule(()->{
  36. Set<String> keys = scan(LOCK_PREFIX);
  37. Iterator<String> iterator = keys.iterator();
  38. //遍历所有的key 延长key的时间
  39. while (iterator.hasNext()) {
  40. log.info("执行动态定时任务: " + LocalDateTime.now().toLocalTime());
  41. redisUtils.expire(key, Long.valueOf(OVER_TIME), TimeUnit.SECONDS);//延长时间(秒)
  42. }
  43. },new Trigger(){
  44. @Override
  45. public Date nextExecutionTime(TriggerContext triggerContext){
  46. return new CronTrigger("0/10 * * * * ?").nextExecutionTime(triggerContext);
  47. }
  48. });
  49. return;
  50. }
  51. }
  52. }
  53.  
  54. /**
  55. * setnx
  56. *
  57. * @param key
  58. * @return
  59. */
  60. public boolean setLock(String key) {
  61. String lock = LOCK_PREFIX + key;
  62. return (Boolean) redisTemplate.execute(new RedisCallback<Object>() {
  63. @Override
  64. public Object doInRedis(RedisConnection redisConnection) throws DataAccessException {
  65. long expireAt = System.currentTimeMillis() + LOCK_EXPIRE + 1;
  66. Boolean acquire = redisConnection.setNX(lock.getBytes(), String.valueOf(expireAt).getBytes());
  67. if (acquire) {
  68. return true;
  69. } else {
  70. byte[] value = redisConnection.get(lock.getBytes());
  71. if (Objects.nonNull(value) && value.length > 0) {
  72. long expireTime = Long.parseLong(new String(value));
  73. if (expireTime < System.currentTimeMillis()) {
  74. // 如果锁已经过期
  75. byte[] oldValue = redisConnection.getSet(lock.getBytes(), String.valueOf(System.currentTimeMillis() + LOCK_EXPIRE + 1).getBytes());
  76. // 防止死锁
  77. return Long.parseLong(new String(oldValue)) < System.currentTimeMillis();
  78. }
  79. }
  80. }
  81. return false;
  82. }
  83. });
  84. }
  85.  
  86. /**
  87. * 删除锁
  88. *
  89. * @param key
  90. */
  91. public void unlock(String key) {
  92. String lock = LOCK_PREFIX + key;
  93. synchronized (this) {
  94. futureMap.get(lock).cancel(true);//停止任务
  95. redisTemplate.delete(lock);
  96. }
  97. }
  98.  
  99. /**
  100. * 判断key是否存在
  101. *
  102. * @param key 键
  103. * @return true 存在 false不存在
  104. */
  105. public boolean hasKey(String key) {
  106. try {
  107. return redisTemplate.hasKey(key);
  108. } catch (Exception e) {
  109. e.printStackTrace();
  110. return false;
  111. }
  112. }
  113.  
  114. public Set<String> scan(String key) {
  115. return (Set<String>) redisTemplate.execute((RedisCallback<Set<String>>) connection -> {
  116. Set<String> keys = Sets.newHashSet();
  117.  
  118. JedisCommands commands = (JedisCommands) connection.getNativeConnection();
  119. MultiKeyCommands multiKeyCommands = (MultiKeyCommands) commands;
  120.  
  121. ScanParams scanParams = new ScanParams();
  122. scanParams.match("*" + key + "*");
  123. scanParams.count(1000);
  124. ScanResult<String> scan = multiKeyCommands.scan("0", scanParams);
  125. while (null != scan.getStringCursor()) {
  126. keys.addAll(scan.getResult());
  127. if (!StringUtils.equals("0", scan.getStringCursor())) {
  128. scan = multiKeyCommands.scan(scan.getStringCursor(), scanParams);
  129. continue;
  130. } else {
  131. break;
  132. }
  133. }
  134.  
  135. return keys;
  136. });
  137. }
  138.  

分析:

  • 判断是否获取到锁,获取到锁,继续执行,没有获取到锁,自旋继续获取。
  • 获取到锁后调度一个任务。每10秒执行一次,并且如果发现所没有释放延长10秒。
  • 释放锁,删除掉redis中的key,并结束掉对应的锁的任务。

加锁运行原理:

在这里插入图片描述

解锁操作原理:

在这里插入图片描述

解锁操作就比较简单了。但是得为了不出必要的麻烦,最好是给停止锁延时任务,和删除所 这两部添加进程锁,可以使用synchronized,也可以使用AQS lock锁。

这里Redis非公平锁详解算是结束了,后期可能会更新使用Redis,实现公平锁,谢谢大家的支持,如果有需要的小伙伴可以直接拿走,希望能给大家带来帮助。

在这里我希望看过文章的小伙伴能够根绝实现原理自己去实现,这样可以帮助小伙伴理解非公平锁机制,和Redis实现非公平,如果不喜欢自己去实现的话,这里我给大家推荐一个Redission 这个插件,这个插件是一个Redis锁的很好的一个实现,大家可以直接用这个。具体怎么用就不讲解了,操作非常简单。

到此这篇关于Redis分布式非公平锁的使用的文章就介绍到这了,更多相关Redis分布式非公平锁内容请搜索w3xue以前的文章或继续浏览下面的相关文章希望大家以后多多支持w3xue!

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号