文章目录
1. 秒杀
1.1 全局唯一ID
全局ID生成器,是一种在分布式系统下用来生成全局唯一ID的工具,一般要满足下列特性:
唯一性、高性能、安全性、递增性、高可用
为了使ID更加安全,不会直接使用Redis自增的数值,而是拼接其他信息
成部分:符号位:1bit,永远为0
时间戳:31bit,以秒为单位,可以使用69年
序列号:32bit,秒内的计数器,支持每秒产生2^32个不同ID
1.2 使用Redis生成全局唯一Id(使用Redis实现ID自增长 increment(key))
@Component
public class RedisIdWorker {
private static final long BEGIN_TIMESTAMP = 1640995200L; //设置开始时间戳
private static final int COUNT_BITS = 32;//序列号位数
//使用构造方法注入Redis
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.生成序列号
String date = now.format(DateTimeFormatter.ofPattern("yyyy:MM:dd"));// 获取当前日期,精确到天(格式化)
long count = stringRedisTemplate.opsForValue().increment("icr:" + keyPrefix + ":" + date); // 使用Redis实现ID自增长 increment(key)
// 3.拼接并返回 timestamp左移32位,再与count相与 有1则1
return timestamp << COUNT_BITS | count;
}
}
2. 解决优惠券超卖问题
2.0 悲观锁和乐观锁
针对这一问题的常见解决方案就是加锁:而对于加锁,我们通常有两种解决方案
- 悲观锁:任务线程安全问题一定会发生,在操作数据之前先获取锁,确保线程串行执行
- 例如:Synchronized、Lock都属于悲观锁
- 优点:简单粗暴
- 缺点:性能一般
- 例如:Synchronized、Lock都属于悲观锁
- 乐观锁:任务线程安全问题不一定发生,只是在更新数据时去判断有没有其他线程对数据进行了更改,针对可能发生的问题去加锁
- 如果没有修改则任务是安全的,自己才能更新数据
- 如果已经被其他线程修改说明发生了安全问题,此时可以重试或抛异常
- 优点:性能好
- 缺点:存在成功率低问题,并给数据库加大了压力
2.1 分析超卖问题原因
源码如下:
if (voucher.getStock() < 1) { // 库存不足
return Result.fail("库存不足!");
}
boolean success = seckillVoucherService.update() //5,扣减库存
.setSql("stock= stock -1")
.eq("voucher_id", voucherId).update();
if (!success) { //判断扣减库存是否成功
return Result.fail("库存不足!");
}
假设线程1过来查询库存,判断出来库存大于1,正准备去扣减库存,但是还没有来得及去扣减,此时线程2过来,线程2也去查询库存,发现这个数量一定也大于1,那么这两个线程都会去扣减库存,最终多个线程相当于一起去扣减库存,此时就会出现库存的超卖问题。
2.2 使用乐观锁解决超卖问题
有两种方式:
- 版本号法
- CAS法