分布式锁redisson

1:pom.xml添加依赖

<dependency>
	<groupId>org.redisson</groupId>
	<artifactId>redisson-spring-boot-starter</artifactId>
	<version>3.21.1</version>
</dependency>

2-1:方法一:读取默认yml配置

# redis配置
spring:
  redis:
    database: 0
    host: 127.0.0.1
    password: 123456
    port: 6379

2-2:redisson.yml文件的方式

spring:
  redis:
    redisson:
        file: classpath:redisson.yml

2-3:redisson.yml

# 单节点配置
singleServerConfig:
  # 数据库编号
  database: 0
  # 节点地址
  address: redis://127.0.0.1:6379
  # 密码
  password: 123456
# 集群模式
clusterServersConfig:
  # 集群节点地址
  nodeAddresses:
    - "redis://127.0.0.1:16379"
    - "redis://127.0.0.1:26379"
    - "redis://127.0.0.1:36379"
  password: 123456
#Redis集群不支持多个数据库的概念,默认只有一个数据库,即db 0,所以这里是没有database这个参数的

3-1:方法二:自定义yml配置

spring:
  redis:
    # redisson配置
    redisson:
        # 如果该值为false,系统将不会创建RedissionClient的bean。
        enabled: true
        # mode的可用值为,single/cluster/sentinel/master-slave
        mode: single
        # single: 单机模式
        #   address: redis://localhost:6379
        # cluster: 集群模式
        #   每个节点逗号分隔,同时每个节点前必须以redis://开头。
        #   address: redis://localhost:6379,redis://localhost:6378,...
        # sentinel:
        #   每个节点逗号分隔,同时每个节点前必须以redis://开头。
        #   address: redis://localhost:6379,redis://localhost:6378,...
        # master-slave:
        #   每个节点逗号分隔,第一个为主节点,其余为从节点。同时每个节点前必须以redis://开头。
        #   address: redis://localhost:16379,redis://localhost:26379
        address: redis://127.0.0.1:6379
        # redis 密码,空可以不填。
        password: 123456
        database: 0

3-2:RedissonConfig自定义配置类

import org.apache.commons.lang3.StringUtils;
import org.redisson.Redisson;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
 
/**
 * Redisson配置类。
 */
@Configuration
@ConditionalOnProperty(name = "spring.redis.redisson.enabled", havingValue = "true")
public class RedissonConfig {
 
    @Value("${spring.redis.redisson.mode}")
    private String mode;
 
    /**
     * 仅仅用于sentinel模式。
     */
    @Value("${spring.redis.redisson.masterName:}")
    private String masterName;
 
    @Value("${spring.redis.redisson.address}")
    private String address;
 
    @Value("${spring.redis.redisson.password:}")
    private String password;
 
    /**
     * 数据库默认0
     */
    @Value("${spring.redis.redisson.database:0}")
    private Integer database;
 
    @Bean
    public RedissonClient redissonClient() {
        if (StringUtils.isBlank(password)) {
            password = null;
        }
        Config config = new Config();
        if ("single".equals(mode)) {
            config.useSingleServer()
                    .setDatabase(database)
                    .setPassword(password)
                    .setAddress(address);
        } else if ("cluster".equals(mode)) {
            String[] clusterAddresses = address.split(",");
            config.useClusterServers()
                    //集群模式不支持多个数据库概念,默认db 0
                    .setPassword(password)
                    .addNodeAddress(clusterAddresses);
        } else if ("sentinel".equals(mode)) {
            String[] sentinelAddresses = address.split(",");
            config.useSentinelServers()
                    .setDatabase(database)
                    .setPassword(password)
                    .setMasterName(masterName)
                    .addSentinelAddress(sentinelAddresses);
        } else if ("master-slave".equals(mode)) {
            String[] masterSlaveAddresses = address.split(",");
            if (masterSlaveAddresses.length == 1) {
                throw new IllegalArgumentException(
                        "redis.redisson.address MUST have multiple redis addresses for master-slave mode.");
            }
            String[] slaveAddresses = new String[masterSlaveAddresses.length - 1];
            System.arraycopy(masterSlaveAddresses, 1, slaveAddresses, 0, slaveAddresses.length);
            config.useMasterSlaveServers()
                    .setDatabase(database)
                    .setPassword(password)
                    .setMasterAddress(masterSlaveAddresses[0])
                    .addSlaveAddress(slaveAddresses);
        } else {
            throw new IllegalArgumentException(mode);
        }
        return Redisson.create(config);
    }
}

