本文目录导读:

在PHP项目中,规避定时任务重复执行主要有以下几种常用方案:
基于文件锁(最常用)
<?php
class TaskLocker {
private $lockFile;
private $fp;
public function __construct($taskName) {
$this->lockFile = sys_get_temp_dir() . '/' . md5($taskName) . '.lock';
}
public function acquire() {
$this->fp = fopen($this->lockFile, 'c');
if (flock($this->fp, LOCK_EX | LOCK_NB)) {
return true; // 获取锁成功
}
return false; // 获取锁失败,任务正在运行
}
public function release() {
if ($this->fp) {
flock($this->fp, LOCK_UN);
fclose($this->fp);
@unlink($this->lockFile);
}
}
}
// 使用示例
$locker = new TaskLocker('sync_order_task');
if (!$locker->acquire()) {
echo "任务正在运行中\n";
exit;
}
try {
// 执行业务逻辑
echo "执行定时任务...\n";
sleep(10); // 模拟耗时操作
} finally {
$locker->release();
}
基于数据库锁
<?php
class DatabaseTaskLocker {
private $db;
private $taskName;
private $lockTime = 300; // 锁超时时间(秒)
public function __construct($taskName) {
$this->taskName = $taskName;
$this->db = new PDO('mysql:host=localhost;dbname=test', 'user', 'pass');
}
public function acquire() {
try {
// 使用MySQL GET_LOCK() 函数
$stmt = $this->db->prepare("SELECT GET_LOCK(:task_name, 0) as locked");
$stmt->execute([':task_name' => $this->taskName]);
$result = $stmt->fetch(PDO::FETCH_ASSOC);
return $result['locked'] == 1;
} catch (Exception $e) {
return false;
}
}
public function release() {
$stmt = $this->db->prepare("SELECT RELEASE_LOCK(:task_name)");
$stmt->execute([':task_name' => $this->taskName]);
}
}
// 或使用表记录方式
class TaskLockByTable {
private $db;
public function tryLock($taskName, $timeout = 300) {
$expireTime = date('Y-m-d H:i:s', time() + $timeout);
// 尝试插入锁记录,如果已存在则更新失败
$sql = "INSERT INTO task_locks (task_name, expire_time, created_at)
VALUES (:task_name, :expire_time, NOW())
ON DUPLICATE KEY UPDATE
expire_time = IF(expire_time < NOW(), :expire_time2, expire_time)";
$stmt = $this->db->prepare($sql);
$stmt->execute([
':task_name' => $taskName,
':expire_time' => $expireTime,
':expire_time2' => $expireTime
]);
return $stmt->rowCount() > 0;
}
public function release($taskName) {
$this->db->prepare("DELETE FROM task_locks WHERE task_name = :task_name")
->execute([':task_name' => $taskName]);
}
}
基于Redis锁(推荐用于分布式环境)
<?php
class RedisTaskLocker {
private $redis;
private $lockKey;
private $lockValue;
private $ttl = 300; // 锁超时时间
public function __construct($taskName) {
$this->redis = new Redis();
$this->redis->connect('127.0.0.1', 6379);
$this->lockKey = 'task_lock:' . $taskName;
$this->lockValue = uniqid('', true);
}
public function acquire() {
// SET NX EX 原子操作
return $this->redis->set(
$this->lockKey,
$this->lockValue,
['NX', 'EX' => $this->ttl]
);
}
public function release() {
// 使用Lua脚本保证原子性,只释放自己的锁
$script = "
if redis.call('get', KEYS[1]) == ARGV[1] then
return redis.call('del', KEYS[1])
else
return 0
end";
return $this->redis->eval($script, [$this->lockKey, $this->lockValue], 1);
}
}
// 使用示例
$locker = new RedisTaskLocker('sync_order_task');
if (!$locker->acquire()) {
echo "任务正在其他实例上运行\n";
exit;
}
try {
// 执行业务逻辑
echo "执行定时任务...\n";
} finally {
$locker->release();
}
使用缓存(如Redis)记录任务执行状态
<?php
class TaskExecutionTracker {
private $redis;
public function shouldExecute($taskName, $interval = 60) {
$key = 'task_last_run:' . $taskName;
$lastRun = $this->redis->get($key);
if ($lastRun && (time() - $lastRun) < $interval) {
return false; // 间隔内已执行
}
// 更新执行时间
$this->redis->setex($key, $interval, time());
return true;
}
}
// 使用示例
$tracker = new TaskExecutionTracker();
if (!$tracker->shouldExecute('notify_users', 300)) { // 5分钟执行一次
echo "该任务已执行,等待下次执行\n";
exit;
}
基于crontab的自我保护
# crontab 配置,每隔5分钟执行一次 */5 * * * * /usr/bin/php /path/to/task.php # 如果任务执行时间超过5分钟,下次执行会自动跳过
// task.php 中判断进程数量
<?php
function isProcessRunning($processName) {
exec("ps aux | grep '{$processName}' | grep -v grep", $output);
return count($output) > 1; // 包含当前进程
}
$processName = 'order_sync';
if (isProcessRunning($processName)) {
echo "进程已存在,退出\n";
exit;
}
最佳实践建议
-
选择合适方案:
- 单机环境:文件锁(最简单)
- 分布式环境:Redis锁或数据库锁
- 对一致性要求高:Redis + Lua脚本
-
设置合理的锁超时时间:
// 锁超时时间应大于任务最大执行时间 $lockTimeout = maxExpectedExecutionTime * 2;
-
异常处理:
try { // 获取锁 // 执行业务 } catch (Exception $e) { // 记录日志 error_log("Task failed: " . $e->getMessage()); } finally { // 释放锁 $locker->release(); } -
监控告警:
// 记录任务执行状态到监控系统 class TaskMonitor { public static function recordExecution($taskName, $status, $duration) { // 发送到监控系统(如 Prometheus、Grafana) } }
推荐使用 Redis锁 或 文件锁 方案,它们实现简单且可靠,如果使用多台服务器执行任务,必须使用分布式锁(Redis或数据库)。
版权声明:NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播