Redis实践篇(二)优惠卷秒杀 一人一单、分布锁

目录

 全局ID生成器​编辑

实现优惠卷下单

优惠卷超卖问题

乐观锁

 一人一单

分布式锁

分布锁的实现

 基于Redis的分布锁

 Redis的Lua脚本

 再次改进Redis的分布锁

基于Redis的分布锁优化

Redisson分布式框架

 引入依赖

Redisson可重入锁原理

 Redisson分布锁原理​编辑


Redis实践篇(二)优惠卷秒杀 一人一单、分布锁_第1张图片

 全局ID生成器Redis实践篇(二)优惠卷秒杀 一人一单、分布锁_第2张图片

Redis实践篇(二)优惠卷秒杀 一人一单、分布锁_第3张图片

Redis实践篇(二)优惠卷秒杀 一人一单、分布锁_第4张图片

 Redis实践篇(二)优惠卷秒杀 一人一单、分布锁_第5张图片


@Component
public class RedisIdWorker {

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

    /**
     * 序列号的位数
     */
    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获取当前日期,精确到天
        String date = now.format(DateTimeFormatter.ofPattern("yyyyMMdd"));
        //2.2自增长
        Long count = stringRedisTemplate.opsForValue().increment("icr:" + keyPrefix + ":" + date);


        //3.拼接并返回
        return timestamp << COUNT_BITS | count;
    }


}

实现优惠卷下单

Redis实践篇(二)优惠卷秒杀 一人一单、分布锁_第6张图片

 Redis实践篇(二)优惠卷秒杀 一人一单、分布锁_第7张图片

优惠卷超卖问题

Redis实践篇(二)优惠卷秒杀 一人一单、分布锁_第8张图片

 Redis实践篇(二)优惠卷秒杀 一人一单、分布锁_第9张图片

乐观锁

Redis实践篇(二)优惠卷秒杀 一人一单、分布锁_第10张图片

 Redis实践篇(二)优惠卷秒杀 一人一单、分布锁_第11张图片

 Redis实践篇(二)优惠卷秒杀 一人一单、分布锁_第12张图片

 一人一单

Redis实践篇(二)优惠卷秒杀 一人一单、分布锁_第13张图片

 Redis实践篇(二)优惠卷秒杀 一人一单、分布锁_第14张图片

 


@Service
public class VoucherOrderServiceImpl extends ServiceImpl implements IVoucherOrderService {

    @Resource
    private ISeckillVoucherService seckillVoucherService;

    @Resource
    private RedisIdWorker redisIdWorker;

    @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 ("秒杀已结束!");
        }
        //4.判断库存是否充足
        if ( voucher.getStock()<1 ) {
            //库存不足
            return Result.fail("库存不足");
        }
        Long userId = UserHolder.getUser().getId();
        synchronized(userId.toString().intern()) {//intern返回字符串的规范表示
            //获取代理对象(事务)事务生效
            //获取锁之后再创建事务
            IVoucherOrderService proxy = (IVoucherOrderService)AopContext.currentProxy();
            //事务提交完再释放锁
            return proxy.createVoucherOrder(voucherId);//事务能够
            //可以避免事务没提交就释放锁的安全问题
        }
    }

    @Transactional//加入事务
    public   Result createVoucherOrder(Long voucherId) {
        //TODO 6.一人一单
        Long userId = UserHolder.getUser().getId();
//        userId.toString()底层代码是创建对象,所以有可能还是不一样的
//        synchronized(userId.toString().intern()) {//intern返回字符串的规范表示


            //TODO 6.1查询订单
            Integer count = query().eq("user_id", userId).eq("voucher_id", voucherId).count();
            //TODO 6.2判断是否存在
            if ( count > 0 ) {
                //用户已经买过了
                return Result.fail("用户已经买过了一次了");
            }


            //5.扣减库存
            boolean sucess = seckillVoucherService.update()
                    .setSql("stock = stock-1")//set stock = stock-1
                    .gt("stock", "0")//可以解决超卖 where id ?and stock >0
                    .eq("voucher_id", voucherId).update();//
//                .eq("stock",voucher.getStock()).update();//where id ?and stock = ?

            if ( !sucess ) {
                //扣减不足
                return Result.fail("库存不足");

            }


            //6.创建订单
            VoucherOrder voucherOrder = new VoucherOrder();
            //6.1订单di
            long orderId = redisIdWorker.nextId("order");
            voucherOrder.setId(orderId);
            //6.2用户id
//        Long userId = UserHolder.getUser().getId();
            voucherOrder.setUserId(userId);
            //6.3代金卷id
            voucherOrder.setVoucherId(voucherId);
            save(voucherOrder);
            //7.返回订单
            return Result.ok(orderId);

    }
}

Redis实践篇(二)优惠卷秒杀 一人一单、分布锁_第15张图片

Redis实践篇(二)优惠卷秒杀 一人一单、分布锁_第16张图片

Redis实践篇(二)优惠卷秒杀 一人一单、分布锁_第17张图片

分布式锁

Redis实践篇(二)优惠卷秒杀 一人一单、分布锁_第18张图片

 Redis实践篇(二)优惠卷秒杀 一人一单、分布锁_第19张图片

