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

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

相关推荐
BestAns8 小时前
一文带你吃透 Java 反射机制
java·后端
wasp5208 小时前
AgentScope Java 核心架构深度解析
java·开发语言·人工智能·架构·agentscope
2501_916766548 小时前
【Springboot】数据层开发-数据源自动管理
java·spring boot·后端
自在极意功。8 小时前
MyBatis 动态 SQL 详解:从基础到进阶实战
java·数据库·mybatis·动态sql
软件管理系统8 小时前
基于Spring Boot的便民维修管理系统
java·spring boot·后端
百***78759 小时前
Step-Audio-2 轻量化接入全流程详解
android·java·gpt·php·llama
快乐肚皮9 小时前
MySQL递归CTE
java·数据库·mysql·递归表达式
tap.AI9 小时前
(三)Stable Diffusion 3.5 与 ComfyUI
分布式·stable diffusion
廋到被风吹走9 小时前
【Spring】DispatcherServlet解析
java·后端·spring