linux cron是最稳妥的定时推送触发方式,需用绝对路径调用php cli脚本并重定向日志;推送逻辑必须实现状态标记与幂等控制,避免重复发送。

PHP 本身不维护后台进程,所谓“定时推送”必须依赖外部调度器或协程运行时;直接在 Web 请求里 sleep() 或用 pcntl_alarm() 做延时,99% 的情况会失败或被 kill。
Linux cron 是最稳妥的触发方式
所有生产环境的定时推送任务,第一选择是让系统级 cron 每分钟(或更细粒度)调用一次 PHP CLI 脚本。它不依赖 PHP 进程存活,不占用 Web 服务器资源,且可精确控制执行时机。
- 必须用绝对路径调用 PHP 解释器,例如
/usr/bin/php,不能只写php(crontab 环境无 PATH) - 脚本路径也必须是绝对路径,
/var/www/project/notify.php,相对路径在 crontab 下无效 - 建议加日志重定向:在 crontab 条目末尾加上
>> /var/log/notify.log 2>&1 - 避免每秒调度——cron 最小粒度是 1 分钟;若需秒级,得用
while sleep 1; do php notify.php; done+nohup,但要配supervisor管理生命周期
推送逻辑必须带状态标记和幂等控制
因为 cron 只管“按时触发”,不管上一次是否执行完、是否成功。若数据库里一条待推送记录没加锁就直接 UPDATE ... SET status = 1,高并发下极易重复发送。
- 查任务用
SELECT ... WHERE status = 0 AND send_at ,避免全表扫描 - 查出后立刻用
UPDATE notifications SET status = 1 WHERE id IN (1,2,3) AND status = 0标记为“处理中”,利用 WHERE 条件防并发覆盖 - 每条消息发送后,再
UPDATE ... SET status = 2, sent_at = NOW();失败则设为status = 3并记录error_msg - 不要用
WHERE status = 0 AND send_at 一次性更新全部,大表易锁死
Swoole 协程定时器适合长连接场景下的延迟通知
当你的服务已基于 Swoole 运行(如 WebSocket 推送、IM 服务),可以用 Swoole\Timer::after() 或 Swoole\Timer::tick() 实现毫秒级延迟或周期检查,但注意这不是替代 cron 的方案,而是补充。
-
Swoole\Timer::after(5000, function () { sendReminder(); });表示 5 秒后发提醒,不阻塞其他协程 - 该定时器绑定在当前进程内,若 Worker 进程重启(如 reload),未触发的
after会丢失,不可用于关键业务 -
tick()适合轮询检查数据库中send_at刚到时的任务,但频率不宜高于 1s,否则 CPU 压力大 - 务必配合
try/catch和go(function () { ... })包裹实际发送逻辑,防止异常中断整个定时器循环
时间戳字段设计直接影响查询效率
推送任务表里的 send_at 字段必须是 INT 或 DATETIME 类型,并建立联合索引:INDEX idx_status_sendat (status, send_at)。否则每次 WHERE status = 0 AND send_at 都是全表扫描。
- 别用字符串存时间戳(如
"2026-05-20 22:00:00"),MySQL 无法走索引做范围比较 - 如果业务要求支持毫秒级精度,
send_at用BIGINT存 Unix 时间戳(秒或毫秒),比DATETIME(3)更省空间、索引更紧凑 -
status字段建议用 TINYINT(1) 而非 ENUM,方便后续扩展状态值(如 0=待发、1=发送中、2=成功、3=失败、4=已取消) - 上线前务必用
EXPLAIN验证查询是否命中索引,尤其在数据量超 10 万后
真正难的不是“怎么让 PHP 在某个时间点执行”,而是“怎么确保它只执行一次、失败能重试、并发不乱序、日志可追溯”。这些细节往往在压测或上线后才暴露,别等到用户投诉重复短信才想起加 WHERE status = 0 条件。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











