Redis实现购物秒杀及分布式锁
全局唯一ID
Redis自增ID策略
ID构造是:时间戳 + 计数器
每天一个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;
}
实现秒杀下单
秒杀下单逻辑流程
业务实现
@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, "库存不足");}// 库存 - 1pro.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个线程进行测试
20个库存卖了210个订单
原因分析
锁的类型
超卖问题是典型的多线程安全问题,针对这一问题的常见解决方案就是加锁
悲观锁:认为线程安全问题一定会发生,因此在操作数据之前先获取锁,确保线程串行执行。
- 例如Synchronized、Lock都属于悲观锁
乐观锁:认为线程安全问题不一定会发生,因此不加锁,只是在更新数据时去判断有没有其它线程对数据做了修改。
- 如果没有修改则认为是安全的,自己才更新数据。
- 如果已经被其它线程修改说明发生了安全问题,此时可以重试或异常。
乐观锁
乐观锁的关键是判断之前查询得到的数据是否有被修改过。(CAS法)