多机环境下必须用 redislockstore 或数据库锁,因 filelockstore 依赖本地文件系统,跨容器或服务器时互不可见;应配置 redis dsn 并在 lock.yaml 中指定 default_locker 为 redis,命令中通过 lockfactory 创建带 ttl 的锁并确保 finally 释放。

不能只靠 Symfony Lock 的 FileLockStore,多机环境下必须换 RedisLockStore 或数据库锁。 Symfony Lock 组件本身不决定存储后端,它只是抽象层;默认的 FileLockStore 只在单机有效,跨容器或跨服务器时完全失效——两个实例会各自在本地创建同名锁文件,互不可见。
为什么 FileLockStore 在多机/容器环境一定失效
文件锁依赖操作系统内核对同一文件路径的原子控制,而容器间、服务器间根本没有共享文件系统(除非你显式挂载了 NFS 或类似方案,但极不推荐用于锁)。哪怕所有机器都写 /tmp/task.lock,它们操作的是各自隔离的文件系统实例。
常见错误现象:
- 你在两台服务器上都部署了相同 crontab,任务仍同时执行
- Docker Swarm/K8s 多副本下,多个 Pod 同时触发同一个 Symfony 命令
- 日志里反复出现“锁已存在”却毫无作用——那只是本机的假阳性
正确配置 RedisLockStore(推荐)
Redis 是最轻量、最通用的分布式锁后端。Symfony Lock 内置支持,无需额外包(v5.4+ / v6.x 默认包含)。
关键步骤:
- 确保
symfony/lock已安装(通常随框架自带),且 Redis DSN 配置正确:REDIS_URL=redis://10.0.1.5:6379 - 在
config/packages/lock.yaml中强制指定存储器:framework: lock: default_locker: 'redis' - 在命令中使用锁时,不要 new
FileLockStore,而是注入LockFactory并调用createLock('task-name')—— 它会自动走 Redis - 注意:Redis 锁默认 TTL 是 300 秒,若任务可能超时,需显式设置更长 TTL:
$lock = $this->lockFactory->createLock('report-gen', 600);
Command 内部加锁的典型写法
别在 crontab 层加 flock,那只能防单机;必须在 Symfony 命令逻辑里用 Lock 组件控制业务级互斥。
示例片段(非完整类):
public function execute(InputInterface $input, OutputInterface $output): int
{
$lock = $this->lockFactory->createLock('daily-report');
if (!$lock->acquire()) {
$output->writeln('Another instance is running. Exiting.');
return Command::FAILURE;
}
try {
// 执行报表生成逻辑
$this->generateReport();
} finally {
$lock->release(); // 必须确保释放,建议放 finally
}
return Command::SUCCESS;
}
容易踩的坑:
- 忘记
release()→ Redis key 永久残留,后续所有任务都被阻塞 - 没设足够长的 TTL → 任务还在跑,锁被 Redis 自动删掉,新实例闯入
- 用
LockFactory::createLock()但没配好 DSN → 回退到FileLockStore,你以为分布式,其实还是单机
复杂点在于:锁的粒度要和业务语义对齐。比如“每小时发一次邮件”和“每天生成一份报表”,必须用不同锁名,否则互相阻塞。别图省事全用 'global'。











