• Redis锁实现防重复提交和并发问题


    @Slf4j
    @Component
    public class RedisLock {
    
        public static final int LOCK_EXPIRE = 5000;
    
        @Autowired
        private StringRedisTemplate redisTemplate;
    
    
        /**
         *  分布式锁
         *
         * @param key key值
         * @return 是否获取到
         */
        public boolean lock(String key) {
    
            String lock = key;
            try {
                return (Boolean) redisTemplate.execute((RedisCallback) connection -> {
                    long expireAt = System.currentTimeMillis() + LOCK_EXPIRE;
                    Boolean acquire = connection.setNX(lock.getBytes(), String.valueOf(expireAt).getBytes());
                    if (acquire) {
                        log.info("用户 [{}]加锁成功",key);
    
                        return true;
                    } else {
                        log.info("用户 [{}]加锁失败",key);
    
                        //判断该key上的值是否过期了
                        byte[] value = connection.get(lock.getBytes());
                        if (Objects.nonNull(value) && value.length > 0) {
                            long expireTime = Long.parseLong(new String(value));
                            if (expireTime < System.currentTimeMillis()) {
                                // 如果锁已经过期
                                byte[] oldValue = connection.getSet(lock.getBytes(), String.valueOf(System.currentTimeMillis() + LOCK_EXPIRE).getBytes());
                                // 防止死锁
                                return Long.parseLong(new String(oldValue)) < System.currentTimeMillis();
                            }
                        }
                    }
                    return false;
                });
            } finally {
                RedisConnectionUtils.unbindConnection(redisTemplate.getConnectionFactory());
            }
        }
    
    
    
        /**
         * 删除锁
         *
         * @param key
         */
        public void delete(String key) {
            try {
                redisTemplate.delete(key);
            } finally {
                RedisConnectionUtils.unbindConnection(redisTemplate.getConnectionFactory());
            }
        }
    
    //实现业务
    
            try{
                // 判断是否获取了锁
                boolean getLock = redisLock(lockKey);
                if(getLock){
                    // 此处可以开始写需要实现的代码
                 
                }
            }catch(Exception e){
          e.printStackTrace();
    }finally { 
    // 判断是否超时了,如果未超时,则释放锁。 超时了,锁有可能被其他线程拿走了,就不做任何操作
    redisLock.delete(String.valueOf(caronwerId));
    }
    }

     知识点: redis Setnx(SET if Not eXists) 命令在指定的 key 不存在时,为 key 设置指定的值。

    设置成功,返回 1 。 设置失败,返回 0 。

    redis> EXISTS job                # job 不存在
    (integer) 0
    
    redis> SETNX job "programmer"    # job 设置成功
    (integer) 1
    
    redis> SETNX job "code-farmer"   # 尝试覆盖 job ,失败
    (integer) 0
    
    redis> GET job                   # 没有被覆盖
    "programmer"
    
    一.redis命令讲解: 
    setnx()命令: 
    setnx的含义就是SET if Not Exists,其主要有两个参数 setnx(key, value)。
    
    该方法是原子的,如果key不存在,则设置当前key成功,返回1;如果当前key已经存在,则设置当前key失败,返回0。
    
    get()命令: 
    get(key) 获取key的值,如果存在,则返回;如果不存在,则返回nil; 
    getset()命令: 
    这个命令主要有两个参数 getset(key, newValue)。该方法是原子的,对key设置newValue这个值,并且返回key原来的旧值。 
    假设key原来是不存在的,那么多次执行这个命令,会出现下边的效果: 
    1. getset(key, “value1”) 返回nil 此时key的值会被设置为value1 
    2. getset(key, “value2”) 返回value1 此时key的值会被设置为value2 
    3. 依次类推! 
    二.具体的使用步骤如下: 
    1. setnx(lockkey, 当前时间+过期超时时间) ,如果返回1,则获取锁成功;如果返回0则没有获取到锁,转向2。 
    2. get(lockkey)获取值oldExpireTime ,并将这个value值与当前的系统时间进行比较,如果小于当前系统时间,则认为这个锁已经超时,可以允许别的请求重新获取,转向3。 
    3. 计算newExpireTime=当前时间+过期超时时间,然后getset(lockkey, newExpireTime) 会返回当前lockkey的值currentExpireTime。 
    4. 判断currentExpireTime与oldExpireTime 是否相等,如果相等,说明当前getset设置成功,获取到了锁。如果不相等,说明这个锁又被别的请求获取走了,那么当前请求可以直接返回失败,或者继续重试。 
    5. 在获取到锁之后,当前线程可以开始自己的业务处理,当处理完毕后,比较自己的处理时间和对于锁设置的超时时间,如果小于锁设置的超时时间,则直接执行delete释放锁;如果大于锁设置的超时时间,则不需要再锁进行处理
    
  • 相关阅读:
    Kendo UI
    Docker
    jQuery DataTables && Django serializer
    MySQL ODBC for Linux
    mongoengine
    Python之多线程
    Python中的正则表达式
    通过恢复目录(Catalogue)进行PDB级别的PITR恢复
    执行PDB的PITR恢复失败的说明
    在PDB级别中如何切换或重建UNDO表空间
  • 原文地址:https://www.cnblogs.com/ampl/p/13609165.html
Copyright © 2020-2023  润新知