cron 备份没结束又启动下一轮:用 flock 控制同机并发
cron 备份超过触发周期而多份并行时,用同机 flock 约束所有入口,区分非阻塞跳过、等待队列和后台进程持锁。
备份每小时由 cron 启动一次,但一轮同步超过一小时,进程列表里不断出现多份相同的备份任务。cron 会按时间触发命令,不会自动判断上一轮是否完成。需要先确定任务是否允许排队,再用同一把锁约束所有入口。
在 Linux 的 util-linux flock 中,锁由打开的文件及进程持有。锁文件存在不代表任务正在运行,任务结束后文件仍可以保留;把文件是否存在当作锁状态,容易误判。
不希望积压,使用非阻塞锁
下面是用户 crontab 的结构示例,假设系统提供这些绝对路径,运行用户有权限创建锁文件,backup.sh 会在前台等待全部备份步骤完成:
0 * * * * /usr/bin/flock -n /var/lock/myapp-backup.lock /usr/local/sbin/backup.sh
flock 默认取得排他锁。-n 表示锁被占用时立即失败,不启动这次备份;不加 -n 时默认等待,虽然不会同时执行,但频繁触发可能让等待中的进程积压。
如果确实需要有限等待,可以按备份时效使用 -w 指定等待时长。跳过一次和排队等待是不同的业务选择,应记录被跳过的触发,并确认后一轮会覆盖需要备份的内容,不能把“没有并发”当作“没有遗漏”。
锁冲突默认返回状态码 1,可用 -E 设置另一个冲突状态码。命令真正执行后,flock 返回的是被包装命令的状态码。因此监控要区分抢锁失败、锁路径权限错误和备份本身失败;选择专用冲突码时,也要避免与脚本已有的退出码混淆。
同一路径、所有入口都用锁
定时任务、人工执行和其他脚本应通过一致的加锁入口启动。仅给 cron 那一行加锁,不能阻止管理员绕过它直接运行备份脚本。不同锁文件也不能互相排斥,即使它们保护的是同一个备份目录。
如果普通用户无法写入 /var/lock,应由有权限的管理者预先准备合适的目录和权限,或使用运行用户专属的本地锁路径。不要为了加锁把整份备份改成 root 执行,也不要选择其他用户可以随意替换文件的路径。
flock 是协作锁,只有遵守同一加锁规则的进程才会受约束。多台机器分别在自己的本地文件上加锁,不能形成跨主机互斥;NFS、CIFS 等文件系统的锁支持还受实现和挂载条件限制。本方案优先用于单机、本地文件系统上的任务。
脚本启动后台任务,会改变锁的生命周期
若 backup.sh 启动后台任务后立即退出,锁的持续时间可能与真正备份时间不一致。子进程是否继承锁文件描述符也会影响释放时机,不能仅看外层脚本是否退出就判断锁已释放。
最直接的组织方式是让加锁范围内的脚本在前台运行,并等待它启动的所有必要步骤结束。调整前核对脚本中的 &、后台化程序和子进程,不要随意使用 --close,因为关闭继承的描述符也可能让锁提前失去保护。
不要在任务仍运行时删除锁文件来“解锁”。旧进程可能继续锁住旧文件,新进程却打开同名的新文件,从而绕开互斥。文件留在磁盘上通常不需要清理,锁随相关文件描述符关闭而释放。
验收要同时检查任务和触发记录
修改前保存 crontab 和脚本。已存在的重复备份不会因为新增锁自动停止,应先确认各进程状态及正在写入的目标,再按备份工具支持的方法收尾,避免直接杀死进程造成半完成数据。
在受控窗口检查一个备份持锁期间,第二次同入口触发是否按设计跳过或等待;第一轮正常结束后,下一轮是否能取得锁。还要核对备份日志、产物完整性和失败状态,确保锁没有把权限错误或真正的备份失败掩盖成“任务正在运行”。
需要恢复旧调度时,可以还原保存的 crontab 和脚本;这会重新允许原来的并发风险,恢复前应先处理长任务与触发周期的关系。


