黑马Redis实战项目——黑马点评笔记04 | 优惠券秒杀

黑马Redis实战项目——黑马点评笔记04 | 优惠券秒杀

在这里插入图片描述


1、redis应用场景一:全局唯一ID

问题:
在这里插入图片描述

1.1 生成策略

在这里插入图片描述
在这里插入图片描述

1.2 实践

@Component
public class RedisIdWorker {
   
    /**
     * 开始时间戳
     */
    private static final long BEGIN_TIMESTAMP = 1672531200;
    /**
     * 序列号位数
     */
    private static final int COUNT_BITS = 32;

    @Resource
    private StringRedisTemplate stringRedisTemplate;

    /**
     * ID自动生成器并返回
     *
     * @param keyPrefix 业务前缀
     * @return
     */
    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("yyyy:MM:dd"));
        //2.2 自增长
        long count = stringRedisTemplate.opsForValue().increment("icr" + keyPrefix + ":" + date);

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

    public static void main(String[] args) {
   
        LocalDateTime time = LocalDateTime.of(2023, 1, 1, 0, 0, 0);
        long second = time.toEpochSecond(ZoneOffset.UTC);
        System.out.println("second:" + second);
    }
}

1.3 总结

在这里插入图片描述


2、优惠券秒杀下单

2.1 流程分析

在这里插入图片描述使用postman添加秒杀券
在这里插入图片描述

//秒杀券信息
{
   
    "shopId": 1,
    "title": "100元代金券",
    "subTitle": "周一到周日均可使用",
    "rules": "全场通用\\n无需预约\\n可无限叠加\\不兑现、不找零\\n仅限堂食",
    "payValue": 8000,
    "actualValue": 10000,
    "type": 1,
    "stock": 100,
    "beginTime":"2023-04-18T15:40:00",
    "endTime":"2023-04-18T23:40:00"
}

在这里插入图片描述

2.2 代码实现

在这里插入图片描述

//VoucherOrderController类中
	@PostMapping("seckill/{id}")
    public Result seckillVoucher(@PathVariable("id") Long voucherId) {
   
        return voucherOrderService.seckillVoucher(voucherId);
    }
//IVoucherOrderService接口中声明方法
    Result seckillVoucher(Long voucherId);
@Service
public class VoucherOrderServiceImpl extends ServiceImpl<VoucherOrderMapper, VoucherOrder> implements IVoucherOrderService {
   

    @Resource
    private ISeckillVoucherService seckillVoucherService;
    @Resource
    private RedisIdWorker redisIdWorker;

    @Override
    @Transactional
    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("库存不足!");
        }
        //5 扣减库存
        boolean success = seckillVoucherService.update()
                .setSql("stock = stock - 1")
                .eq("voucher_id",voucherId).update();
        if(!success){
   
            //扣减失败
            return Result.fail("库存不足!");
        }
        //6 创建订单
        VoucherOrder voucherOrder = new VoucherOrder();
        //6.1 订单id
        long orderId = redisIdWorker.nextId("order");
        voucherOrder.setId(orderId);
        //6.2 用户id
        voucherOrder.setUserId(UserHolder.getUser().getId());
        //6.3 代金券id
        voucherOrder.setVoucherId(voucherId);
        save(voucherOrder);
        //7 返回订单id
        return Result.ok(orderId);
    }
}

3、超卖问题

3.1 原因分析

在这里插入图片描述

3.2 解决方案选择:悲观锁or乐观锁

在这里插入图片描述

3.3 乐观锁实现

方案一:版本号法

在这里插入图片描述

方案二:CAS法

在这里插入图片描述

CAS法代码实现

在VoucherOrderServiceImpl类的seckillVoucher方法中修改操作数据库条件

		//5 扣减库存
        boolean success = seckillVoucherService.update()
                .setSql("stock = stock - 1") //set stock=stock-1
                .eq("voucher_id",voucherId).eq("stock",voucher.getStock()) //where id=? and stock=?
                .update();

改进,提高成功率

		//5 扣减库存
        boolean success = seckillVoucherService.update()
                .setSql("stock = stock - 1") //set stock=stock-1
                .eq("voucher_id",voucherId).gt("stock",0) //where id=? and stock>0
                .update();

3.4 线程安全总结

在这里插入图片描述


4、一人一单

4.1 实现流程

在这里插入图片描述

4.2 代码实现

这个实现过程比较复杂,包含spring事务失效、aop代理对象、synchronized锁等知识点,可以多看几遍。

@Service
public class VoucherOrderServiceImpl extends ServiceImpl<VoucherOrderMapper, VoucherOrder> 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("库存不足!");
        }
        //synchronized悲观锁,给用户加锁
        //要在事务外层加锁,因为要在事务提交之后释放锁,才能确保线程安全
        Long userId = UserHolder.getUser().getId();
        synchronized(userId.toString().intern()){
   
            //使用代理对象的createVouterOrder方法才能开启事务
            //获取代理对象
            IVoucherOrderService proxy = (IVoucherOrderService) AopContext.currentProxy();
            return proxy.createVouterOrder(voucherId);
        }
    
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值