分布锁的实现

Redis实践篇(二)优惠卷秒杀 一人一单、分布锁_第20张图片

 基于Redis的分布锁

Redis实践篇(二)优惠卷秒杀 一人一单、分布锁_第21张图片

Redis实践篇(二)优惠卷秒杀 一人一单、分布锁_第22张图片

      //TODO 分布锁
        //TODO 创建锁对象
        SimpleRedisLock lock = new SimpleRedisLock("order" + userId, stringRedisTemplate);
        //获取锁
        boolean isLock = lock.tryLock(1200);
        if ( !isLock ){
            //获取锁失败,返回错误或重试
            return Result.fail("不允许重复下单");

        }
        try {
            //获取代理对象(事务)事务生效
            //TODO 获取锁之后再创建事务
            IVoucherOrderService proxy = (IVoucherOrderService)AopContext.currentProxy();
            //TODO 事务提交完再释放锁
            return proxy.createVoucherOrder(voucherId);//事务能够
            //TODO 可以避免事务没提交就释放锁的安全问题
        } finally {
            //释放锁
            lock.unlock();
        }
//        }

public class SimpleRedisLock implements ILock{

    private String name;
    private StringRedisTemplate stringRedisTemplate;

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

    private static  final String KEY_PREFIX="lock:";
    @Override
    public boolean tryLock(long timeoutSec) {
       //获取线程标识
        long threadId = Thread.currentThread().getId();
        String key = KEY_PREFIX + name;
        //获取锁
        Boolean success = stringRedisTemplate.opsForValue()
                .setIfAbsent(key, threadId + "", timeoutSec, TimeUnit.SECONDS);

        return Boolean.TRUE.equals(success);
//        //防止拆箱
//        return BooleanUtil.isTrue(success);
    }

    @Override
    public void unlock() {
        //释放锁
        stringRedisTemplate.delete(KEY_PREFIX+name);
    }
}

Redis实践篇(二)优惠卷秒杀 一人一单、分布锁_第23张图片

Redis实践篇(二)优惠卷秒杀 一人一单、分布锁_第24张图片

Redis实践篇(二)优惠卷秒杀 一人一单、分布锁_第25张图片

 Redis实践篇(二)优惠卷秒杀 一人一单、分布锁_第26张图片

 Redis的Lua脚本

Redis实践篇(二)优惠卷秒杀 一人一单、分布锁_第27张图片

Redis实践篇(二)优惠卷秒杀 一人一单、分布锁_第28张图片

Redis实践篇(二)优惠卷秒杀 一人一单、分布锁_第29张图片

 再次改进Redis的分布锁

Redis实践篇(二)优惠卷秒杀 一人一单、分布锁_第30张图片

 Redis实践篇(二)优惠卷秒杀 一人一单、分布锁_第31张图片

基于Redis的分布锁优化

Redis实践篇(二)优惠卷秒杀 一人一单、分布锁_第32张图片

Redisson分布式框架

 Redis实践篇(二)优惠卷秒杀 一人一单、分布锁_第33张图片

 引入依赖

Redis实践篇(二)优惠卷秒杀 一人一单、分布锁_第34张图片

Redis实践篇(二)优惠卷秒杀 一人一单、分布锁_第35张图片

    @Resource
    private RedissonClient redissonClient;

   //TODO 分布锁
        //TODO 创建锁对象
//        SimpleRedisLock lock = new SimpleRedisLock("order" + userId, stringRedisTemplate);
//        TODO 使用工具Redisson
        RLock lock = redissonClient.getLock("lock:order" + userId);
        //获取锁
        //TODO
        boolean isLock = lock.tryLock();
        if ( !isLock ){
            //获取锁失败,返回错误或重试
            return Result.fail("不允许重复下单");

        }
        try {
            //获取代理对象(事务)事务生效
            //TODO 获取锁之后再创建事务
            IVoucherOrderService proxy = (IVoucherOrderService)AopContext.currentProxy();
            //TODO 事务提交完再释放锁
            return proxy.createVoucherOrder(voucherId);//事务能够
            //TODO 可以避免事务没提交就释放锁的安全问题
        } finally {
            //释放锁
            lock.unlock();
        }
//        }

Redisson可重入锁原理

Redis实践篇(二)优惠卷秒杀 一人一单、分布锁_第36张图片

Redis实践篇(二)优惠卷秒杀 一人一单、分布锁_第37张图片

Redis实践篇(二)优惠卷秒杀 一人一单、分布锁_第38张图片

 Redisson分布锁原理Redis实践篇(二)优惠卷秒杀 一人一单、分布锁_第39张图片

leaseTime:释放时间

 Redis实践篇(二)优惠卷秒杀 一人一单、分布锁_第40张图片

Redisson分布锁主从一致性问题

Redis实践篇(二)优惠卷秒杀 一人一单、分布锁_第41张图片

Redis实践篇(二)优惠卷秒杀 一人一单、分布锁_第42张图片

Redis实践篇(二)优惠卷秒杀 一人一单、分布锁_第43张图片

 Redis实践篇(二)优惠卷秒杀 一人一单、分布锁_第44张图片

你可能感兴趣的:(Redis,redis,数据库,缓存)