zoukankan      html  css  js  c++  java
  • Redis 分布式锁

    一、描述

      一套系统,集群部署,高并发时,访问同一个业务方法,该业务涉及到数据的安全性、准确性,只允许单线程操作,这个时候就需要分布式锁来实现。

      redis实现分布式锁可以采用ValueOperations.setIfAbsent(key, value)或RedisConnection.setNX(key, value)方法

      ValueOperations.setIfAbsent(key, value)封装了RedisConnection.setNX(key, value)

      该方法用意:

        并发或非并发都只允许一个插入成功。

        如果key存在,插入值失败,返回false,可以循环执行,直至成功或超时。

        如果key不存,插入值成功,返回true,业务逻辑处理结束后,将key删除。

    二、实现(SpringBoot集成redis)

      1.定义结果类

    package com.sze.redis.lock;
    
    /**
     * <br>类 名: LockInfo 
     * <br>描 述: 加锁、释放锁的返回信息
     * <br>作 者: shizhenwei 
     * <br>创 建: 2018年9月10日 
     * <br>版 本: v1.0.0 
     * <br>
     * <br>历 史: (版本) 作者 时间 注释
     */
    public class LockInfo {
        private boolean success;
        private String key;
        private String value;
        private String description;
        
        public LockInfo(boolean success, String key, String value) {
            super();
            this.success = success;
            this.key = key;
            this.value = value;
        }
        
        public LockInfo(boolean success, String key, String value, String description) {
            super();
            this.success = success;
            this.key = key;
            this.value = value;
            this.description = description;
        }
        
        public boolean isSuccess() {
            return success;
        }
        public void setSuccess(boolean success) {
            this.success = success;
        }
        public String getKey() {
            return key;
        }
        public void setKey(String key) {
            this.key = key;
        }
        public String getValue() {
            return value;
        }
        public void setValue(String value) {
            this.value = value;
        }
        public String getDescription() {
            return description;
        }
        public void setDescription(String description) {
            this.description = description;
        }
    }

     

      2.定义接口

    package com.sze.redis.lock;
    
    public interface RedisLockManager {
        public static final String ID = "RedisLockManager";
        
        /**
         * <br>描 述: 添加一把锁
         * <br>作 者: shizhenwei 
         * <br>历 史: (版本) 作者 时间 注释
         * @param key 锁的唯一标识
         * @param value 给该key设置一个值,建议使用sessionId,这样你可以通过自己的sessionId删除释放自己的锁
         * @param addTimeOut 添加锁的过程的超时时长,即 时间内  锁没有添加上视为失败 单位毫秒
         * @param lockTimeOut 添加锁成功后,该锁的有效时长,即 超过该时长 锁自动放弃 单位毫秒         建议 大于 真实业务逻辑处理时长
         * @return
         */
        LockInfo addlock(String key,String value,int addTimeOut,int lockTimeOut);
    
        
        /**
         * <br>描 述:    放弃 释放锁
         * <br>作 者: shizhenwei 
         * <br>历 史: (版本) 作者 时间 注释
         * @param key 锁的唯一标识
         * @param value 校验值是否正确,正确则允许释放
         * @return
         */
        LockInfo freeLock(String key,String value);
        
        /**
         * <br>描 述: 强制占有一把锁,如果这把锁之前有线程占有,可能会出现高并发导致的数据错误
         * <br>作 者: shizhenwei 
         * <br>历 史: (版本) 作者 时间 注释
         * @param key 锁的唯一标识
         * @param value 给该key设置一个值,建议使用sessionId,这样你可以通过自己的sessionId删除释放自己的锁
         * @param lockTimeOut 添加锁成功后,该锁的有效时长,即 超过该时长 锁自动放弃 单位毫秒        建议 大于 真实业务逻辑处理时长
         * @return
         */
        LockInfo enforceAddlock(String key,String value,int lockTimeOut);
        
        
        /**
         * <br>描 述:    强制 放弃 释放锁,如果这把锁之前有线程占有,释放后,有可能会出现高并发导致的数据错误
         * <br>作 者: shizhenwei 
         * <br>历 史: (版本) 作者 时间 注释
         * @param key 锁的唯一标识
         * @return
         */
        LockInfo enForceFreeLock(String key);
    }

     

      3.实现类

    package com.sze.redis.lock;
    
    import java.util.concurrent.TimeUnit;
    
    import javax.annotation.PostConstruct;
    
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.data.redis.core.StringRedisTemplate;
    import org.springframework.data.redis.core.ValueOperations;
    import org.springframework.stereotype.Service;
    import org.springframework.util.StringUtils;
    
    @Service(RedisLockManager.ID)
    public class RedisLockManagerImpl implements RedisLockManager {
        
        @Autowired
        private StringRedisTemplate redisTampate;
        
        private ValueOperations<String, String> stringRedis;
        
        /**
         * <br>描 述: 初始化时赋值
         * <br>作 者: shizhenwei 
         * <br>历 史: (版本) 作者 时间 注释
         */
        @PostConstruct
        private void init(){
            stringRedis = redisTampate.opsForValue();
        }
        
        /**
         * 添加分布式锁
         * key 建议全局常量定义
         * value 建议使用sessionId
         * addTimeOut 毫秒
         * lockTimeOut 毫秒 建议大于 真实业务逻辑处理时长
         * 详解见接口注解
         */
        @Override
        public LockInfo addlock(String key, String value, int addTimeOut, int lockTimeOut) {
            //StringUtils是spring framework自带的工具类,功能还是挺齐全的,建议开发中使用该类或者继承、copy其类
            if(!StringUtils.hasText(key) || !StringUtils.hasText(value)){
                return new LockInfo(false, key, value,"key和value不能为空");
            }
            
            if(addTimeOut<100 || lockTimeOut<100){
                return new LockInfo(false, key, value,"addTimeOut和lockTimeOut不能小于100毫秒");
            }
            
            //setIfAbsent执行的底层的RedisConnection.setNX方法,分布式锁的判定也是通过该方法
            Boolean b = stringRedis.setIfAbsent(key, value);//该key有值,返回false,意为该key在被其他分布式锁定占用
            if(b){
                //获取到锁
                redisTampate.expire(key, lockTimeOut, TimeUnit.MILLISECONDS);//设置锁的有效时长
                return new LockInfo(true, key, value,"锁添加成功");        
            }else{
                //循环获取锁,直到获取到,或者超市
                long timeOut = System.currentTimeMillis()+addTimeOut;
                while(System.currentTimeMillis()<timeOut){
                    try {
                        //之所以设置睡眠,目的为了降低jvm压力
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        return new LockInfo(false, key, value,"线程异常");
                    }
                    //setIfAbsent执行的底层的RedisConnection.setNX方法,分布式锁的判定也是通过该方法
                    Boolean b2 = stringRedis.setIfAbsent(key, value);
                    if(b2){
                        redisTampate.expire(key, lockTimeOut, TimeUnit.MILLISECONDS);//设置锁的有效时长
                        return new LockInfo(true, key, value,"锁添加成功,之前有线程在占用");
                    }
                }
                return new LockInfo(false, key, value,"锁正在被其他线程持续占用中,获取锁的请求超时");
            }
        }
        
        /**
         * 释放分布式锁
         * key 建议全局常量定义
         * value 建议使用sessionId
         * 详解见接口注解
         */
        @Override
        public LockInfo freeLock(String key, String value) {
            //StringUtils是spring framework自带的工具类,功能还是挺齐全的,建议开发中使用该类或者继承、copy其类
            if(!StringUtils.hasText(key) || !StringUtils.hasText(value)){
                return new LockInfo(false, key, value,"key和value不能为空");
            }
            if(value.equals(stringRedis.get(key))){
                redisTampate.delete(key);
                return new LockInfo(true, key, value,"锁释放成功");
            }else{
                return new LockInfo(false, key, value,"value不对,无权释放锁");
            }
        }
        
        
        /**
         * 强制添加分布式锁
         * key 建议全局常量定义
         * value 建议使用sessionId
         * lockTimeOut 毫秒 建议大于 真实业务逻辑处理时长
         * 详解见接口注解
         */
        @Override
        public LockInfo enforceAddlock(String key, String value,int lockTimeOut) {
            //StringUtils是spring framework自带的工具类,功能还是挺齐全的,建议开发中使用该类或者继承、copy其类
            if(!StringUtils.hasText(key) || !StringUtils.hasText(value)){
                return new LockInfo(false, key, value,"key和value不能为空");
            }
            
            if(lockTimeOut<100){
                return new LockInfo(false, key, value,"addTimeOut和lockTimeOut不能小于100毫秒");
            }
            stringRedis.set(key, value, lockTimeOut, TimeUnit.MILLISECONDS);
            return new LockInfo(true, key, value,"锁强制添加成功");
        }
        
        /**
         * 强制释放分布式锁
         * key 建议全局常量定义
         * 详解见接口注解
         */
        @Override
        public LockInfo enForceFreeLock(String key) {
            //StringUtils是spring framework自带的工具类,功能还是挺齐全的,建议开发中使用该类或者继承、copy其类
            if(!StringUtils.hasText(key)){
                return new LockInfo(false, key, null,"key不能为空");
            }
            redisTampate.delete(key);
            return new LockInfo(true, key, null,"锁释放成功");
        }
    }

     

      4.定义常量类

    package com.sze.redis.lock;
    
    /**
     * <br>类 名: LockConstants 
     * <br>描 述: 各个流程需要获取锁,释放锁的key、获取锁的超时时长(毫秒)、拿到锁后的超时时长(毫秒)
     * <br>作 者: shizhenwei 
     * <br>创 建: 2018年9月10日 
     * <br>版 本: v1.0.0 
     * <br>
     * <br>历 史: (版本) 作者 时间 注释
     */
    public class LockConstants {
        //默认key
        public static final String DEFAULT_KEY = "DEFAULT_KEY";
        //添加锁时的默认时长,即 开始——添加锁成功的超时时长
        public static final long DEFAULT_ADDTIME = 3_000;
        //锁添加成功后的有效时长,即 该时长过后,锁自动放弃
        public static final long DEFAULT_LOCKTIME = 3_000;
        
        
        //购买流程——锁——key,分布式多应用,高并发时,获取该key的所
        public static final String BUY_DINGJIE_KEY = "BUY_DINGJIE_KEY";
        
        //获取购买流程锁时的超时长
        public static final int BUY_DINGJIE_ADDTIME = 10_000;
        
        //获取到购买流程锁后的有效时长
        public static final int BUY_DINGJIE_LOCKTIME = 10_000;
    }

      

      5.测试类——模拟多线程

    package com.sze.redis.lock;
    
    import org.junit.Test;
    import org.junit.runner.RunWith;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.boot.test.context.SpringBootTest;
    import org.springframework.test.context.junit4.SpringRunner;
    
    @SpringBootTest
    @RunWith(SpringRunner.class)
    public class RedisLockManagerTest {
        
        @Autowired
        private RedisLockManager redisLockManager;
        
        /**
         * <br>描 述: 购买丁洁
         * <br>作 者: shizhenwei 
         * <br>历 史: (版本) 作者 时间 注释
         */
        public void buy(){
            //这个value值,建议使用sessionId,这里为了测试,使用了一个妹子的名字
            //先取得锁
            LockInfo lockInfo = redisLockManager.addlock(LockConstants.BUY_DINGJIE_KEY, "丁洁", LockConstants.BUY_DINGJIE_ADDTIME, LockConstants.BUY_DINGJIE_LOCKTIME);
            //处理业务
            if(lockInfo.isSuccess()){
                //成功业务
                System.out.println("============"+Thread.currentThread().getName()+"===========");
                System.out.println(lockInfo.getDescription());
                System.out.println("开始购买丁洁业务。。。");
                System.out.println("购买丁洁业务结束。。。");
                //释放锁
                LockInfo lockInfo2 = redisLockManager.freeLock(LockConstants.BUY_DINGJIE_KEY, "丁洁");
                System.out.println(lockInfo2.getDescription());
            }else{
                //失败业务
                System.out.println("============"+Thread.currentThread().getName()+"===========");
                System.out.println(lockInfo.getDescription());
                System.out.println("购买丁洁业务失败。。。");
            }
        }
        
        @Test
        public void test(){
            //测试前先把锁释放掉
            redisLockManager.enForceFreeLock(LockConstants.BUY_DINGJIE_KEY);
            Thread t1 = new Thread(new Runnable() {
                @Override
                public void run() {
                     buy();
                }
            },"线程1");
            Thread t2 = new Thread(new Runnable() {
                @Override
                public void run() {
                     buy();
                }
            },"线程2");
            Thread t3 = new Thread(new Runnable() {
                @Override
                public void run() {
                     buy();
                }
            },"线程3");
            Thread t4 = new Thread(new Runnable() {
                @Override
                public void run() {
                     buy();
                }
            },"线程4");
            t1.start();
            t2.start();
            t3.start();
            t4.start();
            t1.run();
            t2.run();
            t3.run();
            t4.run();
        }
        
    }

      

      6.输出结果

     注:关于SpringBoot集成redis,参考:https://www.cnblogs.com/zwcry/p/9633184.html 

     

     

     

  • 相关阅读:
    GB 51411-2020 金属矿山土地复垦工程设计标准
    GB/T 51413-2020 有色金属工业余热利用设计标准
    DL/T 1907.1-2018等最新电力行业标准
    GB 50205-2020 钢结构工程施工质量验收标准
    DL/T 5210.6-2019 电力建设施工质量验收规程 第6部分:调整试验
    GB/T 38640-2020 盲用数字出版格式
    GB 50325-2020 民用建筑工程室内环境污染控制标准(含条文说明)
    GB 50216-2019 铁路工程结构可靠性设计统一标准
    最新发布的国家标准下载(2020-5-21)
    bind、apply、call的区别
  • 原文地址:https://www.cnblogs.com/zwcry/p/9629127.html
Copyright © 2011-2022 走看看