4:demo

    @Resource
    private RedissonClient redissonClient;

    private static final String LOCK_KEY = "myLock";

    @SneakyThrows
    @GetMapping("test")
    public void test() {

        // 获取锁对象
        RLock lock = redissonClient.getLock(LOCK_KEY);

        // 模拟多个线程尝试获取锁
        for (int i = 0; i < 5; i++) {
            new Thread(() -> {
                try {
                    // 尝试获取锁,最多等待10秒,获取锁后10秒自动释放
                    lock.lock(10, TimeUnit.SECONDS);
                    System.out.println(new Date()+Thread.currentThread().getName() + " 获取到锁,开始处理任务...");
                    Thread.sleep(2000); // 模拟处理任务需要花费一些时间
                    System.out.println(new Date()+Thread.currentThread().getName() + " 处理任务完成,释放锁...");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    // 无论如何,最后都要确保锁被释放
                    lock.unlock();
                }
            }).start();
        }

        // 让主线程等待一段时间,以便观察其他线程的行为
        Thread.sleep(10000);

        System.out.println("===");

    }

4-1:运行结果

在这里插入图片描述

5-1:自定义注解分布式锁JLock



package com.huan.study.mybatis.config;

import java.lang.annotation.*;

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface JLock {
    LockModel lockModel() default LockModel.AUTO;

    String[] lockKey() default {};

    String keyConstant() default "";

    long expireSeconds() default 30000L;

    long waitTime() default 10000L;

    String failMsg() default "获取锁失败,请稍后重试";
}

5-2:LockModel


package com.huan.study.mybatis.config;

/**
 * 锁的模式
 */
public enum LockModel {
    //可重入锁
    REENTRANT,
    //公平锁
    FAIR,
    //联锁(可以把一组锁当作一个锁来加锁和释放)
    MULTIPLE,
    //红锁
    REDLOCK,
    //读锁
    READ,
    //写锁
    WRITE,
    //自动模式,当参数只有一个.使用 REENTRANT 参数多个 REDLOCK
    AUTO
}


5-3:BaseAspect

package com.huan.study.mybatis.aspect;

import lombok.extern.slf4j.Slf4j;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;

import java.util.ArrayList;
import java.util.List;

@Slf4j
public class BaseAspect {

    /**
     * 通过spring SpEL 获取参数
     *
     * @param key            定义的key值 以#开头 例如:#user
     * @param parameterNames 形参
     * @param values         形参值
     * @param keyConstant    key的常亮
     * @return
     */
    public List<String> getValueBySpEL(String key, String[] parameterNames, Object[] values, String keyConstant) {
        List<String> keys = new ArrayList<>();
        if (!key.contains("#")) {
            String s = "redis:lock:" + key + keyConstant;
            log.debug("lockKey:" + s);
            keys.add(s);
            return keys;
        }
        //spel解析器
        ExpressionParser parser = new SpelExpressionParser();
        //spel上下文
        EvaluationContext context = new StandardEvaluationContext();
        for (int i = 0; i < parameterNames.length; i++) {
            context.setVariable(parameterNames[i], values[i]);
        }
        Expression expression = parser.parseExpression(key);
        Object value = expression.getValue(context);
        if (value != null) {
            if (value instanceof List) {
                List value1 = (List) value;
                for (Object o : value1) {
                    addKeys(keys, o, keyConstant);
                }
            } else if (value.getClass().isArray()) {
                Object[] obj = (Object[]) value;
                for (Object o : obj) {
                    addKeys(keys, o, keyConstant);
                }
            } else {
                addKeys(keys, value, keyConstant);
            }
        }
        log.info("表达式key={},value={}", key, keys);
        return keys;
    }

    private void addKeys(List<String> keys, Object o, String keyConstant) {
        keys.add("redis:lock:" + o.toString() + keyConstant);
    }
}

5-4:DistributedLockHandler

package com.huan.study.mybatis.aspect;

import com.huan.study.mybatis.config.JLock;
import com.huan.study.mybatis.config.LockModel;
import lombok.SneakyThrows;
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.reflect.MethodSignature;
import org.redisson.RedissonMultiLock;
import org.redisson.RedissonRedLock;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
import org.springframework.stereotype.Component;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;


/**
 * 分布式锁解析器
 */
@Slf4j
@Aspect
@Component
public class DistributedLockHandler extends BaseAspect {


    @Autowired(required = false)
    private RedissonClient redissonClient;

