Spring Boot + Kafka + Redis 实现电商秒杀系统:高并发场景下的技术深度解析

发布时间:2026/7/24 6:25:19

Spring Boot + Kafka + Redis 实现电商秒杀系统:高并发场景下的技术深度解析 Spring Boot Kafka Redis 实现电商秒杀系统高并发场景下的技术深度解析一、业务背景与技术挑战在大型电商平台如双11中秒杀活动是典型的高并发、低延迟、强一致性的业务场景。瞬时流量可达数十万QPS而库存仅数百件。若处理不当将导致超卖、数据库雪崩、服务宕机等问题。核心挑战包括超卖问题多个请求同时读取库存为1均判断可扣减导致库存变为-1数据库压力直接穿透到MySQL单库难以承载峰值写入响应延迟同步扣减DB写入耗时长用户感知卡顿服务可用性热点商品请求集中易引发线程池打满、OOM。二、整体架构设计采用分层解耦架构用户请求 → Nginx → Spring Boot网关 → Redis缓存校验 → Kafka异步下单 → MySQL最终落库接入层Nginx限流漏桶算法、前端按钮防重点击服务层Spring Boot 3.2 Jakarta EE 9基于Spring WebFlux非阻塞模型提升吞吐缓存层Redis Cluster Lua脚本实现原子扣减与分布式锁消息层Kafka 3.6seckill-order-topic分区数32启用幂等生产者与事务存储层MySQL 8.0 ShardingSphere分库分表库存字段使用version乐观锁。三、关键技术实现与代码案例3.1 Redis预减库存Lua脚本保证原子性// RedisTemplate执行Lua脚本 String script local stock redis.call(GET, KEYS[1])\n if not stock or tonumber(stock) 0 then\n return -1\n end\n local result redis.call(DECR, KEYS[1])\n if result 0 then\n redis.call(INCR, KEYS[1])\n return -1\n end\n return result; Long result redisTemplate.execute( new DefaultRedisScript(script, Long.class), Collections.singletonList(seckill:stock: skuId), new Object[]{});✅ 原子性保障避免先GET再DECR的竞态条件 ❌ 禁用Cacheable注解因缓存穿透风险必须显式控制缓存生命周期。3.2 Kafka异步下单解耦库存校验与订单生成// 生产者发送SeckillOrderEvent事件 kafkaTemplate.send(seckill-order-topic, SeckillOrderEvent.builder() .orderId(UUID.randomUUID().toString()) .skuId(skuId) .userId(userId) .timestamp(System.currentTimeMillis()) .build()); // 消费者KafkaListener(topics seckill-order-topic) Transactional // 在消费者端开启本地事务确保DB写入与offset提交原子性 public void onOrderEvent(SeckillOrderEvent event) { // 1. 再次校验库存防缓存失效/网络重试 if (!redisTemplate.hasKey(seckill:stock: event.getSkuId())) { throw new RuntimeException(库存已售罄); } // 2. 插入订单MySQL orderMapper.insert(toOrderPO(event)); // 3. 扣减最终库存乐观锁 int updated stockMapper.decrStockWithVersion(event.getSkuId(), 1); if (updated 0) { throw new RuntimeException(超卖异常版本号不匹配); } }3.3 分布式锁优化Redisson RLock// 防止缓存击穿对空库存结果也加锁 RLock lock redissonClient.getLock(seckill:lock: skuId); try { if (lock.tryLock(3, 10, TimeUnit.SECONDS)) { // 查询DB确认库存若为0则写入空值缓存Cache Aside Pattern Integer dbStock stockMapper.selectStock(skuId); if (dbStock null || dbStock 0) { redisTemplate.opsForValue().set( seckill:stock: skuId, 0, 2, TimeUnit.MINUTES); } } } finally { if (lock.isHeldByCurrentThread()) lock.unlock(); }四、监控与可观测性Spring Boot Actuator Micrometer Prometheus自定义MeterRegistry统计秒杀成功率Counter.builder(seckill.attempt) .tag(result, success) .register(meterRegistry);Grafana看板配置实时QPS、Redis命中率、Kafka消费延迟、MySQL慢SQL TOP10。五、面试官视角为什么这样设计Q1为何不用MySQL行锁解决超卖 AInnoDB行锁在高并发下会升级为表锁且锁等待导致RT飙升Redis内存操作微秒级性能提升百倍。Q2Kafka消息丢失如何保证 A生产者设置acksallretriesInteger.MAX_VALUE消费者手动提交offsetenable.auto.commitfalse配合DLQ死信队列兜底。Q3如果Redis集群故障如何降级 A通过Sentinel监听Redis状态自动切换至本地Caffeine缓存TTL1sMySQL悲观锁兜底牺牲一致性保可用性。【小白学习指南】业务场景与技术点总结| 场景环节 | 技术点 | 学习要点 | |----------------|-------------------------|--------------------------------------------------------------------------| | 用户抢购入口 | Spring WebFlux Reactor | 非阻塞IO模型对比Servlet容器理解背压Backpressure机制 | | 库存预校验 | Redis Lua脚本 | 为什么不能用MULTI/EXEC——Lua在Redis单线程内原子执行避免网络往返开销 | | 异步下单 | Kafka事务 幂等生产者 |transaction.id如何保证Exactly-Once语义需配合Consumer端isolation.levelread_committed| | 最终一致性 | Saga模式补偿事务 | 若订单插入成功但库存扣减失败触发CancelOrderSaga回滚订单需设计反向SQL | | 容灾降级 | Sentinel熔断规则 | 配置QPS阈值5000超阈值自动返回HTTP 429 Too Many Requests并记录日志 | 进阶思考结合Resilience4j实现RateLimiter与CircuitBreaker组合策略用OpenFeign调用风控服务实时拦截黑产IP。最后面试官微笑合上简历“方案很扎实细节也考虑周全。我们会在3个工作日内通知您后续流程请保持手机畅通。” 文章配套GitHub仓库https://github.com/yourname/seckill-demo 含Docker Compose一键部署KafkaRedisMySQL

相关新闻