服务器之家:专注于服务器技术及软件下载分享
分类导航

Mysql|Sql Server|Oracle|Redis|MongoDB|PostgreSQL|Sqlite|DB2|mariadb|Access|数据库技术|

香港云服务器
服务器之家 - 数据库 - Redis - Redis分布式非公平锁的使用

Redis分布式非公平锁的使用

2021-09-15 17:42木得未来 Redis

分布式锁很多人都能接触到,本文主要介绍了Redis分布式非公平锁,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

前言

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

分析:

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

加锁运行原理:

Redis分布式非公平锁的使用

解锁操作原理:

Redis分布式非公平锁的使用

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

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

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

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

原文链接:https://blog.csdn.net/qq_36622496/article/details/98739712

延伸 · 阅读

精彩推荐
  • Redisredis 交集、并集、差集的具体使用

    redis 交集、并集、差集的具体使用

    这篇文章主要介绍了redis 交集、并集、差集的具体使用,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友...

    xiaojin21cen10152021-07-27
  • Redis详解Redis复制原理

    详解Redis复制原理

    与大多数db一样,Redis也提供了复制机制,以满足故障恢复和负载均衡等需求。复制也是Redis高可用的基础,哨兵和集群都是建立在复制基础上实现高可用的...

    李留广10222021-08-09
  • RedisRedis全量复制与部分复制示例详解

    Redis全量复制与部分复制示例详解

    这篇文章主要给大家介绍了关于Redis全量复制与部分复制的相关资料,文中通过示例代码介绍的非常详细,对大家学习或者使用Redis爬虫具有一定的参考学习...

    豆子先生5052019-11-27
  • RedisRedis的配置、启动、操作和关闭方法

    Redis的配置、启动、操作和关闭方法

    今天小编就为大家分享一篇Redis的配置、启动、操作和关闭方法,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧 ...

    大道化简5312019-11-14
  • RedisRedis 事务知识点相关总结

    Redis 事务知识点相关总结

    这篇文章主要介绍了Redis 事务相关总结,帮助大家更好的理解和学习使用Redis,感兴趣的朋友可以了解下...

    AsiaYe8232021-07-28
  • RedisRedis如何实现数据库读写分离详解

    Redis如何实现数据库读写分离详解

    Redis的主从架构,能帮助我们实现读多,写少的情况,下面这篇文章主要给大家介绍了关于Redis如何实现数据库读写分离的相关资料,文中通过示例代码介绍...

    罗兵漂流记6092019-11-11
  • Redisredis实现排行榜功能

    redis实现排行榜功能

    排行榜在很多地方都能使用到,redis的zset可以很方便地用来实现排行榜功能,本文就来简单的介绍一下如何使用,具有一定的参考价值,感兴趣的小伙伴们...

    乘月归5022021-08-05
  • Redisredis中如何使用lua脚本让你的灵活性提高5个逼格详解

    redis中如何使用lua脚本让你的灵活性提高5个逼格详解

    这篇文章主要给大家介绍了关于redis中如何使用lua脚本让你的灵活性提高5个逼格的相关资料,文中通过示例代码介绍的非常详细,对大家的学习或者工作具...

    一线码农5812019-11-18
783