一、为什么票务系统重度依赖Redis?
做景区票务系统的同学都知道,这个场景对Redis的依赖非常重。原因很简单:
- 节假日开园瞬间,数万游客同时抢票
- 闸机每秒钟要校验成百上千次
- 订单库存扣减必须原子性,不能超卖
- 多渠道库存要保持一致
如果所有请求都直接打MySQL数据库,哪怕是再强的RDS实例也扛不住。我们从第一天起就把阿里云Redis作为核心缓存层,下面分享一下这几年踩过的坑和设计经验。
二、我们的Redis使用场景
2.1 门票库存扣减
这是最核心的场景。热门门票只剩几百张,瞬间涌入几千个请求,怎么保证不超卖?
我们的方案是:
- 开园前把所有票种库存预热到Redis
- 用户下单时先执行DECR扣减库存
- 扣减成功(返回值>=0)才创建订单
- 支付超时未付的订单,通过定时任务回补库存
这里用的是阿里云Redis的单线程原子性,DECR操作天然线程安全,不需要额外加锁。
2.2 闸机检票缓存
游客刷二维码过闸机的时候,每刷一次就要校验一次票是否有效。如果每次都查数据库,闸机高峰期直接把RDS打挂。
我们把所有已售出门票的有效性状态都存在Redis里:
- key是门票二维码ID
- value是检票状态(未检票/已检票)
- TTL设为门票有效期
闸机校验直接查Redis,平均响应时间不到1ms,高峰期QPS上万也稳得住。
2.3 分布式锁
多渠道库存同步的时候,我们用Redis分布式锁保证同一时刻只有一个请求能操作某个票种的库存:
- SETNX key value NX EX 10
- 操作完成后DEL释放锁
这个方案简单粗暴,但非常有效。
三、踩过的坑和教训
3.1 坑一:缓存穿透
现象:有段时间发现Redis内存占用很高,但命中率很低,大量请求都穿透到了数据库。
原因:有人恶意刷不存在的票种ID,每次请求Redis都miss,然后都去查MySQL。
解决方案:
- 对查询结果为空的key也缓存一个空值,TTL设60秒
- 给票种ID加布隆过滤器,不存在的ID直接拦截
改完之后,Redis命中率从70%提升到了99%以上。
3.2 坑二:缓存雪崩
现象:某天一早就收到告警,Redis突然大量key同时过期,所有请求瞬间都打到RDS上,数据库CPU直接100%。
原因:我们当初给所有缓存key都设了相同的TTL(30分钟),结果某一批key同时过期。
解决方案:
- 给TTL加随机偏移量,比如基础30分钟+随机0-5分钟
- 热点数据永不过期,通过后台异步更新
- 给RDS加限流,防止被打挂
3.3 坑三:大key问题
现象:发现Redis偶尔会卡顿,响应时间从1ms涨到几十ms。
原因:有个运营同学把某个景区的所有票种信息都存在了一个Hash里,里面有几千个field,每次HGETALL都要拉几MB数据。
解决方案:
- 拆key,按票种ID拆分
- 用阿里云Redis的大key分析工具定期扫描
- 制定规范:单个key的value不能超过10KB
3.4 坑四:库存回补不及时
现象:发现有些票明明显示有库存,但用户下单的时候提示没票了。
原因:支付超时的订单回补库存是用定时任务每分钟扫一次,高峰期积累了太多未支付订单,回补速度跟不上。
解决方案:
- 用Redis的过期监听(Keyspace Notifications),订单key过期的时候主动回补库存
- 配合延迟队列,下单后15分钟未支付自动取消
四、阿里云Redis的选型建议
我们用过好几种规格的Redis,分享一下选型经验:
- 开发测试环境:用1G主从版就够了,便宜
- 生产环境:至少用2G集群版,支持读写分离
- 核心业务:用8G以上的标准版,多副本高可用
- 注意:一定要开启持久化,阿里云Redis默认开启RDB+AOF双持久化,不怕丢数据
成本方面,我们目前用的是4G集群版,每月费用大概300多块钱,比自己搭Redis集群省心多了,而且稳定性有保障。
五、总结
Redis看起来简单,但真用到高并发业务里,坑真的不少。我们总结几条核心经验:
- 缓存不是银弹:一定要想清楚哪些数据该缓存,哪些不该
- TTL一定要加随机值:避免雪崩
- 空值也要缓存:防止穿透
- 大key要及时拆:不然迟早出问题
- 用好云厂商的监控:阿里云Redis自带命中率、延迟、大key分析,不用自己搭监控
做高并发系统,缓存设计是基本功。一个设计良好的Redis缓存层,能把数据库压力降低90%以上,是系统稳定的基石。后续我们还在尝试用阿里云Redis的GEO特性做附近景区推荐,有机会再分享。