redis和redisson实现分布式锁

redis 实现分布式锁

介绍三种方式实现分布式锁:

  1. Redis原生方式实现分布式锁

  2. Redisson实现分布式锁

  3. 注解aop的方式加Redisson实现分布式锁(推荐)

一、redis原生方式实现分布式锁

Redis 单线程

SETNX (set if not exists)

setnx key value 若key存在则添加失败,若key不存在才会添加存在

redisTempalte.setIfAbsetn(key,value,time) 设置 key 和 value 和 超时时间

try {
    // todo 业务
    
    } finally {
        // get 值,若 value 与 设置的value相等 则 删除key
        
    }

简单的实现分布式锁:

// 这里使用到了 springboot集成Redis  用到了springboot的 StringRedisTemplate
@GetMapping("/transaction")
    public String transaction() {
        // 锁的键值
        String lockKey = "orderLock";
        // 设置客户端id
        String clientId = UUID.randomUUID().toString();
        try {
            // 设置锁    如果redis中已经存在 lockKey 则会添加失败
            Boolean result = stringRedisTemplate.opsForValue().setIfAbsent(lockKey, clientId, 30, TimeUnit.SECONDS);

            if (result == null || !result) {
                return "error";
            }
            String stock = stringRedisTemplate.opsForValue().get("stock");
            if (StrUtil.isEmpty(stock)) {
                return ">>>>>>>> 数据异常请稍后重试";
            }
            int number = Integer.parseInt(stock);
            if (number > 0) {
                Long lastNumber = stringRedisTemplate.opsForValue().decrement("stock");
                System.out.println(">>>>>>>>>>> 扣除库存成功,剩余:" + lastNumber);
            } else {
                System.out.println(">>>>>>>>>>> 扣除库存失败,库存不足");
            }
        } finally {
            // 如果客户端id相等
            if (clientId.equals(stringRedisTemplate.opsForValue().get(lockKey))) {
                stringRedisTemplate.delete(lockKey);
            }
        }
        return "success";
    }

二、Redisson实现分布式锁

1.导入jar包

<!-- redisson  -->
<dependency>
     <groupId>org.redisson</groupId>
     <artifactId>redisson</artifactId>
     <version>3.12.0</version>
</dependency>

2.设置连接配置

@Getter
@Setter
@ConfigurationProperties(prefix="redisson")
public class RedissonProperties {

    private int timeout = 3000;

    private String address;

    private String password;

    private int connectionPoolSize = 64;

    private int connectionMinimumIdleSize=10;

    private int slaveConnectionPoolSize = 250;

    private int masterConnectionPoolSize = 250;

    private String[] sentinelAddresses;

    private String masterName;

}

// 自动装配

@Configuration
@ConditionalOnClass(Config.class)
@EnableConfigurationProperties(RedissonProperties.class)
public class RedissonAutoConfiguration {


    @Autowired
    private RedissonProperties redssionProperties;

	//  import org.springframework.boot.autoconfigure.data.redis.RedisProperties;
//    @Autowired
//    private RedisProperties redisProperties;

    /**
     * 哨兵模式自动装配
     *      # 哨兵模式
     *      #redisson:
     *      #  master-name: mymaster
     *      #  password:
     *      #  sentinel-addresses: 10.47.91.83:26379,10.47.91.83:26380,10.47.91.83:26381
     * @return
     */
//    @Bean
//    @ConditionalOnProperty(name="redisson.master-name")
//    RedissonClient redissonSentinel() {
//        Config config = new Config();
//        SentinelServersConfig serverConfig = config.useSentinelServers().addSentinelAddress(redssionProperties.getSentinelAddresses())
//                .setMasterName(redssionProperties.getMasterName())
//                .setTimeout(redssionProperties.getTimeout())
//                .setMasterConnectionPoolSize(redssionProperties.getMasterConnectionPoolSize())
//                .setSlaveConnectionPoolSize(redssionProperties.getSlaveConnectionPoolSize());
//
//        if(StrUtil.isNotBlank(redssionProperties.getPassword())) {
//            serverConfig.setPassword(redssionProperties.getPassword());
//        }
//        return Redisson.create(config);
//    }

