你一定是用了假的 Linux cron

场景

好多系统中会用到邮件系统,我们假设有一个 PHP 脚本用来发送邮件。使用 Linux cron 每分钟执行一次

我们暂时不引入队列系统,其实使用队列处理此方式更优。

我们得到下面的基本配置

* * * * * php /home/app/email.php

问题分析和解决

如果这个邮件服务出现异常,进程僵死怎么办?

假设由于未知因素, email.php 脚本一直执行,没有退出。极端的情况,进入一个 while 死循环。

这下倒好,原来说好的一分钟执行一次,现在一直死这边了,后面的脚本也不能跑了


解决办法:

使用 timeout,假设我们设定每个脚本最多执行时间位 200秒,超过 200秒 就自动死掉。

* * * * * timeout 200 php /home/app/email.php

如果这个脚本执行时间超过 60秒,下一分钟又会执行 php email.php,如果避免重复执行?

这样会出现,有两个进程同时在执行 php email.php,那会不会出现同一个任务被执行了两次?


解决办法:

使用 flock 进行互斥控制

用法:
 flock [选项] <文件|目录> <命令> [<参数>...]
 flock [选项] <文件|目录> -c <命令>
 flock [选项] <文件描述符号码>

通过 shell 脚本管理文件锁。

选项:
 -s, --shared             获取共享锁
 -x, --exclusive          获取排他锁(默认)
 -u, --unlock             移除锁
 -n, --nonblock           失败而非等待
 -w, --timeout <秒>       等待限定的时间
 -E, --conflict-exit-code <数字>     冲突或超时后的退出代码
 -o, --close              运行命令前关闭文件描述符
 -c, --command <命令>      通过 shell 运行单个命令字符串
 -F, --no-fork            执行命令时不 fork
     --verbose            增加详尽程度

 -h, --help               display this help
 -V, --version            display version

我们用到其中的排他设置

* * * * * flock -xn /tmp/test.lock -c "timeout 200 php /home/app/email.php"

记录好日志

定时任务可能要记录日志呀,不然后期怎么排查

* * * * * flock -xn /tmp/test.lock -c "timeout 200 php /home/app/email.php >> /home/log/test.log 2>&1" 

总结

* * * * * flock -xn /tmp/test.lock -c "timeout 200 php /home/app/email.php >> /home/log/test.log 2>&1" 

番外篇 频率提升

我觉得一分钟一次频率太低,想 10s 执行一次怎么办?

* * * * * php /home/app/email.php >> /home/log/test.log 2>&1
* * * * * ( sleep 10 ; php /home/app/email.php >> /home/log/test.log 2>&1 )
* * * * * ( sleep 20 ; php /home/app/email.php >> /home/log/test.log 2>&1 )
* * * * * ( sleep 30 ; php /home/app/email.php >> /home/log/test.log 2>&1 )
* * * * * ( sleep 40 ; php /home/app/email.php >> /home/log/test.log 2>&1 )
* * * * * ( sleep 50 ; php /home/app/email.php >> /home/log/test.log 2>&1 )

番外篇 flock 测试

准备一个 php 脚本 /home/rovast/Code/flock/test.php

<?php

$i = 10000;
while ($i > 0) {
  echo --$i . \PHP_EOL;
  sleep(1);
}

执行

flock -xn /tmp/mytest.lock -c "timeout 30 php /home/rovast/Code/flock/test.php"

我们看到终端不停输出数值

9999
9998
9997
9996
9995
9994
9993
9992
9991
9990

我们再打开另外一个终端,执行

flock -xn /tmp/mytest.lock -c "timeout 30 php /home/rovast/Code/flock/test.php"

我们发现:

  1. 第二次执行的没有输出(因为 flock 互斥)
  2. 第一个执行的,30秒后自动关闭进程(因为 timeout 30)
本作品采用《CC 协议》,转载必须注明作者和本文链接
本帖由系统于 10个月前 自动加精
《L02 从零构建论坛系统》
以构建论坛项目 LaraBBS 为线索,展开对 Laravel 框架的全面学习。应用程序架构思路贴近 Laravel 框架的设计哲学。
《L05 电商实战》
从零开发一个电商项目,功能包括电商后台、商品 & SKU 管理、购物车、订单管理、支付宝支付、微信支付、订单退款流程、优惠券等
讨论数量: 6

笔记

-n, --nonblock           被其他服务锁住的时候返回失败而非等待
10个月前 评论

@xuzili 嗯,根据需求选择合适的参数。在我举的这个示例中,如果旧的进程A还没有结束,而下一个进程B时间又到了执行时刻,我们选择不执行B,等下一个周期再去执行,以此类推

10个月前 评论
Benny 3个月前
rovast (作者) (楼主) 3个月前

刚好最近在用Golang实现 分布式Crontab服务,互斥的逻辑可以加进去 :+1:

9个月前 评论
JasonG

想请问一下,/tmp/test.lock 是什么文件呢?

7个月前 评论

@JasonG 这是一个临时文件,用于标记互斥用的,运行时会生成。


你可以理解为,进程 A 行时,给这个文件加了一个锁标记。当下次定时任务 B 尝试再次运行时,读取这个文件“锁属性”,发现被标记了,表示 A 未结束。当 A 结束时,会释放标记。


另外,php 自己也有对应的文件锁参数 flock,可以了解一波 https://php.net/manual/zh/function.flock.p...

7个月前 评论

@rovast 有点类似MySQL的悲观锁

7个月前 评论

请勿发布不友善或者负能量的内容。与人为善,比聪明更重要!