当前位置:首页 > 问答 > 正文

用Redis应对高并发面试那些坑和心得分享,聊聊真实体验

那次面试我记得特别清楚,面试官是个挺有经验的技术负责人,他没问我Redis的五种数据结构,也没让我手写LRU算法,上来就直接抛场景:“我们有个核心业务,高峰时段并发量很高,用Redis做库存扣减,遇到过超卖的情况,如果是你,怎么排查和解决?”

我当时心里一紧,这问题太实在了,就是实际开发里真会踩的坑,我赶紧把之前项目里遇到的和他分享,我说,首先最基础的,肯定得用Redis的原子操作,比如用DECR命令来减库存,或者用Lua脚本把“判断库存”和“扣减”这两个操作绑在一起,确保原子性,这个算是教科书答案了,但光这个不够。

我接着说,更隐蔽的一个坑是“网络和超时”,应用服务器A发起了DECR命令,Redis也执行成功了,但返回结果的那个网络包在中间丢掉了,或者刚好碰上Full GC,应用服务器没收到成功响应,它可能以为扣减失败了,然后给用户返回“库存不足”,但实际上库存已经少了,这就产生了少卖,反过来也一样,如果网络延迟,应用服务器可能会因为等不及响应而超时,然后重试,导致一次请求扣了两次库存,这就超卖了,解决这个,不能光靠Redis,得在应用层加逻辑,比如给每个扣减请求一个唯一ID,在Redis里记一下这个ID是否已经处理过,做到幂等性。

面试官点点头,又问:“那如果Redis突然挂了呢?或者整个机房网络出问题了,你的库存数据怎么办?”

这问题问到根子上了,我说,这就涉及到数据持久化和高可用方案的选择了,光用RDB快照不行,万一刚做完快照,还没到下一次快照Redis宕机了,那期间的库存变更就全丢了,恢复后库存会变多,商家得亏死,用AOF的话,如果配置为每秒同步一次,最多丢一秒的数据,但对于抢购场景,一秒的订单量也可能是巨大的,最保险是配成每次写操作都同步,但那样性能代价太高,可能根本扛不住高并发。

真正的解决方案不是追求单机Redis的绝对不丢数据,而是靠架构,我当时项目里的做法是,用Redis Cluster做分片,保证容量和性能,同时主从异步复制,我们心里清楚,极端情况下会丢一点数据,所以不能完全依赖Redis做最终准确性的保证,我们会把每一次扣减请求,在发往Redis的同时,也异步地发送一份消息到Kafka这样的可靠消息队列里,然后有一个单独的后台服务,从Kafka里消费这些消息,慢慢地、稳妥地持久化到数据库里,这样,即使Redis临时挂了,或者有点数据不一致,我们最终也能以数据库为准进行对账和修复,说白了,Redis在这里的角色是“高速缓存”,负责扛住瞬间的洪峰流量,而把保证数据最终一致性的重任交给更稳妥的数据库和消息队列体系。

我还补充了一个很容易被忽略的点:热Key和大Key,有一次我们做活动,有个爆款商品,它的库存Key就是典型的热Key,所有请求都打到一个Redis节点上,虽然我们用了集群,但这个分片差点被打挂,后来我们提前做了准备,对这种已知的热门商品,在应用层就用随机数把库存拆分成好几个Key,比如stock_product_01, stock_product_02...,扣减的时候随机选一个来操作,把压力分散开,大Key也一样,比如用Hash存一个商品的很多属性,如果这个商品属性极其多,每次读取整个Hash都会很慢,容易阻塞,我们的心得是,设计Key的时候就要有意识地避免value过大,该拆就拆。

最后我跟面试官聊到,用Redis应对高并发,心态很重要,你不能指望它是个万能药,什么数据都往里扔,然后指望它又快又稳永不犯错,它更像一个能力超强但有点小脾气的先锋官,你得了解它的长处(内存操作、丰富数据结构)和短板(持久化取舍、网络问题),然后用整个系统架构去配合它、弥补它,前面说的用消息队列兜底,就是一种承认它可能“犯错”的防御性设计。

那场面试聊了挺久,感觉面试官更看重的是我面对真实问题时的排查思路和权衡取舍的能力,而不是死记硬背几个命令,这些坑和心得,确实都是实实在在的项目体验积累下来的。

用Redis应对高并发面试那些坑和心得分享,聊聊真实体验