• 分布式锁redisson


    在单机环境中,应用是同一进程下,我们只需要保证单进程多线程线程安全性即可,通过Java提供的volatile、ReentrantLock、synchronized 以及 concurrent 并发包下一些线程安全的类等就可以做到。但是在多机部署的环境中,不同机器不同进程,就需要在多进程下保证线程安全性,因此,分布式锁应允而生

    1:pom.xml添加依赖

    <dependency>
    	<groupId>org.redissongroupId>
    	<artifactId>redisson-spring-boot-starterartifactId>
    	<version>3.21.1version>
    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;
        }
    }
    
    

    6:使用简单的依赖包实现分布式锁

    6-1:引入依赖

    
    <dependency>
        <groupId>org.springframework.bootgroupId>
        <artifactId>spring-boot-starter-data-redisartifactId>
    dependency>
    

    6-2:application.yml 配置文件

    
    spring:
      data:
        redis:
          database: 0 #redis库
          host: 192.168.11.50 #服务器地址
          password:  #密码
          port: 6379 #端口号
          jedis:
            pool:
              max-active: 8 #连接池最大连接数
              max-idle: 8 #连接池最大空闲连接数
              min-idle: 0 #连接池最小空闲连接数
              max-wait: -1 #端口号
          timeout: 1000 #连接超时时间(毫秒)
    

    6-3:示例代码

    
    @RestController
    public class RedisController {
    
        @Autowired
        private StringRedisTemplate redisTemplate;
    
        /**
         * 模拟下单减库存的场景
         *
         * @return
         */
        @RequestMapping(value = "/deduct_stock")
        public String deductStock() {
            redisTemplate.opsForValue().set("stock", String.valueOf(100));
            for (int i = 0; i < 5; i++) {
                CompletableFuture.runAsync(() -> {
                    // 从redis 中拿当前库存的值
                    int stock = Integer.parseInt(redisTemplate.opsForValue().get("stock"));
                    if (stock > 0) {
                        int realStock = stock - 1;
                        redisTemplate.opsForValue().set("stock", realStock + "");
                        System.out.println("扣减成功,剩余库存:" + realStock);
                    } else {
                        System.out.println("扣减失败,库存不足");
                    }
                });
            }
            return "success";
        }
      }
    
  • 相关阅读:
    生产真实案例:震惊,几条SQL把服务器干崩了,事后还大言不惭!
    内存管理
    [USACO2012-Mar-Silver] Flowerpot 题解(单调队列 c++)
    反转单向链表
    【CSP-J/S初赛知识点整理】
    【Linux】shell脚本+cron定时任务实现“当程序报错时,发送邮件”
    AcWing 831. KMP字符串
    云计算和大数据技术
    Android 性能优化--内存优化分析总结
    k8s pod 绑核
  • 原文地址:https://blog.csdn.net/qq_19891197/article/details/139482436