Symfony锁组件需明确选存储、判返回、控范围、管生命周期:首选RedisStore确保跨进程有效,必须检查acquire()返回值,显式释放锁,且锁范围应最小化,仅包裹真正需互斥的代码。

Symfony 锁组件不是“加了就自动生效”的黑盒,它需要明确选存储、判返回、控范围、管生命周期。用错方式,锁等于没加。
选对存储驱动,锁才跨进程有效
锁组件本身不存数据,必须指定一个 StoreInterface 实现——这一步错了,整个锁机制就失效。
-
RedisStore:生产环境首选。支持原子操作、自动过期、多进程/多机器共享。需确认 Redis 配置了
timeout和retry_interval,否则等待可能卡死 -
FlockStore:仅限单机、同文件系统。容器中若未挂载持久化卷,或用了
tmpfs,每次重启锁就消失;不同用户(如www-data和root)运行的 CLI 进程也无法共享同一把锁 -
PdoStore:依赖数据库事务隔离级别。MySQL 必须设为
REPEATABLE READ或更高,否则SELECT ... FOR UPDATE可能漏锁 - NullStore:仅用于开发调试,不真正加锁,上线前必须删掉
acquire() 返回值必须检查
调一次 $lock->acquire() 不代表任务已受保护。忽略返回值,等于裸奔。
- 非阻塞模式(默认):
if (!$lock->acquire()) { throw new RuntimeException('Lock not acquired'); } - 阻塞模式:
$lock->acquire(true),但必须搭配超时控制,避免无限等待 - 不要依赖
__destruct()自动释放——PHP 垃圾回收时机不可控;应显式调$lock->release(),或包在try/finally中
锁的范围要最小化
锁不是越早加越好,而是只包真正需要互斥的那几行代码。
- 禁止在锁内做远程 API 调用、大文件读写、
sleep()等耗时 I/O 操作 - 推荐结构:
acquire → 读DB → 修改内存数据 → 写DB → release,把 DB 查询和写入之外的逻辑全移出去 - 对读多写少场景,可考虑
acquireRead()获取共享锁,提升并发度
CLI 命令加锁仍并发?大概率是存储选错了
Web 请求和 CLI 命令本质都是独立 PHP 进程。FlockStore 在容器、NFS、多用户等环境下极易失效。
- 验证方式:起两个终端,同时运行同一命令,观察是否并行执行
- 根治方法:改用
RedisStore,确保所有 CLI 进程连接的是同一个 Redis 实例和 DB 库 - 进阶技巧:给 CLI 锁加唯一上下文标识(如
"import:{$batchId}"),避免不同批次互相阻塞


















