1. 面试场景还原与技术挑战拆解去年冬天的一次技术面试让我记忆犹新面试官推了推眼镜问道如果让你设计一个秒杀系统怎么保证100万QPS下不超卖这个问题看似简单却直接戳中了分布式系统的三大痛点原子性操作、资源竞争和最终一致性。当时我给出的方案虽然通过了面试但在实际生产环境中又经历了多次迭代优化。秒杀系统的核心矛盾在于瞬时流量往往是日常流量的百倍以上而库存数量可能只有几十个。这就引出了几个关键技术点Redis的原子计数器实现精确扣减分布式锁控制并发写操作异步消息保证最终一致性多级缓存缓解数据库压力重要提示真正的秒杀系统设计不能只考虑不超卖还需要考虑恶意请求拦截、库存预热、失败补偿等完整链路。我曾见过一个案例由于没有做好限流瞬间流量直接打挂了Nginx。2. Redis原子扣减的六种实现方案对比2.1 基础版INCRBY/DECRBY最简单的原子操作适合计数器场景Long remaining redisTemplate.opsForValue() .decrement(stock:product_123, quantity); if (remaining 0) { // 回滚操作 redisTemplate.opsForValue() .increment(stock:product_123, quantity); throw new RuntimeException(库存不足); }缺陷无法实现扣减后比较的复合操作可能出现负数。2.2 进阶版Lua脚本通过Lua保证多个操作的原子性local stock tonumber(redis.call(GET, KEYS[1])) if stock tonumber(ARGV[1]) then return redis.call(DECRBY, KEYS[1], ARGV[1]) else return -1 end实测性能单节点可达5万TPS是Java客户端的3倍效率。2.3 生产级方案Redis模块阿里云Redis企业版提供的TairString支持直接CAS操作String result redisTemplate.execute( new TairStringScriptExecutorString(script), Collections.singletonList(key), String.valueOf(deductAmount) );优势内置版本号控制避免ABA问题。方案对比表方案TPS一致性实现复杂度适用场景INCRBY10万弱低计数场景Lua脚本5万强中通用方案Redis模块8万强高金融级3. 分布式锁的陷阱与Redisson最佳实践3.1 经典错误案例我曾见过一个团队自己实现的分布式锁// 错误示范 Boolean locked redisTemplate.opsForValue() .setIfAbsent(lock:order, 1, 10, TimeUnit.SECONDS); if (locked) { try { // 业务逻辑 } finally { redisTemplate.delete(lock:order); } }这个实现有四个致命缺陷锁过期时间与业务执行时间无法对齐删除锁时可能误删其他线程的锁单点Redis故障时锁失效不可重入3.2 Redisson解决方案Redisson的分布式锁实现堪称教科书级别RLock lock redissonClient.getLock(orderLock); try { // 支持自动续期 if (lock.tryLock(5, 30, TimeUnit.SECONDS)) { // 业务逻辑 } } finally { if (lock.isHeldByCurrentThread()) { lock.unlock(); } }核心机制Watch Dog自动续期默认30秒检测一次线程标识防止误删红锁算法RedLock应对单点故障支持公平锁、读写锁等变种踩坑记录曾经因为没检查lock.isHeldByCurrentThread()导致在finally块中误删锁引发连锁雪崩。建议在关键操作处添加ThreadLocal标记。4. 消息可靠性的四重保障体系4.1 消息丢失防护RabbitMQ的解决方案// 发送端确认 channel.confirmSelect(); channel.addConfirmListener((sequenceNumber, multiple) - { // 消息落地DB }); // 消费端手动ACK channel.basicConsume(queue, false, deliverCallback, cancelCallback);4.2 消息幂等处理Redis数据库事务方案START TRANSACTION; -- 先查防重表 SELECT id FROM deduplication WHERE msg_id123 FOR UPDATE; -- 不存在则插入 INSERT INTO deduplication VALUES (123); -- 业务操作 UPDATE stock SET countcount-1 WHERE product_id100; COMMIT;4.3 延迟消息设计RocketMQ的延迟队列Message message new Message(); message.setDelayTimeLevel(3); // 对应10s延迟4.4 补偿对账机制定时任务扫描待处理订单Scheduled(cron 0 0/5 * * * ?) public void checkTimeoutOrders() { ListOrder orders orderMapper.selectTimeoutOrders(); orders.forEach(order - { // 恢复库存 redisTemplate.opsForValue() .increment(stock:order.getProductId()); // 更新订单状态 order.setStatus(OrderStatus.CANCELED); orderMapper.update(order); }); }5. 秒杀系统完整架构设计5.1 流量漏斗模型前端限流按钮置灰随机延迟网关层令牌桶算法RateLimiter服务层Semaphore信号量控制数据层Redis集群分片5.2 热点数据优化本地缓存Redis多级架构用户请求 → 本地缓存 → Redis主从 → DB采用一致性哈希减少缓存抖动。5.3 压测数据对比某电商平台优化前后对比指标优化前优化后QPS2万35万平均耗时450ms28ms超卖率0.3%0%服务器成本50台12台关键配置参数# Redis连接池 spring.redis.lettuce.pool.max-active500 # Tomcat线程池 server.tomcat.max-threads800 # 熔断配置 resilience4j.circuitbreaker.instances.stock.failure-rate-threshold506. 面试官最爱问的刁钻问题6.1 Redis事务与MySQL事务的区别Redis事务实质是命令批量执行没有隔离级别概念不存在回滚即使命令出错也会继续执行。而MySQL事务满足ACID特性支持回滚和多种隔离级别。6.2 为什么不用ZK做分布式锁虽然ZK通过临时节点和Watcher机制能实现分布式锁但在高并发场景下性能只有Redis的1/10网络分区时可能产生脑裂需要维护长连接增加开销6.3 如何设计一个分布式ID生成器综合方案对比// Snowflake实现示例 public synchronized long nextId() { long timestamp timeGen(); if (timestamp lastTimestamp) { throw new RuntimeException(时钟回拨); } if (lastTimestamp timestamp) { sequence (sequence 1) sequenceMask; if (sequence 0) { timestamp tilNextMillis(lastTimestamp); } } else { sequence 0L; } lastTimestamp timestamp; return ((timestamp - twepoch) timestampLeftShift) | (datacenterId datacenterIdShift) | (workerId workerIdShift) | sequence; }7. 生产环境中的血泪教训缓存击穿某次大促热点商品缓存失效导致直接打到DB。解决方案采用互斥锁重建缓存public Product getProduct(String id) { Product product getFromCache(id); if (product null) { RLock lock redisson.getLock(product_lock: id); try { lock.lock(); // 双重检查 product getFromCache(id); if (product null) { product loadFromDB(id); putToCache(id, product); } } finally { lock.unlock(); } } return product; }库存不一致由于缓存与DB不一致导致超卖。最终采用异步校对方案Scheduled(fixedDelay 60000) public void syncStock() { MapObject, Object cacheStocks redisTemplate.opsForHash() .entries(product_stocks); cacheStocks.forEach((productId, stock) - { int dbStock productDao.getStock((String)productId); if (Integer.parseInt(stock.toString()) ! dbStock) { // 触发告警并修复 } }); }消息堆积订单取消消息处理不及时导致库存未及时释放。解决方案增加消费者数量设置消息TTL死信队列特殊处理在真实系统中没有银弹方案。最近我们在测试环境引入Jepsen进行混沌测试发现即使RedLock在特定网络分区场景下也会出现锁失效。最终采用锁本地事务异步校对的多重保障机制。技术选型永远是在一致性和性能之间寻找平衡点而这正是分布式系统的魅力所在。 SEO 优化官网定制响应式建站教育培训建站