【Redis】实现购物秒杀及分布式锁

Redis实现购物秒杀及分布式锁

全局唯一ID

Redis自增ID策略

ID构造是:时间戳 + 计数器

【Redis】实现购物秒杀及分布式锁_第1张图片

每天一个key,方便统计订单量

业务实现

获取指定时间的秒数

LocalDateTime timeBegin = LocalDateTime.of(2024, 1, 1, 0, 0, 0);
long second = timeBegin.toEpochSecond(ZoneOffset.UTC);

获取当前时间的秒数

long now = LocalDateTime.now().toEpochSecond(ZoneOffset.UTC);

全局唯一ID业务代码

public Long getID(String key) {
   
    // 获取时间戳
    long now = LocalDateTime.now().toEpochSecond(ZoneOffset.UTC);
    long timestamp = now - TIMESTAMP_BEGIN;

    // 利用redis实现自增长
    String date = new SimpleDateFormat("yyyyMMdd").format(new Date());
    Long inc = redisTemplate.opsForValue().increment("ID:" + key + ":" + date);

    // 拼接并返回
    return timestamp << 32 | inc;
}

实现秒杀下单

秒杀下单逻辑流程

【Redis】实现购物秒杀及分布式锁_第2张图片

业务实现

存在
不存在
充足
不足
begin
判断库存
判断存在
end
提交商品id
返回异常
扣减库存
创建订单
返回订单id
@Transactional
public String getProduct(Long id) {
   
    // 判断商品是否存在
    Product pro = getById(id);
    if (pro == null) {
   
        throw new BusinessException(400, "商品不存在");
    }
    // 判断库存
    int num = pro.getNum();
    if (num <= 0) {
   
        throw new BusinessException(400, "库存不足");
    }
    // 库存 - 1
    pro.setNum(num - 1);
    update(pro, new QueryWrapper<>());
    
    // 下订单
    Ordertable order = new Ordertable();
    order.setOrderID(idUtil.getID("order").toString());
    order.setUserID(123);
    ordertableService.save(order);
    return order.getOrderID();
}

“超卖”问题

问题复现

在多线程并发会产生问题

使用5000个线程进行测试

【Redis】实现购物秒杀及分布式锁_第3张图片

20个库存卖了210个订单

【Redis】实现购物秒杀及分布式锁_第4张图片

原因分析

【Redis】实现购物秒杀及分布式锁_第5张图片

锁的类型

超卖问题是典型的多线程安全问题,针对这一问题的常见解决方案就是加锁

悲观锁:认为线程安全问题一定会发生,因此在操作数据之前先获取锁,确保线程串行执行。

  • 例如Synchronized、Lock都属于悲观锁

乐观锁:认为线程安全问题不一定会发生,因此不加锁,只是在更新数据时去判断有没有其它线程对数据做了修改。

  • 如果没有修改则认为是安全的,自己才更新数据。
  • 如果已经被其它线程修改说明发生了安全问题,此时可以重试或异常。

乐观锁

乐观锁的关键是判断之前查询得到的数据是否有被修改过。(CAS法)

【Redis】实现购物秒杀及分布式锁_第6张图片

代码实现

你可能感兴趣的:(redis,分布式,wpf)