导读:本期聚焦于天马创作的《PHP 锁机制怎么用?Redis分布式锁实现与并发控制策略详解》,敬请观看详情。为什么同一份库存在高并发下会被超卖?为什么定时任务部署多台服务器后会重复执行?这些问题的根源大多出在没有正确使用锁。本文围绕PHP项目中的锁机制展开,先讲清楚文件锁、MySQL行锁这类单机或数据库层面锁的适用边界,再重点剖析基于Redis的分布式锁实现细节,包括setnx加锁、过期时间设置、锁续期、Lua脚本原子性释放锁,以及防止误删他人锁的常见做法。文中还对比了Redlock、Redisson等方案的优劣,并给出秒杀扣库存、防重复提交等典型场景下的并发控制策略与踩坑提醒,帮助你写出真正安全可靠的并发代码。

并发问题几乎是所有PHP业务系统绕不开的坑。库存超卖、订单重复创建、定时任务被多台机器同时触发,这些线上事故十有八九都和锁的使用不当有关。单机环境下一个flock就能解决大部分问题,可一旦服务上了集群,文件锁就彻底失效了,这时候分布式锁就成了必选项。本文从单机锁讲到Redis分布式锁,把加锁、续期、解锁的完整链路和容易踩的坑一次讲透。

PHP 锁机制怎么用?Redis分布式锁实现与并发控制策略详解

一、先搞清楚:单机锁和分布式锁的区别在哪

很多PHP开发者对锁的理解停留在flock阶段。文件锁在单机部署下确实好用,比如防止同一个cron脚本并发执行,代码非常简单:

$fp = fopen('/tmp/my_task.lock', 'w+');
if (!flock($fp, LOCK_EX | LOCK_NB)) {
    exit("任务正在执行中\n");
}
// 执行业务逻辑
flock($fp, LOCK_UN);
fclose($fp);

这段代码在单台服务器上没有任何问题,但项目一旦横向扩容到两台机器,每台机器上都有自己的/tmp/my_task.lock,锁就形同虚设。同理,MySQL的行锁(SELECT ... FOR UPDATE)虽然能跨机器生效,但它依赖事务连接,持有时间受长连接和超时配置限制,高并发场景下容易把数据库连接池打满,性能开销也不小。

分布式锁的核心诉求是:多个进程、多台机器抢同一把锁,且这把锁要有一个所有节点都能访问的第三方存储来托管。可选方案主要有三种:数据库(性能差)、Redis(性能最好,使用最广)、ZooKeeper(一致性最强但部署重)。对绝大多数PHP项目来说,Redis是性价比最高的选择,PHP的phpredis扩展也提供了完善的原子命令支持。

二、Redis分布式锁的正确加锁姿势

网上流传最广的错误写法是用SETNX加锁,再用EXPIRE设置过期时间。这两步不是原子操作,如果程序在两步之间崩溃,锁就永远无法释放,形成死锁。正确的做法是使用一条命令完成加锁和设置过期时间:

$redis = new Redis();
$redis->connect('127.0.0.1', 6379);

$lockKey   = 'lock:stock:1001';
$lockValue = uniqid('', true) . getmypid(); // 唯一标识,防止误删别人的锁
$expire    = 10; // 秒

// 一条命令原子完成 SETNX + EXPIRE
$result = $redis->set($lockKey, $lockValue, ['NX', 'EX' => $expire]);

if ($result) {
    echo "加锁成功\n";
    try {
        // 执行业务逻辑
    } finally {
        releaseLock($redis, $lockKey, $lockValue);
    }
} else {
    echo "获取锁失败\n";
}

这里有几个关键细节必须强调。第一,EX过期时间一定要设置,这是防止死锁的最后防线——即使进程被kill,锁到期后也会自动释放。第二,锁的value必须是每个客户端唯一的标识,目的是在释放锁时校验这把锁是不是自己加的。如果不做校验,就会出现这样的悲剧:客户端A的业务执行太慢导致锁过期,客户端B拿到了锁开始执行,随后A执行完毕直接删除了锁,结果C又拿到了锁,B和C并发执行,锁彻底失效。

第三,过期时间设多长是个经验活。设太短,业务没执行完锁就没了;设太长,进程崩溃后其他节点要干等。稳妥的做法是结合「看门狗」机制:开一个后台定时任务,每隔过期时间的三分之一就检查锁是否还属于自己,属于则续期。如果用Swoole或Workerman常驻进程,实现起来很自然;FPM模式下可以用子进程实现,或者直接把过期时间设得宽裕一些。

