全局ID生成器在分布式系统下用来生成全局唯一ID的工具唯一性高可用递增性安全性高性能MySQL自增 ID 缺陷1ID 可被预测2单库自增 ID 有性能上限高并发场景扛不住3分库分表自增 ID 会重复全局 ID符号位(1 bit)时间戳(31 bit)序列号(32 bit)ComponentpublicclassRedisIdWorker{//开始时间戳privatestaticfinallongBEGIN_TIMESTAMP1640995200L;//序列号位数privatestaticfinalintCOUNT_BITS32;privateStringRedisTemplatestringRedisTemplate;publicRedisIdWorker(StringRedisTemplatestringRedisTemplate){this.stringRedisTemplatestringRedisTemplate;}publiclongnextId(StringkeyPrefix){//这个参数是业务前缀// 1.生成时间戳LocalDateTimenowLocalDateTime.now();longnowSecondnow.toEpochSecond(ZoneOffset.UTC);longtimestampnowSecond-BEGIN_TIMESTAMP;// 2.生成序列号// 2.1.获取当前日期精确到天Stringdatenow.format(DateTimeFormatter.ofPattern(yyyy:MM:dd));// 2.2.自增长longcountstringRedisTemplate.opsForValue().increment(icr:keyPrefix:date);// 3.拼接并返回returntimestampCOUNT_BITS|count;}}1,每个业务用自己的自增序列2INCR icr:order:2026:06:01如果这个key不存在创建它值设置为1每天自增都会清0防止redis内存过大UUID雪花算法优惠券下单表现层PostMapping(seckill)publicResultaddSeckillVoucher(RequestBodyVouchervoucher){voucherService.addSeckillVoucher(voucher);returnResult.ok(voucher.getId());}其中 voucher 是这个优惠券实体类优惠券分为普通优惠券和秒杀优惠券TableField(existfalse)privateIntegerstock;这个字段在 Java 类里有但 tb_voucher 表里没有对应的列。SeckillVoucherTableId(valuevoucher_id,typeIdType.INPUT)privateLongvoucherId;把 SeckillVoucher 的字段放进 Voucher里是为了省事——前端展示优惠券时基本都要同时显示库存和时间每次手动合并太麻烦干脆在 Voucher 类里用 TableField(exist false) 带上这些字段。新增优惠券OverrideTransactionalpublicvoidaddSeckillVoucher(Vouchervoucher){// 保存优惠券save(voucher);// 保存秒杀信息SeckillVoucherseckillVouchernewSeckillVoucher();seckillVoucher.setVoucherId(voucher.getId());seckillVoucher.setStock(voucher.getStock());seckillVoucher.setBeginTime(voucher.getBeginTime());seckillVoucher.setEndTime(voucher.getEndTime());seckillVoucherService.save(seckillVoucher);// 保存秒杀库存到Redis中stringRedisTemplate.opsForValue().set(SECKILL_STOCK_KEYvoucher.getId(),voucher.getStock().toString());}}保存秒杀券的库存到缓存实现下单功能1秒杀时间是否开始或者结束2库存是否充足RestControllerRequestMapping(/voucher-order)publicclassVoucherOrderController{ResourceprivateIVoucherOrderServicevoucherOrderService;PostMapping(seckill/{id})publicResultseckillVoucher(PathVariable(id)LongvoucherId){returnvoucherOrderService.seckillVoucher(voucherId);}}略过部分简单的代码。// 5. 扣减库存booleansuccessseckillVoucherService.update().setSql(stock stock - 1)// SET stock stock - 1.eq(voucher_id,voucherId)// WHERE voucher_id ?.update();// 执行更新这是 MyBatis-Plus 提供的链式查询语法。 MyBatis-Plus 的 ServiceImpl 里继承来的 update() 方法它返回一个UpdateWrapper 对象让你可以链式拼接 SQL 条件。在 MySQL 里完成加减而不是在 Java里算好再传进去能避免并发问题超卖问题悲观锁串行执行/** * 悲观锁方式秒杀扣减库存 * 必须加 Transactional 锁和事务绑定 */TransactionalpublicResultseckillByPessimisticLock(LongvoucherId){LonguserIdUserHolder.getUser().getId();// 1.【核心】查询优惠券库存 加悲观锁 (FOR UPDATE)// 关键SQLSELECT * FROM tb_seckill_voucher WHERE voucher_id ? FOR UPDATESeckillVouchervoucherseckillVoucherService.lambdaQuery().eq(SeckillVoucher::getVoucherId,voucherId).last(FOR UPDATE)// 这行就是加悲观锁.one();// 2. 判断库存if(voucher.getStock()0){returnResult.fail(库存不足);}// 3. 扣减库存因为加了锁只有一个线程能执行到这booleansuccessseckillVoucherService.lambdaUpdate().set(SeckillVoucher::getStock,voucher.getStock()-1).eq(SeckillVoucher::getVoucherId,voucherId).update();if(!success){returnResult.fail(扣减失败);}// 4. 创建订单...returnResult.ok();}Transactional 到底是什么为什么必须加它的作用Spring 声明式事务保证方法内所有数据库操作要么全部成功要么全部失败比如扣减库存成功、创建订单失败 → 事务回滚库存恢复悲观锁必须加它的生死原因FOR UPDATE 加的锁和事务绑定事务开启 → 加锁事务执行完毕提交 / 回滚 → 自动释放锁如果不加 TransactionalSpring 不会开启事务查询完数据锁立刻释放等于没加锁Transactional 给悲观锁提供生命周期容器没有它悲观锁瞬间失效。MySQL InnoDB 引擎的行锁分为两种共享锁 (S 锁)读锁多个线程可以同时加 S 锁互不阻塞排他锁 (X 锁)写锁一个线程加了 X 锁其他线程不能加任何锁必须阻塞等待所有写操作INSERT/UPDATE/DELETE都会自动加 X 锁这是数据库的基本规则没有例外。利用MySQL的条件式乐观锁乐观锁判断之前查询的数据是否有被修改1版本号2CAS3条件式乐观锁booleansuccessseckillVoucherService.update().setSql(stock stock - 1)// 原子扣减.eq(voucher_id,voucherId).gt(stock,0)// ←这就是乐观锁.update();MySQL 执行 UPDATE 时会用行锁锁住这一行两个 UPDATE 不会同时执行会排队在高并发的场景下条件式的乐观锁比版本号性能更好能让更多线程执行一人一单同一个优惠券一个用户只能下一单通过悲观锁synchronized事务来实现TransactionalpublicResultcreateVoucherOrder(LongvoucherId){// 5.一人一单LonguserIdUserHolder.getUser().getId();synchronized(userId.toString().intern()){// 5.1.查询订单intcountquery().eq(user_id,userId).eq(voucher_id,voucherId).count();// 5.2.判断是否存在if(count0){// 用户已经购买过了returnResult.fail(用户已经购买过一次);}// 6.扣减库存booleansuccessseckillVoucherService.update().setSql(stock stock - 1)// set stock stock - 1.eq(voucher_id,voucherId).gt(stock,0)// where id ? and stock 0.update();if(!success){// 扣减失败returnResult.fail(库存不足);}// 7.创建订单VoucherOrdervoucherOrdernewVoucherOrder();// 7.1.订单idlongorderIdredisIdWorker.nextId(order);voucherOrder.setId(orderId);// 7.2.用户idvoucherOrder.setUserId(userId);// 7.3.代金券idvoucherOrder.setVoucherId(voucherId);save(voucherOrder);// 7.返回订单idreturnResult.ok(orderId);}}synchronized (userId.toString().intern()) {锁住同一个用户的对象头防止一人多单。这里有个spring代理对象没搞懂并发安全问题这个锁可以解决单机情况下的问题但是如果是集群模式下就失效了两台服务器各有一个 JVM各有各的常量池。服务器 A 的 1001和服务器 B 的 “1001” 是两个不同的对象头synchronized管不到对方。