    /**
     * 单机模式自动装配
     * @return
     */
    @Bean
    @ConditionalOnProperty(name="redisson.address")
    RedissonClient redissonClient() {
        Config config = new Config();
        SingleServerConfig serverConfig = config.useSingleServer()
                .setAddress(redssionProperties.getAddress())
                .setTimeout(redssionProperties.getTimeout())
                .setDatabase(0)
                .setConnectionPoolSize(redssionProperties.getConnectionPoolSize())
                .setConnectionMinimumIdleSize(redssionProperties.getConnectionMinimumIdleSize());
        if(StrUtil.isNotBlank(redssionProperties.getPassword())) {
            serverConfig.setPassword(redssionProperties.getPassword());
        }

        return Redisson.create(config);
    }
}

/**
     *  redis集群下的配置  cluster
     * @return
     */
//    @Bean
//    public RedissonClient redisson() {
//        //redisson版本是3.5,集群的ip前面要加上“redis://”,不然会报错,3.2版本可不加
//        List<String> clusterNodes = new ArrayList<>();
//        for (int i = 0; i < redisProperties.getCluster().getNodes().size(); i++) {
//            clusterNodes.add("redis://" + redisProperties.getCluster().getNodes().get(i));
//        }
//        Config config = new Config();
//        // 采用集群模式
//        ClusterServersConfig clusterServersConfig = config.useClusterServers()
//                .addNodeAddress(clusterNodes.toArray(new String[clusterNodes.size()]));
//        //设置密码  注意这里集群的 redis 密码都必须一致
//        clusterServersConfig.setPassword(redisProperties.getPassword());
//        return Redisson.create(config);
//    }

yaml中的配置文件

# 哨兵模式
#redisson:
#  master-name: mymaster
#  password:
#  sentinel-addresses: 10.47.91.83:26379,10.47.91.83:26380,10.47.91.83:26381


# redis单机模式
redisson:
  address: redis://81.69.43.66:6379
  password: root

3.编写代码实现分布式锁

// 实现分布式锁

@GetMapping("/redisson")
    public String redisson() {
        String lockKey = "orderLock";
        // 获取锁
        RLock lock = redissonClient.getLock(lockKey);
        try {
            // 加锁
            lock.lock();

            String stock = stringRedisTemplate.opsForValue().get("stock");
            if (StrUtil.isEmpty(stock)) {
                return ">>>>>>>> 数据异常请稍后重试";
            }
            int number = Integer.parseInt(stock);
            if (number > 0) {
                Long lastNumber = stringRedisTemplate.opsForValue().decrement("stock");
                System.out.println(">>>>>>>>>>> 扣除库存成功,剩余:" + lastNumber);
            } else {
                System.out.println(">>>>>>>>>>> 扣除库存失败,库存不足");
            }

        } finally {
            System.out.println(">>>>>>>>>>>>>>>>> 解除锁定");
            // 解锁
            lock.unlock();
        }
        return "success";
    }

三、注解aop的方式加Redisson实现分布式锁(推荐)

// 定义注解类

