天天看點

(Redis使用系列) Springboot 整合Redisson 實作分布式鎖 七

前言

該篇是基于springboot 項目整合 Redisson 實作對redis的操作。

内容:

1.以自定注解aop方式實作對接口使用分布式鎖

2.使用RedissonClient對一些集合的正常操作,資料查詢,存儲等

正文

第一步:

 pom.xml 添加核心依賴包:

<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>
        <!--使用Redis-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
        <dependency>
            <groupId>org.redisson</groupId>
            <artifactId>redisson</artifactId>
            <version>3.9.1</version>
        </dependency>      

第二步:

建立RedissonConfig.java:

import org.redisson.Redisson;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.io.IOException;

/**
 * redisson bean管理
 */
@Configuration
public class RedissonConfig {
    
    /**
     * Redisson用戶端注冊
     * 單機模式
     */
    @Bean(destroyMethod = "shutdown")
    public RedissonClient createRedissonClient() throws IOException {

//       Config config = new Config();
//        SingleServerConfig singleServerConfig = config.useSingleServer();
//        singleServerConfig.setAddress("redis://127.0.0.1:6379");
//        singleServerConfig.setPassword("12345");
//        singleServerConfig.setTimeout(3000);
//        return Redisson.create(config)

        // 本例子使用的是yaml格式的配置檔案,讀取使用Config.fromYAML,如果是Json檔案,則使用Config.fromJSON
        Config config = Config.fromYAML(RedissonConfig.class.getClassLoader().getResource("redisson-config.yml"));
        return Redisson.create(config);
    }


    /**
     * 主從模式 哨兵模式
     *
     **/
   /* @Bean
    public RedissonClient getRedisson() {
        RedissonClient redisson;
        Config config = new Config();
        config.useMasterSlaveServers()
                //可以用"rediss://"來啟用SSL連接配接
                .setMasterAddress("redis://***(主伺服器IP):6379").setPassword("web2017")
                .addSlaveAddress("redis://***(從伺服器IP):6379")
                .setReconnectionTimeout(10000)
                .setRetryInterval(5000)
                .setTimeout(10000)
                .setConnectTimeout(10000);//(連接配接逾時,機關:毫秒 預設值:3000);

        //  哨兵模式config.useSentinelServers().setMasterName("mymaster").setPassword("web2017").addSentinelAddress("***(哨兵IP):26379", "***(哨兵IP):26379", "***(哨兵IP):26380");
        redisson = Redisson.create(config);
        return redisson;
    }*/


   

    

}      

上面配置裡可以使用傳值方式去連接配接redis,也可以選擇從配置檔案擷取參數,反正方式多樣。

redisson-config.yml 檔案(如果沒有密碼設定為null即可):

#Redisson配置
singleServerConfig:
  address: "redis://127.0.0.1:6379"
  password: 12345
  clientName: null
  database: 7 #選擇使用哪個資料庫0~15
  idleConnectionTimeout: 10000
  pingTimeout: 1000
  connectTimeout: 10000
  timeout: 3000
  retryAttempts: 3
  retryInterval: 1500
  reconnectionTimeout: 3000
  failedAttempts: 3
  subscriptionsPerConnection: 5
  subscriptionConnectionMinimumIdleSize: 1
  subscriptionConnectionPoolSize: 50
  connectionMinimumIdleSize: 32
  connectionPoolSize: 64
  dnsMonitoringInterval: 5000
  #dnsMonitoring: false

threads: 0
nettyThreads: 0
codec:
  class: "org.redisson.codec.JsonJacksonCodec"
transportMode: "NIO"      

第三步:

使用鎖,建立DistributeLocker.java :

import java.util.concurrent.TimeUnit;

/**
 * @Author : JCccc
 * @CreateTime : 2020/5/13
 * @Description :
 **/
public interface  DistributeLocker {

    /**
     * 加鎖
     * @param lockKey key
     */
    void lock(String lockKey);

    /**
     * 釋放鎖
     *
     * @param lockKey key
     */
    void unlock(String lockKey);

    /**
     * 加鎖鎖,設定有效期
     *
     * @param lockKey key
     * @param timeout 有效時間,預設時間機關在實作類傳入
     */
    void lock(String lockKey, int timeout);

