分布式锁解决集群下一人一单超卖问题

由于本项目是专门学习Redis的,所以这里会使用Redis的setnx指令实现分布式锁解决超卖问题

创建分布式锁

java 复制代码
public class SimpleRedisLock implements ILock {

    private String name;
    private StringRedisTemplate stringRedisTemplate;
    private static final String KEY_PREFIX = "lock:";

    public SimpleRedisLock(String name, StringRedisTemplate stringRedisTemplate) {
        this.name = name;
        this.stringRedisTemplate = stringRedisTemplate;
    }

    @Override
    public boolean tryLock(long timeoutSec) {
        String key = KEY_PREFIX + name;
        String value = Thread.currentThread().getId() + "";
        Boolean res = stringRedisTemplate.opsForValue()
                .setIfAbsent(key, value, timeoutSec, TimeUnit.SECONDS);
        return Boolean.TRUE.equals(res);
    }

    @Override
    public void unlock() {
        String key = KEY_PREFIX + name;
        stringRedisTemplate.delete(key);
    }
}

使用分布式锁 :改造前面VoucherOrderServiceImpl中的代码,将之前使用sychronized锁的地方,改成自己实现的分布式锁:

java 复制代码
/**
     * 抢购秒杀券
     */
    @Override
    public Result seckillVoucher(Long voucherId) {
        // 1、查询秒杀券
        SeckillVoucher voucher = seckillVoucherService.getById(voucherId);
        // 2、判断秒杀券是否合法
        if (voucher.getBeginTime().isAfter(LocalDateTime.now())) {
            // 秒杀券的开始时间在当前时间之后
            return Result.fail("秒杀尚未开始");
        }
        if (voucher.getEndTime().isBefore(LocalDateTime.now())) {
            // 秒杀券的结束时间在当前时间之前
            return Result.fail("秒杀已结束");
        }
        // 3、判断库存是否充足
        if (voucher.getStock() < 1) {
            return Result.fail("秒杀券已抢空");
        }
        Long userId = UserHolder.getUser().getId();
        // 去字符串常量池找字符串对象,使得加锁同一个对象
        // 先获取锁,再开启事务,事务结束后,才会释放锁
        String key = "order:" + userId;
        // 锁定范围是用户ID
        SimpleRedisLock lock = new SimpleRedisLock(key, stringRedisTemplate);
        boolean isLock = lock.tryLock(1200);
        if(!isLock){
            // 获取锁失败,返回错误或重试,但此时是同一个用户并发多个请求,应该返回错误
            return Result.fail("不允许重复下单");
        }
        // 获取锁成功
        try{
            // spring的事务是基于代理对象的,这里直接调用相当于this.xxx,并非代理对象,因此事务不会生效,所以要拿到代理对象
            IVoucherOrderService proxy = (IVoucherOrderService) AopContext.currentProxy();
            return proxy.createVoucherOrder(voucherId);
        }finally {
            lock.unlock();
        }
    }

重启程序,在postman中使用同一个用户的token发送两次请求,可以发现只有有一个用户获取锁成功。

相关推荐
艾莉丝努力练剑2 小时前
【C++:异常】C++ 异常处理完全指南:从理论到实践,深入理解栈展开与最佳实践
java·开发语言·c++·安全·c++11
武子康2 小时前
Java-184 缓存实战:本地缓存 vs 分布式缓存(含 Guava/Redis 7.2)
java·redis·分布式·缓存·微服务·guava·本地缓存
小马爱打代码8 小时前
Spring Boot:模块化实战 - 保持清晰架构
java·spring boot·架构
ANYOLY8 小时前
分布式面试题库
分布式·面试·职场和发展
小坏讲微服务8 小时前
SpringBoot4.0整合knife4j 在线文档完整使用
java·spring cloud·在线文档·knife4j·文档·接口文档·swagger-ui
8***Z898 小时前
springboot 异步操作
java·spring boot·mybatis
i***13248 小时前
Spring BOOT 启动参数
java·spring boot·后端
坚持不懈的大白8 小时前
后端:SpringMVC
java
IT_Octopus8 小时前
(旧)Spring Securit 实现JWT token认证(多平台登录&部分鉴权)
java·后端·spring
kk哥88999 小时前
Spring详解
java·后端·spring