运维脚本应提前暴露锁冲突,用非阻塞flock显式判断并统一退出码12;日志标记锁行为,隔离临时路径,避免nfs问题,配合trap安全清理。

运维脚本捕获并发锁冲突失败,核心不是“事后检测”,而是**提前暴露冲突、明确退出信号、统一错误归因**。锁本身不报错,但抢锁失败会直接导致脚本中止或跳过关键逻辑——你需要让这个中止可识别、可记录、可区分于其他错误。
用非阻塞 flock 显式判断锁状态
别依赖 flock -c 静默失败。改用文件描述符 + -n 模式,在代码里主动检查:
-
先申请锁,再分支处理:
exec 200>/tmp/myjob.lock后执行if ! flock -n 200; then echo "ERROR: lock held by another instance" >&2; exit 12; fi -
固定退出码便于监控识别:比如约定
exit 12专指“锁冲突”,日志中出现该码即可触发告警,不与命令执行失败(如exit 1)、语法错误(exit 2)混淆 -
避免在锁外做耗时操作:校验依赖、读配置等前置步骤应放在
flock之前;否则锁冲突时,这些步骤已执行,可能造成状态污染
日志中显式标记锁行为
光有退出码不够,需上下文支撑排障:
- 抢锁前打印:
echo "$(date '+%F %T') [LOCK] attempting to acquire /tmp/myjob.lock" >&2 - 成功后记录持有者:
echo "$(date '+%F %T') [LOCK] acquired, PID=$$" >&2 - 失败时附进程线索:
ps -o pid,cmd -C 'myjob.sh' 2>/dev/null | tail -n +2 >&2(列出当前其他实例)
区分锁冲突与其他并发问题
锁失败只是表象,背后可能是更深层的并发设计缺陷:
-
临时文件路径未隔离:多个实例共用
/tmp/output.txt,即使加了锁,写入仍可能覆盖——应改用/tmp/output.$$.txt或mktemp -
锁文件路径不一致:cron 环境下
$HOME可能为空,导致锁文件落到/tmp/.lock;而交互执行时落到/home/user/.lock——统一用绝对路径,如/var/run/myjob.lock -
NFS 文件系统不支持 flock:若锁文件放在 NFS 挂载点,
flock可能始终成功或行为异常——改用mkdir原子性建锁目录,或换本地磁盘路径
配合 trap 实现锁失败后的安全收尾
即使抢锁失败,也要确保不遗留副作用:
- 在脚本开头注册:
trap 'rm -f /tmp/myjob.tmpdata' EXIT,清理可能已生成的中间文件 - 若锁失败前已创建临时目录,应在
exit 12前手动清理:rm -rf /tmp/myjob.work.$$ - 避免在 trap 中再次调用
flock或远程操作——锁失败场景下,环境可能不稳定,trap 应只做本地、幂等、快速释放动作