三、释放锁为什么必须用Lua脚本

释放锁听起来简单,不就是DEL一下?但「判断锁的值 + 删除锁」这两步在PHP中是两次网络往返,中间存在竞态窗口:判断的时候锁还是自己的,删除的时候可能已经过期易主了。Redis单线程执行Lua脚本是原子的,天然解决这个问题:

function releaseLock(Redis $redis, string $key, string $value): bool
{
    $lua = <<<LUA
if redis.call('GET', KEYS[1]) == ARGV[1] then
    return redis.call('DEL', KEYS[1])
else
    return 0
end
LUA;

    return (bool)$redis->eval($lua, [$key, $value], 1);
}

脚本的逻辑很直白:只有锁的值和自己持有的标识一致时才删除,否则返回0。这样即使客户端A的锁早已过期,它尝试释放时校验不通过,不会误删客户端B的锁。这个看似不起眼的校验,恰恰是很多自研分布式锁最常缺失的一环。

另外要理解一个前提:Redis分布式锁保证的是「尽力而为」的安全性。如果Redis是主从架构,主节点写入锁后还没同步到从节点就宕机,从节点晋升为新主后,锁数据丢失,另一个客户端可以再次加锁,出现两个持有者。如果业务对这种极端情况零容忍,要么使用Redlock算法在多个独立Redis节点上同时加锁,要么直接换ZooKeeper或etcd。对库存扣减这类场景,更务实的做法是分布式锁加数据库层面的乐观锁兜底,双保险。

四、典型场景下的并发控制策略

1. 秒杀扣库存:锁加乐观锁兜底

秒杀场景下,Redis锁用来拦截绝大多数无效请求,真正落到数据库的请求再用条件更新兜底:

UPDATE goods SET stock = stock - 1
WHERE goods_id = 1001 AND stock > 0;

受影响行数为0就说明库存不足或已被并发扣减,直接返回失败。这样即使锁出现极端失效,数据库层也能保证不超卖。

2. 防接口重复提交:锁加唯一约束

用户快速双击提交按钮,两次请求几乎同时到达。以用户ID加业务类型作为锁键,第一个请求加锁成功,第二个直接返回「请勿重复提交」。更彻底的方案是给订单表加业务唯一索引,从存储层面杜绝重复数据,锁只是用来提升用户体验和减少无效写入。

3. 多机定时任务:抢锁执行或任务分片

定时任务部署到多台机器时,通过Redis抢锁决定由哪台执行。如果任务量大,还可以把锁粒度细化到任务分片,每台机器抢不同分片的锁,并行执行互不干扰,这就是很多PHP任务调度框架的底层原理。

五、现成方案推荐

如果不想自己造轮子,可以关注这些方案。PHP生态里有基于Redis封装的分布式锁组件,Swoole生态则有Swoole官方的Lock以及各种协程安全的锁实现。Java领域的Redisson提供了完整的可重入锁、读写锁、看门狗实现,其设计思路值得PHP开发者借鉴:可重入性可以通过Hash结构记录持有者和重入次数,读写锁则通过两个关联的键配合Lua脚本控制读写比例。

最后总结几条实践原则:加锁和过期时间必须原子完成;锁的value要唯一并在释放时校验;业务执行时间不可控时要有续期机制;关键数据操作永远要有数据库层的兜底约束。锁不是万能药,它是并发控制的最后一道闸门,设计时优先考虑幂等设计和唯一约束,锁作为性能优化手段来用,系统才能既快又稳。

PHP锁机制Redis分布式锁并发控制修改时间:2026-09-11 04:52:37

免责声明:已尽一切努力确保本网站所含信息的准确性。网站作品多为原创整理与精心创作,观点力求客观中立。本站旨在免费分享,内容仅供个人学习、研究或参考使用。若引用了第三方作品,版权归原作者所有。如内容涉及您的权益,请联系我们进行处理Email:chomcom@qq.com。
引用或转载本作品时,请注明当前出处:https://www.ipipp.com/html/0911/54471.html,基于非商业用途的前提下,欢迎转载或二创本作品。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。AI、前端、编程、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握开发与运维所需的核心技术。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端编程,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。