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

Redis解锁那些简单又实用的小技巧,帮你轻松搞定并发问题

说到Redis,很多人可能只知道它是个缓存数据库,但其实它在处理并发问题上,也是个隐藏的高手,尤其是在我们日常开发中,经常会遇到多个用户或者多个进程同时想操作同一个资源的情况,比如抢购一件库存只有10个的商品,或者多个用户同时编辑同一个文档的自动保存,这时候,如果不加控制,就会产生“并发问题”,比如库存可能被减成负数,或者后保存的内容覆盖掉先保存的。

Redis提供了一些非常简单又实用的命令,能像一把“钥匙”一样,帮我们安全地管理这些共享资源,下面就来聊聊几个最常用、最直接的小技巧。

第一招:最经典的“设置键值对”加锁法

这个方法的核心思想就是“占坑”,想象一下,公共厕所只有一个位置,谁先进去把门锁上(占住这个坑),谁就能使用,在Redis里,这个“坑”就是一个特定的键(Key)。

具体怎么做呢?当一个进程(比如用户A发起购买请求)需要操作某个资源(比如商品ID为123的库存)时,它先向Redis发起一个命令:SET lock:product_123 A_unique_value NX PX 30000

这里有几个关键点,来自Redis官方文档对SET命令参数的说明:

  • lock:product_123:这就是我们创建的锁的“钥匙”名,最好和你要保护的资源关联起来,比如这里就是商品123的锁。
  • A_unique_value:这个值必须是唯一的,比如可以用请求ID或者UUID,它非常重要,是为了避免误删别人的锁,想象一下,如果用户A因为网络问题锁还没释放,锁自动过期了,然后用户B拿到了锁,这时如果用户A的网络恢复了,它手里的还是旧的钥匙,如果没有这个唯一值校验,它就可能把用户B的锁给删掉,造成混乱。
  • NX:这个参数是精髓所在,它的意思是“只有当这个键不存在的时候,才设置它”,这就保证了只有一个进程能成功设置这个键,也就是只有一个进程能抢到锁。
  • PX 30000:这是给锁设置一个过期时间,比如这里设的是30秒,这是为了防止死锁,万一抢到锁的进程因为某种原因(比如程序崩溃)没有主动释放锁,这个锁也会在30秒后自动消失,让其他进程有机会再获取。

如果这个SET命令执行成功了,说明用户A成功抢到了锁,他就可以安心地去数据库里扣减库存了,操作完成后,他需要释放锁,也就是删除这个键,但删除前要小心,最好用Lua脚本先检查一下值是不是自己设置的那个A_unique_value,如果是,再删除,这样可以确保安全。

第二招:应对更复杂场景的“红锁”算法

Redis解锁那些简单又实用的小技巧,帮你轻松搞定并发问题

上面那种单Redis实例加锁的方法在大部分情况下已经够用了,但如果你的业务非常关键,比如涉及到巨额资金的交易,不能容忍任何单点故障,那么就需要更稳妥的方案,因为如果存放锁的那个Redis服务器突然宕机了,那么整个锁服务就不可用了。

这时候,可以考虑Redis官方文档中提到的“Redlock”算法,也就是我们常说的红锁,它的思想很简单,不要把所有鸡蛋放在一个篮子里”。

红锁算法要求你在一个分布式系统中,部署多个(通常是5个)独立的Redis主节点(注意,不是从节点),当你要加锁时,它会向这5个节点依次发送相同的加锁命令(就是第一招里那个SET命令),只要超过半数的节点(比如5个中的3个)都成功设置了锁,那么这次加锁就算成功了,释放锁的时候,也需要向所有节点发送释放命令。

这样做的好处是,即使有少数一两个Redis节点宕机了,只要大多数节点还活着,锁服务就依然可用,极大地提高了可靠性,这个方案搭建和维护起来比单实例要复杂,所以要根据你的业务重要性来决定是否使用。

第三招:巧用“原子操作”避免加锁

Redis解锁那些简单又实用的小技巧,帮你轻松搞定并发问题

我们不一定非要“加锁”才能解决问题,Redis本身提供的一些原子操作命令,能让我们在不显式加锁的情况下,安全地完成一些常见操作,这就像是 bypass 了“占坑”的步骤,直接通过一条谁也无法打断的指令完成任务,效率更高。

最典型的例子就是库存扣减,如果我们用程序先读取库存,判断大于0,然后再去更新库存,这个“读取-判断-写入”的过程不是原子的,就可能出问题,但Redis提供了一个叫DECR的命令,它能将键的值原子性地减1,如果键不存在,它会先初始化为0再减,所以会得到-1,我们可以利用这个特性:

在活动开始前,我们先用SET stock:product_123 10把库存设为10,当用户抢购时,我们不采用加锁再查询数据库的方式,而是直接对Redis执行DECR stock:product_123,这个命令的执行是不会被其他进程打断的,然后我们立刻获取命令的返回值,如果返回值大于等于0,说明扣减成功,用户抢到了商品;如果返回值小于0(比如是-1),说明库存已经不足了。

这种方式非常高效,因为它完全避免了加锁解锁的开销,而且逻辑清晰,类似的原子里还有INCR(加一)、HINCRBY(哈希字段增减)等,可以根据业务场景灵活运用。

总结一下

Redis为我们解决并发问题提供了从简到繁的多种工具,对于大多数日常场景,用设置NX参数的SET命令来加锁就足够了,记得一定要设置过期时间和使用唯一值,对于金融级的高可用要求,可以考虑红锁算法,而最高效的方法,是优先思考能否利用Redis的原子操作命令(如DECR)来直接解决问题,避免加锁,把这些小技巧用好了,你会发现那些令人头疼的并发问题,其实可以轻松搞定。