黑马redis实战-优惠券秒杀

视频地址

1. 全局唯一 ID

1. 1 问题:针对优惠券秒杀模块全局唯一 ID 的作用是什么?

作为优惠券订单 id
在这里插入图片描述

1.2 问题:为什么不使用数据库自增 ID ?

考虑分布式场景下ID的全局唯一性

分库分表后,id主键如何处理

① 数据库自增 id:先从一个数据库表中获取自增id,再根据该 id 往对应的分库分表中写入
问题:生成id的数据库表高并发瓶颈
适用场景:并发不高,数据量太大导致的分库分表

② uuid:UUID.randomUUID()
问题:不适用于实际的业务需求,生成的订单号UUID字符串看不出与订单相关的有用信息。长字符串【存储性能差,查询耗时】

③ 获取系统当前时间:系统时间作为主键
问题:秒级并发时,会有重复情况
适用场景:业务字段与当前时间拼接,组成全局唯一编号

④ redis:redis 的 incr 实现 ID 原子性自增
问题:redis 持久化过程中出现宕机时,RDB持久化会出现重复 id 的情况,AOF 持久化会导致重启恢复数据时间过长 【问题:什么原因导致了两者的差异?】

⑤ 雪花算法:时间戳 + 机器 id + 序列号
问题:强依赖机器时钟

视频中采用 redis 生成全局唯一 id

1.3 如何利用 redis 生成全局唯一 id

① 业务名称前缀 + 当前日期 作为 redis 自增长参数 key

//2.2 自增长
long count = stringRedisTemplate.opsForValue().increment("icr:" + keyPrefix + ":" + date);

② 全局唯一id = 当前时间戳【高 32 位】+ redis 自增长序列【低 32 位】

//3.拼接并返回
private static final int COUNT_BITS = 32;
return timestamp << COUNT_BITS | count;

完整代码:

@Component
public class RedisIdWorker {
   
   
    // 开始时间戳
    private static final long BEGIN_TIMESTAMP = 1648857600L;
	// 序列号的位数
    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("yyyy:MM:dd"));

        //2.2 自增长:redis 自增长key: 前缀 + 当前日期
        long count = stringRedisTemplate.opsForValue().increment("icr:" + keyPrefix + ":" + date);
        //3.拼接并返回
        return timestamp << COUNT_BITS | count;
    }
}

实现效果:
在这里插入图片描述
实现效果分析:高 32 位为 时间戳,低 32 位为 redis 中精确到 天的记录。同一天的记录可以根据时间戳前缀唯一标识,同时在redis 中可以直观看到与相关业务逻辑以及日期相关的信息

2. 优惠券秒杀下单

流程说明:
① 判断秒杀是否开始或结束,若尚未开始或已经结束则无法下单

② 库存是否充足,不足则无法下单

2.1 问题:如何实现优惠券下单

① 扣减库存

// 5.扣减库存
boolean success = seckillVoucherService.update()
        .setSql("stock = stock - 1")
        .eq("voucher_id", voucherId).update();

② 创建订单并保存到数据库

// 6.创建订单
VoucherOrder voucherOrder = new VoucherOrder();
// 6.1. 订单id
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);
2.2 如何添加优惠券?

使用 postman添加优惠券:如下图所示,在 2 号商铺中添加优惠券
在这里插入图片描述

2.3 添加的优惠券是如何保存到数据库的?

通过 postman 添加优惠券后
在这里插入图片描述
会将优惠券信息同时写入 tb_voucher 和 tb_seckill_voucher 表中
在这里插入图片描述
tb_voucher :记录优惠券的店铺,描述,面值等信息

tb_seckill_voucher :记录优惠券秒杀开始,结束时间以及库存

实现过程
① 通过 postman 发送请求到 /voucher/seckill
在这里插入图片描述
请求被分发到 Controller 层的 addSeckillVoucher(@RequestBody Voucher voucher)

@RestController
@RequestMapping("/voucher")
public class VoucherController {
   
   
    /**
     * 新增秒杀券
     * @param voucher 优惠券信息,包含秒杀信息
     * @return 优惠券id
     */
    @PostMapping("seckill")
    public Result addSeckillVoucher(@RequestBody Voucher voucher) {
   
   
        voucherService.addSeckillVoucher(voucher);
        return Result.ok(voucher.getId());
    }
}

② Service 层 VoucherServiceImpl.java 中的 addSeckillVouocher(Voucher voucher) 实现保存秒杀券信息到数据库中

@Service
public class VoucherServiceImpl extends ServiceImpl<VoucherMapper, Voucher> implements IVoucherService {
   
   

    @Resource
    private ISeckillVoucherService seckillVoucherService;
    @Override
    @Transactional
    public void addSeckillVoucher(Voucher voucher) {
   
   
        // 保存优惠券
        save(voucher);
        // 保存秒杀信息
        SeckillVoucher seckillVoucher = new SeckillVoucher();
        seckillVoucher.setVoucherId(voucher.getId());
        seckillVoucher.setStock(voucher.getStock());
        seckillVoucher.setBeginTime(voucher.getBeginTime());
        seckillVoucher.setEndTime(voucher.getEndTime());
        seckillVoucherService.save(seckillVoucher);
}

优惠券秒杀核心代码:

// VoucherOrderServiceImpl.java
@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(
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值