    /**
     * 加鎖,設定有效期并指定時間機關
     * @param lockKey key
     * @param timeout 有效時間
     * @param unit    時間機關
     */
    void lock(String lockKey, int timeout, TimeUnit unit);

    /**
     * 嘗試擷取鎖,擷取到則持有該鎖傳回true,未擷取到立即傳回false
     * @param lockKey
     * @return true-擷取鎖成功 false-擷取鎖失敗
     */
    boolean tryLock(String lockKey);

    /**
     * 嘗試擷取鎖,擷取到則持有該鎖leaseTime時間.
     * 若未擷取到,在waitTime時間内一直嘗試擷取,超過waitTime還未擷取到則傳回false
     * @param lockKey   key
     * @param waitTime  嘗試擷取時間
     * @param leaseTime 鎖持有時間
     * @param unit      時間機關
     * @return true-擷取鎖成功 false-擷取鎖失敗
     */
    boolean tryLock(String lockKey, long waitTime, long leaseTime, TimeUnit unit)
            throws InterruptedException;

    /**
     * 鎖是否被任意一個線程鎖持有
     * @param lockKey
     * @return true-被鎖 false-未被鎖
     */
    boolean isLocked(String lockKey);

    //lock.isHeldByCurrentThread()的作用是查詢目前線程是否保持此鎖定
    boolean isHeldByCurrentThread(String lockKey);


}      

再是建立這個鎖接口的實作類 ,RedissonDistributeLocker.java :

import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import java.util.concurrent.TimeUnit;

/**
 * redisson實作分布式鎖接口
 */
public class RedissonDistributeLocker implements DistributeLocker {

    private RedissonClient redissonClient;

    public RedissonDistributeLocker(RedissonClient redissonClient) {
        this.redissonClient = redissonClient;
    }

    @Override
    public void lock(String lockKey) {
        RLock lock = redissonClient.getLock(lockKey);
        lock.lock();
    }

    @Override
    public void unlock(String lockKey) {
        RLock lock = redissonClient.getLock(lockKey);
        lock.unlock();
    }

    @Override
    public void lock(String lockKey, int leaseTime) {
        RLock lock = redissonClient.getLock(lockKey);
        lock.lock(leaseTime, TimeUnit.MILLISECONDS);
    }

    @Override
    public void lock(String lockKey, int timeout, TimeUnit unit) {
        RLock lock = redissonClient.getLock(lockKey);
        lock.lock(timeout, unit);
    }

    @Override
    public boolean tryLock(String lockKey) {
        RLock lock = redissonClient.getLock(lockKey);
        return lock.tryLock();
    }

    @Override
    public boolean tryLock(String lockKey, long waitTime, long leaseTime,
                           TimeUnit unit) throws InterruptedException {
        RLock lock = redissonClient.getLock(lockKey);
        return lock.tryLock(waitTime, leaseTime, unit);
    }

    @Override
    public boolean isLocked(String lockKey) {
        RLock lock = redissonClient.getLock(lockKey);
        return lock.isLocked();
    }

    @Override
    public boolean isHeldByCurrentThread(String lockKey) {
        RLock lock = redissonClient.getLock(lockKey);
        return lock.isHeldByCurrentThread();
    }


}      

然後建立鎖操作工具類,RedissonLockUtils.java :

import java.util.concurrent.TimeUnit;

/**
 * redisson鎖工具類
 */
public class RedissonLockUtils {

    private static DistributeLocker locker;

    public static void setLocker(DistributeLocker locker) {
        RedissonLockUtils.locker = locker;
    }

    public static void lock(String lockKey) {
        locker.lock(lockKey);
    }

    public static void unlock(String lockKey) {
                locker.unlock(lockKey); }
    public static void lock(String lockKey, int timeout) {
        locker.lock(lockKey, timeout);
    }

    public static void lock(String lockKey, int timeout, TimeUnit unit) {
        locker.lock(lockKey, timeout, unit);
    }

    public static boolean tryLock(String lockKey) {
        return locker.tryLock(lockKey);
    }

    public static boolean tryLock(String lockKey, long waitTime, long leaseTime,
                                  TimeUnit unit) throws InterruptedException {
        return locker.tryLock(lockKey, waitTime, leaseTime, unit);
    }

