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

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

延伸 · 阅读

精彩推荐
  • Redis关于Redis数据库入门详细介绍

    关于Redis数据库入门详细介绍

    大家好,本篇文章主要讲的是关于Redis数据库入门详细介绍,感兴趣的同学赶快来看一看吧,对你有帮助的话记得收藏一下,方便下次浏览...

    沃尔码6982022-01-24
  • Redis详解三分钟快速搭建分布式高可用的Redis集群

    详解三分钟快速搭建分布式高可用的Redis集群

    这篇文章主要介绍了详解三分钟快速搭建分布式高可用的Redis集群,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,...

    万猫学社4502021-07-25
  • RedisRedis 6.X Cluster 集群搭建

    Redis 6.X Cluster 集群搭建

    码哥带大家完成在 CentOS 7 中安装 Redis 6.x 教程。在学习 Redis Cluster 集群之前,我们需要先搭建一套集群环境。机器有限,实现目标是一台机器上搭建 6 个节...

    码哥字节15752021-04-07
  • RedisRedis集群的5种使用方式,各自优缺点分析

    Redis集群的5种使用方式,各自优缺点分析

    Redis 多副本,采用主从(replication)部署结构,相较于单副本而言最大的特点就是主从实例间数据实时同步,并且提供数据持久化和备份策略。...

    优知学院4082021-08-10
  • Redis如何使用Redis锁处理并发问题详解

    如何使用Redis锁处理并发问题详解

    这篇文章主要给大家介绍了关于如何使用Redis锁处理并发问题的相关资料,文中通过示例代码介绍的非常详细,对大家学习或者使用Redis具有一定的参考学习...

    haofly4522019-11-26
  • Redisredis缓存存储Session原理机制

    redis缓存存储Session原理机制

    这篇文章主要为大家介绍了redis缓存存储Session原理机制详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪...

    程序媛张小妍9252021-11-25
  • Redis《面试八股文》之 Redis十六卷

    《面试八股文》之 Redis十六卷

    redis 作为我们最常用的内存数据库,很多地方你都能够发现它的身影,比如说登录信息的存储,分布式锁的使用,其经常被我们当做缓存去使用。...

    moon聊技术8182021-07-26
  • RedisRedis Template实现分布式锁的实例代码

    Redis Template实现分布式锁的实例代码

    这篇文章主要介绍了Redis Template实现分布式锁,需要的朋友可以参考下 ...

    晴天小哥哥2592019-11-18