Redisson入门(优惠券秒杀、分布式锁)

时间:2024-10-02 20:18:00

        <dependency>
            <groupId>org.redisson</groupId>
            <artifactId>redisson</artifactId>
            <version>3.13.6</version>
        </dependency>
@Configuration
public class RedissonConfig {

    @Bean
    public RedissonClient redissonClient() {
        //配置
        Config config = new Config();
        config.useSingleServer().setAddress("redis://127.0.0.1");
        //创建RedissonClient对象
        return Redisson.create(config);
    }
}

        RLock lock = redissonClient.getLock("lock:order" + userId);
        //获取锁,无参就失败不等待,直接返回
        boolean isLock = lock.tryLock();
        //判断是否获取锁成功
        if (!isLock) {
            //获取锁失败,返回错误或重试
            return Result.fail("一个人只允许下一单");
        }
        try {
            //获取代理对象(事务)
            IVoucherOrderService proxy = (IVoucherOrderService) AopContext.currentProxy();
            return proxy.createVoucherOrder(voucherId);
        } finally {
            lock.unlock();
        }
 优惠券秒杀、分布式锁逻辑层代码(乐观锁、悲观锁)实现了一人一单
@Service
public class VoucherOrderServiceImpl extends ServiceImpl<VoucherOrderMapper, VoucherOrder> implements IVoucherOrderService {

    @Resource
    private ISeckillVoucherService seckillVoucherService;
    @Resource
    private SeckillVoucherMapper seckillVoucherMapper;
    @Resource
    private VoucherOrderMapper voucherOrderMapper;
    @Resource
    private RedisIdWorker redisIdWorker;
    @Resource
    private StringRedisTemplate stringRedisTemplate;

    @Resource
    private RedissonClient redissonClient;
    @Override
    public Result seckillVoucher(Long voucherId) {
        //1.查询秒杀优惠卷
        SeckillVoucher voucher = seckillVoucherService.getById(voucherId);
        //2.判断秒杀是否开始
        if (voucher.getBeginTime().isAfter(LocalDateTime.now())) {
            return Result.fail("秒杀尚未开始");
        }
        //3.判断秒杀是否结束
        if (voucher.getEndTime().isBefore(LocalDateTime.now())) {
            return Result.fail("秒杀已经结束");
        }
        //判断库存是否充足
        if (voucher.getStock() <= 0) {
            return Result.fail("库存不足");
        }
        Long userId = UserHolder.getUser().getId();
        //确保当用户id值一样时,就用一把锁,不同的用户用不同的锁
        //确保获取锁,提交事务,释放锁,线程就安全了
        //创建锁对象
//        SimpleRedisLock lock = new SimpleRedisLock("order:" + userId, stringRedisTemplate);
        RLock lock = redissonClient.getLock("lock:order" + userId);
        //获取锁,无参就失败不等待,直接返回
        boolean isLock = lock.tryLock();
        //判断是否获取锁成功
        if (!isLock) {
            //获取锁失败,返回错误或重试
            return Result.fail("一个人只允许下一单");
        }
        try {
            //获取代理对象(事务)
            IVoucherOrderService proxy = (IVoucherOrderService) AopContext.currentProxy();
            return proxy.createVoucherOrder(voucherId);
        } finally {
            lock.unlock();
        }
    }

    @Transactional
    public Result createVoucherOrder(Long voucherId) {
        //查询数据库有没有数据时,不能用乐观锁,需用悲观锁
        //6.一人一单 判断订单表中是否存在用户对应某优惠券的单条记录
        //6.1 查询订单
        Long userId = UserHolder.getUser().getId();
        QueryWrapper<VoucherOrder> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("user_id",userId).eq("voucher_id", voucherId);
        Integer count = voucherOrderMapper.selectCount(queryWrapper);
        //6.2 判断是否存在
        if(count > 0){
            //已经购买过一次
            return Result.fail("用户已经购买过一次");
        }
        //扣减库存
//        boolean success = seckillVoucherService.update()
//                .setSql("stock = stock - 1")
//                .eq("voucher_id", voucherId).update();

        UpdateWrapper<SeckillVoucher> updateWrapper = new UpdateWrapper<>();
        /**
         * 使用乐观锁,乐观锁是在更新数据时使用
         * 为了防止超卖,应该设置查询时的票数等于自己准备修改时的票数,此时会出现少卖问题,
         *         所以直接改为当修改时票数大于0,即可修改
         */
        updateWrapper.eq("voucher_id", voucherId).gt("stock", 0);
        updateWrapper.setSql("stock = stock - 1");
        int success = seckillVoucherMapper.update(null, updateWrapper);
        if (success == 0) {
            return Result.fail("库存不足");
        }
        //没有买过就创建订单
        VoucherOrder voucherOrder = new VoucherOrder();
        //6.1订单id
        long orderId = redisIdWorker.nextId("order");
        voucherOrder.setId(orderId);
        //6.2用户id

        voucherOrder.setUserId(userId);
        //6.3代金券id
        voucherOrder.setVoucherId(voucherId);
        voucherOrderMapper.insert(voucherOrder);
        //返回订单id
        return Result.ok(orderId);
    }
}

里面使用到了代理,需引入pom.xml 和 启动类上加注解

        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
        </dependency>




@EnableAspectJAutoProxy(exposeProxy = true)
public interface IVoucherOrderService extends IService<VoucherOrder> {

    Result seckillVoucher(Long voucherId);

    Result createVoucherOrder(Long voucherId);
}
RedisIdWorker工具类(唯一ID生成器)
@Component
public class RedisIdWorker {

    //开始时间戳
    private static final long BEGIN_TIMESTAMP = 1704067200L;

    //序列号的位数
    private static final int COUNT_BITS = 32;
    private StringRedisTemplate stringRedisTemplate;

    public RedisIdWorker(StringRedisTemplate stringRedisTemplate) {
        this.stringRedisTemplate = stringRedisTemplate;
    }

    public long nextId(String keyPrefix){
        //1.生成当前时间的时间戳
        LocalDateTime now = LocalDateTime.now();
        long nowSecond = now.toEpochSecond(ZoneOffset.UTC);
        long timestamp = nowSecond - BEGIN_TIMESTAMP;
        //2.生成序列号
        //2.1 获取当前日期,精确到天,每天一个key,方便统计订单量
        String date = now.format(DateTimeFormatter.ofPattern("yyyy:MM:dd"));
        //2.2自增长
        Long count = stringRedisTemplate.opsForValue().increment("icr" + keyPrefix + ":" + date);
        //3.拼接并返回  时间戳放左边 序列号放右边  将时间戳右移
        // |count 时间戳右移32位后,右边32位全为0,或之后,原来是什么就是什么
        return timestamp << COUNT_BITS | count;
    }

    //用于生成开始时间的时间戳
//    public static void main(String[] args) {
//        LocalDateTime time = LocalDateTime.of(2024, 1, 1, 0, 0, 0);
//        long second = time.toEpochSecond(ZoneOffset.UTC); //将当前时间转换为秒数
//        System.out.println(second);
//    }
}