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

由于本项目是专门学习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发送两次请求,可以发现只有有一个用户获取锁成功。

相关推荐
zl97989911 分钟前
SpringBoot-Web开发之内容协商
java·spring boot
bb456b12 分钟前
Snipaste (截图贴图工具) 精准截图 中文免费版
java·工具·贴图
or77iu_N36 分钟前
Linux 解压安装(安装tomcat)
linux·运维·tomcat
SimonKing36 分钟前
【开发者必备】Spring Boot 2.7.x:WebMvcConfigurer配置手册来了(三)!
java·后端·程序员
2301_8012522236 分钟前
前端框架Vue(Vue 的挂载点与 data 数据对象)
java·前端·javascript·vue.js·前端框架
ArabySide38 分钟前
【Spring Boot】深入浅出Spring Boot中的控制反转与依赖注入
java·spring boot·后端
shepherd11140 分钟前
破局延时任务(上):为什么选择Spring Boot + DelayQueue来自研分布式延时队列组件?
java·spring boot·后端
技术杠精1 小时前
Docker Swarm之Java 应用部署与平滑更新
java·docker·容器
beyond阿亮1 小时前
nacos支持MCP Server注册与发现
java·python·ai·nacos·mcp
王嘉俊9251 小时前
HarmonyOS 分布式与 AI 集成:构建智能协同应用的进阶实践
人工智能·分布式·harmonyos