    public static boolean isLocked(String lockKey) {
        return locker.isLocked(lockKey);
    }


    public static boolean isHeldByCurrentThread(String lockKey) {
        return locker.isHeldByCurrentThread(lockKey);
    }
}      

然後注意,我們為了友善使用,我們在RedissonConfig.java 配置類裡面添加注入bean代碼(将RedissonDistributeLocker 交給Spring管理,且将RedissonDistributeLocker交給我們的操作鎖工具類),在RedissonConfig.java 加上:

@Bean
    public RedissonDistributeLocker redissonLocker(RedissonClient redissonClient) {
        RedissonDistributeLocker locker = new RedissonDistributeLocker(redissonClient);
        RedissonLockUtils.setLocker(locker);
        return locker;
    }      

到這裡,其實我們已經整合完畢Redisson了。

接下來我們來實作AOP 注解方式去給接口加鎖和釋放鎖。

1. 建立自定義注解 ,RedissonLockAnnotation.java:

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * 分布式鎖自定義注解
 */
@Target(ElementType.METHOD) //注解在方法
@Retention(RetentionPolicy.RUNTIME)
public @interface RedissonLockAnnotation {

    /**
     * 指定組成分布式鎖的key
     */
    String lockRedisKey();
}      

2.建立配合注解使用的aop類,RedissonLockAop.java(自定義注解的路徑改成你自己項目的路徑):

import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.ResponseBody;

import java.util.concurrent.TimeUnit;

/**
 * 分布式鎖的 aop
 */
@Aspect
@Component
@Slf4j
public class RedissonLockAop {

    /**
     * 切點,攔截被 @RedissonLockAnnotation 修飾的方法
     */
    @Pointcut("@annotation(com.bsapple.vshop.redisson.RedissonLockAnnotation)")
    public void redissonLockPoint() {
    }

    @Around("redissonLockPoint()")
    @ResponseBody
    public String checkLock(ProceedingJoinPoint pjp) throws Throwable {
        //目前線程名
        String threadName = Thread.currentThread().getName();
        log.info("線程{}------進入分布式鎖aop------", threadName);
        //擷取參數清單
        Object[] objs = pjp.getArgs();
        //因為隻有一個JSON參數,直接取第一個
        JSONObject param = (JSONObject) objs[0];
        //擷取該注解的執行個體對象
        RedissonLockAnnotation annotation = ((MethodSignature) pjp.getSignature()).
                getMethod().getAnnotation(RedissonLockAnnotation.class);
        //生成分布式鎖key的鍵名,以逗号分隔
        String lockRedisKey = annotation.lockRedisKey();
        StringBuffer keyBuffer = new StringBuffer();
        if (StringUtils.isEmpty(lockRedisKey)) {
            log.info("線程{} lockRedisKey設定為空,不加鎖", threadName);
            pjp.proceed();
            return "NULL LOCK";
        } else {
            //生成分布式鎖key
            String[] keyPartArray = lockRedisKey.split(",");
            for (String keyPart : keyPartArray) {
                keyBuffer.append(param.getString(keyPart));
            }
            String key = keyBuffer.toString();
            log.info("線程{} 鎖的key={}", threadName, key);
            //擷取鎖  3000 等到擷取鎖的時間  leaseTime 擷取鎖後持有時間   時間機關 MILLISECONDS:毫秒
            if (RedissonLockUtils.tryLock(key, 3000, 5000, TimeUnit.MILLISECONDS)) {
                try {
                    log.info("線程{} 擷取鎖成功", threadName);

                    return (String) pjp.proceed();
                } finally {
                    if (RedissonLockUtils.isLocked(key)) {
                        log.info("key={}對應的鎖被持有,線程{}",key, threadName);

                        if (RedissonLockUtils.isHeldByCurrentThread(key)) {
                            log.info("目前線程 {} 保持鎖定", threadName);
                            RedissonLockUtils.unlock(key);
                            log.info("線程{} 釋放鎖", threadName);

                        }

                    }


                }
            } else {
                log.info("線程{} 擷取鎖失敗", threadName);
                return " GET LOCK FAIL";
            }
        }


    }
}      

