全局ID生成器:
全局ID生成器:是一种再分布式系统下生成全局唯一的ID的工具。唯一性,高可用,高性能,安全性,递增性。
封装工具类生成全局ID:
@Component
public class RedisIDWorker {
@Resource
StringRedisTemplate stringRedisTemplate;
//2024年3月19日的时间戳
private static final long BEGIN_TIME = 1710806400l;
//时间戳的位数
private static final int COUNT_BITS=32;
public long nextId(String keyPrefix){
//获取当前时间戳
LocalDateTime now = LocalDateTime.now();
long curSecond = now.toEpochSecond(ZoneOffset.UTC);
long timeStamp = curSecond - BEGIN_TIME;
//生成序列号
//获取当前日期,精确到天
String date = now.format(DateTimeFormatter.ofPattern("yyyy:MM:dd"));
//每天以不同的key做自增,避免超过2^32次方,获取序列号
long count = stringRedisTemplate.opsForValue().increment("icr:" + keyPrefix + ":" + date);
//拼接时间戳和序列号返回
return timeStamp<<COUNT_BITS|count;
}
}
测试工具类:
@Resource
RedisIDWorker redisIDWorker;
private ExecutorService es= Executors.newFixedThreadPool(500);
@Test
void testIDWorker() throws InterruptedException {
CountDownLatch latch=new CountDownLatch(300);
Runnable task=()->{
for (int i = 0; i < 100; i++) {
long id= redisIDWorker.nextId("order");
System.out.println("id:"+id);
}
latch.countDown();
};
long start = System.currentTimeMillis();
for (int i = 0; i < 300; i++) {
es.submit(task);
}
latch.await();
long end = System.currentTimeMillis();
System.out.println("time:"+(end-start));
}
CountDownLatch详解:
ExecutorService详解:
全局唯一ID生成策略:
- UUID
- Redis自增
- snowflake算法
- 数据库自增。
redis自增ID策略:
- 每天一个key,方便统计订单量
- ID的构造是:时间戳+计数器。
实现优惠劵的秒杀下单:
下单时需要判断的两点:
- 秒杀是否开始或结束,如果未开始或已结束则无法下单
- 库存是否充足,不足则无法下单。
优惠劵下单功能实现:
@Autowired
ISeckillVoucherService iSeckillVoucherService;
@Autowired
RedisIDWorker redisIDWorker;
@Override
@Transactional
public Result seckillOrder(Long voucherId) {
//查询当前优惠劵信息
SeckillVoucher voucher = iSeckillVoucherService.getById(voucherId);
if (voucher.getBeginTime().isAfter(LocalDateTime.now())) {
return Result.fail("秒杀尚未开始");
}
if (voucher.getEndTime().isBefore(LocalDateTime.now())) {
return Result.fail("秒杀已经结束");
}
//判断库存是否足够
Integer stock = voucher.getStock();
if (stock<1){
return Result.fail("库存不足");
}
//扣减库存
boolean flag = iSeckillVoucherService.update().setSql("stock=stock-1").eq("voucher_id", voucherId).update();
//判断扣减库存是否失败
if (!flag) {
return Result.fail("库存不足");
}
//扣减库存成功,生成订单
VoucherOrder voucherOrder=new VoucherOrder();
//订单id,使用全局ID生成器
long orderId = redisIDWorker.nextId("order");
voucherOrder.setId(orderId);
//下单人id
voucherOrder.setVoucherId(UserHolder.getUser().getId());
//优惠卷id
voucherOrder.setVoucherId(voucherId);
//保存订单到数据库
save(voucherOrder);
return Result.ok(orderId);
}
//扣减库存
boolean flag = iSeckillVoucherService.update().setSql("stock=stock-1").eq("voucher_id", voucherId).update();
修改sql语句为,使用CAS方式实现乐观锁。在更新库存时查询库存是否已经被修改。
boolean flag = iSeckillVoucherService.update() .setSql("stock=stock-1").eq("voucher_id", voucherId) .eq("stock",voucher.getStock()) .update();
以上方案的失败率高,当100个线程查询到stock为100,然后一个线程修改了stock,其他线程就全部失败,因此需要改为,当stock>0即可。
即:
boolean flag = iSeckillVoucherService.update() .setSql("stock=stock-1").eq("voucher_id", voucherId) .gt("stock",voucher.getStock()) .update();
或者也可以使用分段锁提高成功率。