结论:库存与Redis同步的核心是通过双写+异步队列机制实现。订单创建时,先扣减数据库库存,再写Redis库存;同时异步推送变更到Redis队列,确保Redis实时更新。最终一致性由定时任务兜底校准,避免超卖。代码示例:public void deductStock(Order order) { // 数据库扣库存 boolean success = stockMapper.updateStock(order.getSkuId(), order.getNum()) > 0; if (success) { redisTemplate.opsForValue().decrement("stock:" + order.getSkuId(), order.getNum()); rabbitTemplate.convertAndSend("stock.queue", order); } }
方案一:双写一致性
库存扣减时,先更新MySQL库存表,如果成功,再更新Redis库存key。如果Redis更新失败,不影响业务,但会产生不一致。通过异步任务,每5分钟对比MySQL和Redis库存差值,进行补齐。这样既保证了实时性,也保障了最终一致性。
方案二: Canal + Redis Binlog
使用Canal监听MySQL binlog,库存表变更时,Canal解析binlog并推送到Redis。配置Canal server连接MySQL,订阅库存表。Java客户端接收事件:@CanalEventListener public void onEvent(BinlogEntry event) { if (event.getTableName().equals("stock")) { redisTemplate.opsForValue().set("stock:" + event.getColumn("id").getValue(), event.getColumn("count").getValue()); } } 实现了数据实时同步。
方案三:消息队列异步同步
库存变更后,发送RocketMQ消息到同步队列。消费者消费消息更新Redis。支持重试和死信队列,避免消息丢失。代码:@RocketMQMessageListener(topic = "stock-update") public class StockSyncListener implements RocketMQListener
方案四:Redis Lua脚本原子扣减
使用Lua脚本实现库存扣减原子性:local stock = redis.call('get', KEYS[1]) if stock and tonumber(stock) >= tonumber(ARGV[1]) then redis.call('set', KEYS[1], stock - ARV[1]) return 1 else return 0 end 结合数据库后置校验,双重保障不超卖。
方案五:分布式锁 + 预减库存
秒杀场景用Redisson分布式锁:RLock lock = redisson.getLock("lock:" + skuId); lock.lock(); try { Long stock = redisTemplate.opsForValue().decrement("stock:" + skuId); if (stock < 0) { redisTemplate.opsForValue().increment("stock:" + skuId); throw new Exception(); } // 异步更新DB } 防止并发超卖。
一致性保障最佳实践
1. 读Redis多级缓存,穿透查DB;2. 写DB后异步更新Redis;3. MQ可靠消息确保不丢;4. 定时对账任务兜底;5. 热点key本地缓存降Redis压力。这样库存实时更新,最终强一致。
FAQ
Q: Redis和DB不一致怎么处理?
A: 异步队列补齐 + 定时任务对账,每分钟运行。
Q: 高并发秒杀怎么防超卖?
A: Redis Lua原子扣减 + DB后校验 + 分布式锁。
Q: Redis宕机库存怎么保证?
A: 降级读DB,全链路监控自动切换。
Q: 怎么监控同步延迟?
A: 埋点记录时间差,Prometheus告警超过1s。