@Target({ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface LockAction {

    /**
     * 锁的名称
     */
    String value() default "";

    /**
     * 描述
     */
    String description() default "";

    /**
     * 等待锁超时时间,默认 30
     * @return
     */
    long waitTime() default 30;

    /**
     *  自动解锁时间,自动解锁时间一定得大于方法执行时间,否则会导致锁提前释放,默认100
     * @return
     */
    long leaseTime() default 100;

    /**
     * 时间单位,默认为 秒
     * @return
     */
    TimeUnit timeUnit() default TimeUnit.SECONDS;

    /**
     *  默认:可重入锁
     * @return
     */
    LockTypeEnum lockType() default LockTypeEnum.REENTRANT_LOCK;

}

// 定义AOP

@Slf4j
@Aspect
@Component
@Order(1)   //order越小越是最先执行,但更重要的是最先执行的最后结束。order默认值是2147483647
public class LockAspect implements ApplicationContextAware {

    private final String LOCK_CODE = "lockCode";

    private final RedissonClient redissonClient;

    private ApplicationContext applicationContext;

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

    /**
     *  定义公共的切点
     */
    @Pointcut("@annotation(LockAction)")
    public void log(){

    }

    /**
     *  环绕通知
     * @param joinPoint
     * @return
     * @throws Throwable
     */
    @Around(value = "@annotation(lockAction)")
    public Object around(ProceedingJoinPoint joinPoint,LockAction lockAction) throws Throwable {

        String code = IdUtil.fastSimpleUUID();
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder
                .getRequestAttributes();
        HttpServletRequest request = Objects.requireNonNull(attributes).getRequest();
        String remoteUser = request.getRemoteUser();
        log.info(">>>>>>>>>>>> remoteUser 远程用户:{}",remoteUser);
        String requestURI = request.getRequestURI();
        log.info(">>>>>>>>>>>> requestURI 请求地址:{}",requestURI);
        request.setAttribute(LOCK_CODE, code);

        RLock lock = redissonClient.getLock(lockAction.value());
        Object proceed = null;
        try {
        // lock.lock();  // 这种方式最稳妥,框架自动续期
        // 采用自定义时间的方式,注意时间长短
            boolean b = lock.tryLock(lockAction.waitTime(), lockAction.leaseTime(), lockAction.timeUnit());
            if (b) {
                // 类似于  method.invoke 方法
                System.out.println(">>>>>>>>>>>>>>>>>>> 加锁成功");
                proceed = joinPoint.proceed();
            }
        } finally {
            lock.unlock();
            System.out.println(">>>>>>>>>>>>>>>>>>> 解锁成功");
        }

        // 第一个参数
//        String seckillId = joinPoint.getArgs()[0].toString();
        // 方法名
//        String name = joinPoint.getSignature().getName();
//        Class declaringType = joinPoint.getSignature().getDeclaringType();
//        Method[] methods = declaringType.getMethods();
//        Object proceed = null;
//        for (Method method : methods) {
//            if(name.equals(method.getName())){
//                // 获得方法上面的注解
//                String value = AnnotationUtil
//                        .getAnnotationValue(method, LockAction.class, "value");
//                if (StrUtil.isEmpty(value)){
//                    throw new RuntimeException("运行时发生异常");
//                }
//                // 获取锁
//                RLock lock = redissonClient.getLock(value);
//                try {
//                    // 加锁,加锁结果
//                    lock.lock();
//                    System.out.println(">>>>>>>>>>>>>>>>>>> 枷锁成功");
//                    // 类似于  method.invoke 方法
//                    proceed = joinPoint.proceed();
//                } finally {
//                    System.out.println(">>>>>>>>>>>>>>>>  解除锁定");
//                    lock.unlock();
//                }
//            }
//        }
        return proceed;
    }

    /**
     *  异常通知,当目标方法抛出异常时,该方法会被触发
     * @param joinPoint
     */
    @AfterThrowing(value = "log()",throwing = "e")
    public void afterThrowing(JoinPoint joinPoint, Exception e){
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = Objects.requireNonNull(attributes).getRequest();
        String code = (String) request.getAttribute(LOCK_CODE);
        log.info(">>>>>>>>>>  错误码:{}", code);
    }


    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }
}

// 分布式锁 注解实战使用

 @LockAction(value = "lockey")
    @GetMapping("/order/{parameter}")
    public String order(@PathVariable("parameter") String parameter) {
        String stock = stringRedisTemplate.opsForValue().get("stock");
        int number = Integer.parseInt(stock);
        if (number > 0) {
            Long lastNumber = stringRedisTemplate.opsForValue().decrement("stock");
            System.out.println(">>>>>>>>>>> 扣除库存成功,剩余:" + lastNumber);
        } else {
            System.out.println(">>>>>>>>>>> 扣除库存失败,库存不足");
        }
        return "success";
    }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值