目录
当用户抢购时,就会生成订单并保存到tb_voucher_order这张表中,而订单表如果使用数据库自增ID就存在如下问题:
全局ID生成器,是一种在分布式系统下用来生成全局唯一ID的工具,已满要满足以下特性:
为了增加ID的安全性,我们不可以直接使用Redis自增数值,而是拼接一些其他信息

- @Component
- public class RedisIdWorker {
- private StringRedisTemplate stringRedisTemplate;
-
- public RedisIdWorker(StringRedisTemplate stringRedisTemplate) {
- this.stringRedisTemplate = stringRedisTemplate;
- }
-
- /**
- * 开始时间戳
- */
- private static final long BEGIN_TIMESTAMP = 1640995200L;
- /**
- * 序列号位数
- */
- private static final int COUNT_BITS = 32;
-
- 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自增长
- long count = stringRedisTemplate.opsForValue().increment("icr:" + keyPrefix + ":" + date);
- //3.拼接并返回
- return timestamp<<COUNT_BITS | count;
- }
- }
下单需要判断两点:

- @Service
- public class VoucherOrderServiceImpl extends ServiceImpl<VoucherOrderMapper, VoucherOrder> implements IVoucherOrderService {
-
- @Resource
- private ISeckillVoucherService seckillVoucherService;
- @Resource
- private RedisIdWorker redisIdWorker;
- @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("stock = stock-1").eq("voucher_id", voucherId).update();
- if (!success) {
- //扣减失败
- return Result.fail("优惠券库存不足");
- }
- //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);
- //7.返回订单
- return Result.ok(orderId);
- }
- }
为什么会出现超卖问题?高并发的情况下,会出现访问量过大,同时拿到了库存大于1的数据,多个线程交叉执行,就会出现超卖问题。
超卖问题是典型的多线程安全问题,针对这一个问题的常见解决方案是加锁:
悲观锁性能较低,这里使用乐观锁。
乐观锁的关键是判断之前查询得到的数据是否有被修改过,常见的方式有两种:
版本号发:

CAS法(先比较后设置),用库存代替版本号

乐观锁解决超卖问题存在的弊端:成功率低。因为只要判断库存不相等就执行失败,当高并发执行的时候会有大多的线程执行失败。
解决办法:不再是判断库存是否相等,而是去判断库存是否大于1。
- //5.扣减库存
- boolean success = seckillVoucherService.update()
- .setSql("stock = stock-1")//set stock = stock - 1
- .eq("voucher_id", voucherId).gt("stock",0) //where id = ? and stock >0
- .update();
需求:要求同一个优惠券,一个用户只能下一单。

为了解决并发带来的线程不安全问题,这里采用的是悲观锁,也就是加了synchronized关键字。
- 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("优惠券已经被抢完");
- }
- Long userId = UserHolder.getUser().getId();
- synchronized(userId.toString().intern()){
- IVoucherOrderService proxy = (IVoucherOrderService) AopContext.currentProxy();
- return proxy.creatVoucherOrder(voucherId);
- }
- }
-
- @Transactional
- public Result creatVoucherOrder(Long voucherId) {
- //5.一人一单
- //5.1查询用户
- Long userId = UserHolder.getUser().getId();
- //5.2查询订单
- int count = query().eq("user_id", userId).eq("voucher_id", voucherId).count();
- //5.3判断用户是否存在
- if (count > 0) {
- //用户已经购买过了
- return Result.fail("用户已经购买过了");
- }
- //6.扣减库存
- boolean success = seckillVoucherService.update()
- .setSql("stock = stock-1")//set stock = stock - 1
- .eq("voucher_id", voucherId).gt("stock",0) //where id = ? and stock >0
- .update();
- if (!success) {
- //扣减失败
- return Result.fail("优惠券库存不足");
- }
- //7.创建订单
- VoucherOrder voucherOrder = new VoucherOrder();
- //7.1订单ID
- long orderId = redisIdWorker.nextID("order:");
- voucherOrder.setId(orderId);
-
- voucherOrder.setUserId(userId);
- //7.3代金券id
- voucherOrder.setVoucherId(voucherId);
- save(voucherOrder);
- //8.返回订单
- return Result.ok(orderId);
- }
- }