PHP里用Redis锁和文件锁搞安全操作,避免并发冲突的那些事儿
- 问答
- 2026-01-23 14:07:17
- 3
(引用来源:网络技术博客、PHP官方手册、Redis官方文档以及个人开发实践经验)
在PHP程序里,当很多人或者很多个任务在同一时刻去抢着修改同一个东西的时候,比如大家都想抢一张特价票,或者同时去减少同一个商品的库存,这时候就会出问题,想象一下,库存本来只剩1件,结果两个请求同时读到库存是1,都觉得自己能买,然后都去把库存减成了0,这下就超卖了,卖出了两件但实际上只有一件货,这种因为“同时操作”引发的问题,就叫并发冲突,为了解决这个问题,我们就得用“锁”这种东西,把一些关键操作变成“排队的”,一个一个来,在PHP里,常用的两种锁就是文件锁和Redis锁。
先说说文件锁,这个锁的原理很简单,就是利用操作系统本身提供的文件锁定功能,在PHP里,我们主要用 flock() 函数来实现,它的做法是,让多个进程或线程去争抢同一个物理文件的“打开权”,你可以把这个文件想象成一个只有一个房间的厕所,门上有一把钥匙,第一个来的人(进程)进去后把门锁上(加锁),外面的人就得等着,等他上完厕所出来(释放锁),下一个人才可以进去。
具体用起来大概是这样的:我们先使用 fopen() 打开一个专门用来做锁的文件,这个文件本身内容是什么不重要,它就是个象征,然后调用 flock($handle, LOCK_EX),这个 LOCK_EX 是“独占锁”的意思,就是说,我加锁之后,在我释放锁之前,其他任何进程如果再想用 flock() 来锁这个同一个文件,都会被卡住,一直等到我释放锁为止,这样就保证了,一段需要安全执行的代码,同一时间只有一个进程能运行,执行完了,一定要记得用 flock($handle, LOCK_UN) 来解锁,并且关闭文件句柄 fclose($handle),不然别人就永远等在外面了。
文件锁的好处是简单,不依赖外部服务,只要你的服务器文件系统是正常的,它就能工作,但缺点也不少,它在分布式环境下就不好使了,如果你的网站有好几台服务器,它们之间的文件系统是不共享的,每台服务器都有自己的那个“锁文件”,那A服务器上的进程锁住了自己机器上的文件,根本影响不到B服务器上的进程,锁就失效了,如果加锁后进程意外崩溃,没有正常解锁,可能会导致“死锁”,虽然操作系统通常会自动清理,但也是个潜在风险。
正因为文件锁在分布式场景下的局限性,现在更流行的做法是使用Redis锁,Redis是一个内存数据库,它本身就可以被多台服务器共同访问,就像一个公共的“告示板”,我们用Redis来实现锁,就是利用它的一个特性:SET一个键值对的时候,如果可以设置一个过期时间,并且要求只有当这个键不存在的时候才能设置成功,这个操作在Redis里是原子性的,就是说它是一步到位的,不会被打断,这非常重要。
这个操作命令长这样:SET lock_key_name random_value NX PX 30000,我来解释一下:lock_key_name 就是你给这把锁起的名字,"seckill_product_123_lock"。random_value 最好是一个随机生成的值,比如用 uniqid() 生成的,这个是用来标识当前是哪个客户端加的锁,防止误删别人的锁。NX 意思是只有当 lock_key_name 这个键不存在的时候,才执行SET操作,如果已经存在,SET就失败——这其实就是抢锁,抢成功了你就获得了锁。PX 30000 是设置一个过期时间,比如30000毫秒(30秒),这个过期时间是救命稻草,它保证了即使抢到锁的进程因为某种原因挂掉了,没有主动释放锁,到了30秒后Redis也会自动把这个锁删除,让其他进程有机会获得锁,避免了死锁。
抢锁的代码逻辑就是:用一个循环去尝试执行这个SET命令,如果返回成功,说明抢到锁了,就立刻执行那些危险的核心操作(比如扣减库存),执行完毕后,一定要释放锁,释放锁的时候不能简单地用 DEL 命令删除键,因为可能你程序执行得比较慢,锁已经过期了,并且被另一个进程抢去了,这时候你再删,删掉的就是别人的锁了,所以安全的做法是,用Lua脚本先判断一下当前锁的值是不是自己当初设置的那个 random_value,如果是,才删除;如果不是,说明锁已经不属于自己了,就别动了。
Redis锁的优势很明显,它非常适合多台服务器组成的分布式系统,大家对一个中心化的Redis进行操作,锁的状态对所有人都是统一的,而且通过设置过期时间,避免了死锁,但它的挑战在于,你需要维护一个高可用的Redis服务,如果Redis服务器宕机了,那所有依赖它的锁就都失效了,虽然Redis有主从复制和集群方案,但在主从切换的极短瞬间,还是有可能出现锁失效的问题(这引出了更复杂的RedLock算法,但这里不深入讨论),因为有了过期时间,你需要确保你的业务逻辑能在锁过期之前执行完,否则锁自动释放了,另一个进程闯进来,还是可能产生并发问题。
文件锁和Redis锁都是为了解决PHP中的并发冲突,文件锁简单直接,适合单机部署或者对一致性要求不是极端苛刻的场景,而Redis锁功能更强大,是分布式应用的首选,但实现起来稍复杂,且对Redis服务的稳定性有依赖,选择哪一种,要根据你的实际业务场景、服务器架构和对安全性的要求来定,核心思想都是一样的:在操作要害数据之前,先想办法拿到一个“通行证”,拿不到就等,拿到了就快速做完事然后赶紧把“通行证”还给后面排队的人。

本文由凤伟才于2026-01-23发表在笙亿网络策划,如有疑问,请联系我们。
本文链接:http://waw.haoid.cn/wenda/84497.html
