黑马点评03
黑马点评03:优惠劵秒杀
全局唯一ID
秒杀业务中,数据库自增 ID 不够用:
- *多台服务器同时处理请求 → 每台数据库自增 ID 可能冲突*
- *数据量极大(千万、亿级)→ 自增 ID 容易暴露业务量*
- *分库分表后,单表自增 ID 无法保证全局不重复*
基于 Redis 的全局唯一 ID 生成策略:
采用 时间戳差值(高位) + 自增序列(低位) 的方式,通过 Redis 的 INCR 命令每天重置序列号,保证 ID 在分布式环境下唯一且趋势递增。相比数据库自增,性能更高、支持分库分表;相比雪花算法,实现更简单、无时钟回拨风险(依赖 Redis 时间)。
1 | // 配置常量 |
乐观锁解决库存超卖

1 | sql |
一人一单
执行流程
1 | 请求进来 |

为了能在内存中极速完成校验,Redis 中专门设计了两个 key:
- 库存 Key (String 类型):
seckill:stock:{voucherId},存储当前优惠券的剩余库存量。在秒杀开始前,系统会提前将 MySQL 中的库存量“预热”(加载)到 Redis 中。 - 记录购买者的 Key (Set 类型):
seckill:order:{voucherId},这是一个哈希集合。里面专门存放所有成功抢到该优惠券的userId。
*用 Lua 脚本,是因为它既能保证*“检查库存 + 扣库存 + 记录用户”*这三步的原子性,又能做条件判断(库存不足就返回失败),而且性能极高,不会让线程阻塞等待。Redis 事务做不了条件判断,分布式锁性能差且会阻塞。Lua 脚本是秒杀场景下唯一正确的选择。*
1 | -- 1. 接收 Java 客户端传入的参数 |
private static final DefaultRedisScript<Long> SECKILL_SCRIPT ;
//静态代码块进行初始化
static {
SECKILL_SCRIPT = new DefaultRedisScript<>();
SECKILL_SCRIPT.setLocation(new ClassPathResource("seckill.lua"));
SECKILL_SCRIPT.setResultType(Long.class);
}
/**
* 下单秒杀优惠卷
* @param voucherId 优惠券id
* @return
*/
public Result seckillVoucher(Long voucherId){
//获取用户id
Long userId = UserHolder.getUser().getId();
//1.执行lua脚本
Long result=stringRedisTemplate.execute(
SECKILL_SCRIPT
, Collections.emptyList()
, voucherId.toString(),userId.toString() );
//判断结果是否为0
int r=result.intValue();
//不为0,则代表没有购买资格
if (r!=0){
return Result.fail(r==1?"库存不足":"不能重复下单");
}
kafka
代码里生产者在这里:
1 | kafkaTemplate.send( |
也就是把 VoucherOrder 转成 JSON,发送到 Kafka 的 voucher.orders topic。这里的 key 是 voucherId,value 是订单 JSON。
消费者在这里:
1 | @KafkaListener(topics = VOUCHER_ORDER_TOPIC, groupId = "voucher-order-group") |
它会监听 voucher.orders 这个 topic,一旦有订单消息,就反序列化成 VoucherOrder,然后调用 createVoucherOrder() 创建订单。
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来源 Ling的个人博客!