    /**
     * 切面环绕通知
     *
     * @param joinPoint
     * @param jLock
     * @return Object
     */
    @SneakyThrows
    @Around("@annotation(jLock)")
    public Object around(ProceedingJoinPoint joinPoint, JLock jLock) {
        Object obj = null;
        log.info("进入RedisLock环绕通知...");
        RLock rLock = getLock(joinPoint, jLock);
        boolean res = false;
        //获取超时时间
        long expireSeconds = jLock.expireSeconds();
        //等待多久,n秒内获取不到锁,则直接返回
        long waitTime = jLock.waitTime();
        //执行aop
        if (rLock != null) {
            try {
                if (waitTime == -1) {
                    res = true;
                    //一直等待加锁
                    rLock.lock(expireSeconds, TimeUnit.MILLISECONDS);
                } else {
                    res = rLock.tryLock(waitTime, expireSeconds, TimeUnit.MILLISECONDS);
                }
                if (res) {
                    obj = joinPoint.proceed();
                } else {
                    log.error("获取锁异常");
                }
            } finally {
                if (res) {
                    rLock.unlock();
                }
            }
        }
        log.info("结束RedisLock环绕通知...");
        return obj;
    }

    @SneakyThrows
    private RLock getLock(ProceedingJoinPoint joinPoint, JLock jLock) {
        String[] keys = jLock.lockKey();
        if (keys.length == 0) {
            throw new RuntimeException("keys不能为空");
        }
        String[] parameterNames = new LocalVariableTableParameterNameDiscoverer().getParameterNames(((MethodSignature) joinPoint.getSignature()).getMethod());
        Object[] args = joinPoint.getArgs();

        LockModel lockModel = jLock.lockModel();
        RLock rLock = null;
        String keyConstant = jLock.keyConstant();
        if (lockModel.equals(LockModel.AUTO)) {
            if (keys.length > 1) {
                lockModel = LockModel.REDLOCK;
            } else {
                lockModel = LockModel.REENTRANT;
            }
        }
        if (!lockModel.equals(LockModel.MULTIPLE) && !lockModel.equals(LockModel.REDLOCK) && keys.length > 1) {
            throw new RuntimeException("参数有多个,锁模式为->" + lockModel.name() + ".无法锁定");
        }
        switch (lockModel) {
            case FAIR:
                rLock = redissonClient.getFairLock(getValueBySpEL(keys[0], parameterNames, args, keyConstant).get(0));
                break;
            case REDLOCK:
                List<RLock> rLocks = new ArrayList<>();
                for (String key : keys) {
                    List<String> valueBySpEL = getValueBySpEL(key, parameterNames, args, keyConstant);
                    for (String s : valueBySpEL) {
                        rLocks.add(redissonClient.getLock(s));
                    }
                }
                RLock[] locks = new RLock[rLocks.size()];
                int index = 0;
                for (RLock r : rLocks) {
                    locks[index++] = r;
                }
                rLock = new RedissonRedLock(locks);
                break;
            case MULTIPLE:
                rLocks = new ArrayList<>();

                for (String key : keys) {
                    List<String> valueBySpEL = getValueBySpEL(key, parameterNames, args, keyConstant);
                    for (String s : valueBySpEL) {
                        rLocks.add(redissonClient.getLock(s));
                    }
                }
                locks = new RLock[rLocks.size()];
                index = 0;
                for (RLock r : rLocks) {
                    locks[index++] = r;
                }
                rLock = new RedissonMultiLock(locks);
                break;
            case REENTRANT:
                List<String> valueBySpEL = getValueBySpEL(keys[0], parameterNames, args, keyConstant);
                //如果spel表达式是数组或者LIST 则使用红锁
                if (valueBySpEL.size() == 1) {
                    rLock = redissonClient.getLock(valueBySpEL.get(0));
                } else {
                    locks = new RLock[valueBySpEL.size()];
                    index = 0;
                    for (String s : valueBySpEL) {
                        locks[index++] = redissonClient.getLock(s);
                    }
                    rLock = new RedissonRedLock(locks);
                }
                break;
            case READ:
                rLock = redissonClient.getReadWriteLock(getValueBySpEL(keys[0], parameterNames, args, keyConstant).get(0)).readLock();
                break;
            case WRITE:
                rLock = redissonClient.getReadWriteLock(getValueBySpEL(keys[0], parameterNames, args, keyConstant).get(0)).writeLock();
                break;
        }
        return rLock;
    }
}

  • 4
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值