前几个月,我在公司食堂吃午饭的时候,遇到了一个挺有意思的场面。
那天是周五,中午公司搞活动,限量发 100 份小龙虾饭。我端着餐盘排队,前面长得跟 JVM GC 日志一样,看不到尽头。食堂阿姨在窗口喊:“别挤别挤,一个一个来。”
结果呢?还是挤。有人趁阿姨转身,直接伸手拿;有人觉得“反正快轮到我了”,往前多蹭一步;还有人嘴上说着“我只看一眼”,身体已经挪进窗口了。短短几分钟,秩序彻底乱了。
后来食堂经理一拍桌子,说了一句让我这个写 Java 的人瞬间 DNA 动了的话:“一个窗口同时只能服务一个人,谁抢谁滚蛋!”
我当时就笑了,这不就是并发竞争资源吗?而且,这种事每天都在我们代码里发生,只不过小龙虾换成了 Redis 里的 Key。
什么是 Redis 的并发竞争 Key?社招面试里,Redis 几乎是必问项。而只要 Redis 一问深入,面试官大概率会甩给你一句:
“如果多个线程或多个服务实例 同时操作同一个 Redis Key,你怎么保证数据是对的?”
这句话的潜台词,其实特别简单:
同一份数据,被很多人同时改,会不会乱?
我们先不用技术名词,直接换成人话。还是拿食堂举例。
小龙虾饭:库存 100 份
Redis 中的 Key:stock = 100
每来一个人,就做一次操作:stock = stock - 1
问题来了。如果 100 个人排队,并且他们是“一个一个来”,那肯定没事。但如果 100 个人同时冲到窗口,每个人都看到“窗口里还有饭”,然后一起伸手抢,那结果一定是:
有人没抢到
有人抢了两份
有人抢到了空气
在 Redis 里,这种现象叫:并发竞争 Key 问题,它本质上就是:
多个客户端并发对同一个 Key 做非原子操作,导致数据不一致。
为什么 Redis 单线程,还会并发出问题?很多同学在这一步就懵了。
“Redis 不是单线程吗?怎么还会有并发问题?”
这问题问得非常好,我当年也被坑过。Redis 的单线程,指的是:
Redis 处理命令的执行是单线程的
但请注意几个关键词:
Redis 在 服务端 是单线程
并发问题,发生在 客户端
换成食堂的例子:
窗口里只有一个阿姨(Redis 单线程)
窗口外排着 100 个人(客户端并发)
如果每个人只做一件事,比如:“阿姨,给我一份小龙虾饭。”
那没问题。但如果每个人都说的是:
“阿姨,里面还有饭吗?”
“有。”
“那我要一份。”
这三步不是原子的。回到 Redis:
GET stock
计算 stock - 1
SET stock
这三步,不是一次性完成的。于是就会出现经典场景:
A 读到 stock = 10
B 也读到 stock = 10
A 写回 9
B 也写回 9
少卖了一份,但系统以为没问题。这就是 Redis 并发竞争 Key 的根源。
面试官真正想听的是什么?我后来也当过面试官,说句实在话:
面试官不是想听你背API,而是想看你是否理解“并发控制”的本质
他通常在判断三件事:
你知不知道 问题为什么会发生
你能不能说出 至少 3 种解决思路
你是否知道 不同方案的适用场景和坑
如果你只说一句:“用 Redis 分布式锁。”,恭喜你,通常只能拿到 60 分。
第一类解决方案:原子操作(最稳但有限)我们先从最简单、最底层的办法讲。Redis 有一个非常硬核的能力:
单条命令是原子执行的
就像食堂规定:“不准问还有没有,想吃就直接报数量,由我来扣。”
如果库存扣减是这样完成的,就没问题。在 Redis 里,对应的是:
INCR
DECR
INCRBY
DECRBY
也就是说,把:GET + 计算 + SET,改成:DECR stock,这一步,由 Redis 单线程完成,天然不会并发乱。
优点:
简单
性能极好
不需要锁
缺点:
只能用于 简单数值场景
逻辑一复杂就不行了
所以在面试中,你可以明确说一句:
“如果只是计数、扣库存、点赞数这种场景,优先使用 Redis 原子命令。”
这一句话,会让面试官点头。
第二类解决方案:Lua 脚本(原子中的瑞士军刀)原子命令太简单,那复杂一点怎么办?比如:
判断库存是否大于 0
判断用户是否已经下单
扣库存 + 写订单记录
这些就不是一条命令能搞定的。这时候,Redis 给你准备了一把 瑞士军刀:Lua 脚本
Redis 天生支持:Lua 脚本整体原子执行,换句话说:你可以把一段逻辑,塞进 Redis 里一次性执行。
这就相当于食堂改制度了:“我不听你说话了,你把点单、扣库存、打饭写在一张纸上,我一次性照着做。”
不管外面多少人排队,一次只处理一个 Lua 脚本。
优点:
真·原子操作
能处理比较复杂的业务逻辑
性能比分布式锁高
缺点:
Lua 成本高,维护难
脚本写复杂了,排查问题很痛苦
面试时可以这样总结:
“当业务逻辑稍复杂,但又要求高性能和强一致性时,Lua 脚本是非常好的选择。”
第三类解决方案:Redis 分布式锁(最常考)接下来,来到面试官最爱的一段。
“那如果是多个服务实例并发操作 Redis,你怎么控制?”
答案呼之欲出:分布式锁
我们再回到食堂。经理说:“谁要抢饭,先拿号。拿到号的人,才能进窗口。”
这个“号”,就是锁。
1、最基本的思路
谁先拿到 Key
谁就有资格操作共享数据
操作完释放 Key
在 Redis 里,这个“拿号”的动作通常是:SETNX
但注意,真正好的回答,不是说“用 SETNX 就行了”。你必须意识到,这里面到处是坑。
2、面试官最爱追问的几个坑
如果你说“Redis 分布式锁”,面试官往往会继续问:
锁没释放怎么办?
服务死了怎么办?
锁被别人删了怎么办?
主从切换安全吗?
Redis 宕机怎么办?
如果你能顺着这些问题往下聊,基本就稳了。我一般会用一句话总结:“分布式锁的核心不是‘加锁’,而是‘安全地加锁和释放锁’。”
优点:
通用性强
业务逻辑清晰
社招最容易接受的方案
缺点:
实现复杂,坑多
性能不如原子操作和 Lua
第四类解决方案:版本号 / CAS 思路(很多人忽略)再高级一点,有些面试官喜欢考你“设计者思维”。这时候,可以引入:版本号 / CAS 思路
换成生活例子就是:“我只接受在我看到的状态基础上提交修改,否则就失败。”
在 Redis 中,常见做法是:
数据里带一个 version
更新时校验 version 是否一致
不一致就重试
这种方案非常适合:
允许失败
允许重试
不要求一次成功
比如配置更新、规则更新等。这个点说出来,面试官通常会觉得你思路很全。
到底该怎么选?一句话送你如果你只记一句话,我建议你记这个:
Redis 并发竞争 Key,没有银弹,核心是看业务场景。
我自己在实际工作中,大概遵循这个顺序:
能用 原子命令,绝不用锁
复杂一点,用 Lua 脚本
再复杂,用 分布式锁
可失败、可重试,用 CAS 思路
面试时,把这四层逻辑说清楚,基本就是高分答案。

那天的小龙虾饭,最后怎么解决的?
经理最后只保留了一个窗口,地上画了一条线:“谁越线,直接取消资格。”
大家老老实实排队,100 份饭,一份没多,一份没少。我端着那盒小龙虾饭,突然就明白了一件事:
分布式系统解决并发的核心,从来不是快,而是秩序。
而 Redis 并发竞争 Key,本质上也是如此。
END你不是在写代码,你是在建立规则。好朋友们,我们下篇见~