測試&分析:

第一步,寫一個測試接口,使用分布式鎖注解,來看看效果:

TestController.java:

import com.alibaba.fastjson.JSONObject;
import org.redisson.api.RBucket;
import org.redisson.api.RMap;
import org.redisson.api.RSet;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
import java.util.concurrent.TimeUnit;

/**
 * @Author : JCccc
 * @CreateTime : 2020/5/13
 * @Description :
 **/
@RestController
public class TestController {

    @Autowired
    private RedissonClient redissonClient;

    @PostMapping(value = "testLock", consumes = "application/json")
    @RedissonLockAnnotation(lockRedisKey = "productName,platFormName")
    public String testLock(@RequestBody JSONObject params) throws InterruptedException {
        /**
         * 分布式鎖key=params.getString("productName")+params.getString("platFormName");
         * productName 産品名稱  platFormName 平台名稱 如果都一緻,那麼分布式鎖的key就會一直,那麼就能避免并發問題
         */
        //TODO 業務處理

        try {
            System.out.println("接收到的參數:"+params.toString());
            System.out.println("執行相關業務...");
            System.out.println("執行相關業務.....");

            System.out.println("執行相關業務......");

        } catch (InterruptedException e) {
            System.out.println("已進行日志記錄");
        }

        return "success";
    }


}      

第二步,調用接口,打斷點看看整體的流程:

(Redis使用系列) Springboot 整合Redisson 實作分布式鎖 七

調用接口,

(Redis使用系列) Springboot 整合Redisson 實作分布式鎖 七

繼續往下看,

(Redis使用系列) Springboot 整合Redisson 實作分布式鎖 七

繼續往下,

(Redis使用系列) Springboot 整合Redisson 實作分布式鎖 七

此刻可以看到redis資料庫裡,

生成了對應的鎖:

(Redis使用系列) Springboot 整合Redisson 實作分布式鎖 七

然後業務執行完後,在finally裡會對目前的産品key進行釋放鎖,

(Redis使用系列) Springboot 整合Redisson 實作分布式鎖 七

ok,以上就是使用Redisson實作分布式鎖的相關代碼介紹,接下來簡單介紹下,使用redisson去操作各常用集合資料。

方法的使用介紹:

1. 操作 String :

@GetMapping("/testData")
    public void testData() {

        // 插入 字元串
        RBucket<String> keyObj = redissonClient.getBucket("keyStr");
        keyObj.set("testStr", 300l, TimeUnit.SECONDS);

        //查詢 字元串
        RBucket<String> keyGet = redissonClient.getBucket("keyStr");
        System.out.println(keyGet.get());
        
    }      

存入成功: 

(Redis使用系列) Springboot 整合Redisson 實作分布式鎖 七

取出成功:

(Redis使用系列) Springboot 整合Redisson 實作分布式鎖 七

2.操作list:

// 插入 list
        List<Integer> list = redissonClient.getList("list");
        list.add(1);
        list.add(2);
        list.add(3);
        list.add(4);

        //查詢 list
        List<Integer>  listGet = redissonClient.getList("list");
        System.out.println(listGet.toString());      

 3.操作map:

//插入 map
        RMap<Object, Object> addMap = redissonClient.getMap("addMap");
        addMap.put("man1","a");
        addMap.put("man2","b");
        addMap.put("man3","c");

        //查詢 map
        RMap<Object, Object> mapGet = redissonClient.getMap("addMap");
        System.out.println(mapGet.get("man1"));      

4.操作set:

//設定 set
        RSet<Object> testSet = redissonClient.getSet("testSet");
        testSet.add("S");
        testSet.add("D");
        testSet.add("F");
        testSet.add("G");

        //查詢 set
        RSet<Object> setGet = redissonClient.getSet("testSet");
        System.out.println(setGet.readAll());      

其餘更多的操作方法,可以點進去源碼看:

(Redis使用系列) Springboot 整合Redisson 實作分布式鎖 七

PS:覺得使用RedissonClient存值麻煩的,其實可以使用以前的方法,同時使用redissonClient和正常整合redis使用StringRedisTemplate/RedisTemplate  是完全不沖突的。

繼續閱讀