本文目录导读:

在 PHP 中实现分布式定时任务,通常有以下几种方案,这里为你详细梳理,从简单到复杂:
核心问题
分布式定时任务要解决的三个核心问题:
- 任务分发:确保任务只被一台机器执行(避免重复执行)
- 任务调度:何时执行、执行频率
- 失败重试:任务执行失败后的处理
主流实现方案
方案1:基于 Redis 实现(推荐入门)
原理:利用 Redis 的 SETNX 或 ZSet 实现分布式锁,配合 brpoplpush 或 BLPOP 实现延迟队列。
// 1. 使用 SETNX 实现分布式锁
class DistributedLock {
private $redis;
private $lockKey;
private $expire;
public function __construct($redis, $lockKey, $expire = 10) {
$this->redis = $redis;
$this->lockKey = $lockKey;
$this->expire = $expire;
}
public function acquire($requestId) {
// 使用 SET key value NX PX expire - 原子操作
$result = $this->redis->set(
$this->lockKey,
$requestId,
['NX', 'PX' => $this->expire * 1000]
);
return $result === true;
}
public function release($requestId) {
// Lua 脚本保证原子性
$script = <<<LUA
if redis.call('get', KEYS[1]) == ARGV[1] then
return redis.call('del', KEYS[1])
else
return 0
end
LUA;
return $this->redis->eval($script, [$this->lockKey, $requestId], 1);
}
}
// 2. 定时任务调度器
class DistributedScheduler {
private $redis;
public function __construct($redis) {
$this->redis = $redis;
}
/**
* 添加延迟任务
*/
public function addDelayTask($task, $delay, $data) {
$id = uniqid('task_');
$scheduleTime = time() + $delay;
// 使用 ZSet 存储,score 为执行时间戳
$this->redis->zAdd("delay_tasks", $scheduleTime, json_encode([
'id' => $id,
'task' => $task,
'data' => $data,
'retry_count' => 0
]));
return $id;
}
/**
* 执行循环 - 在每台机器上运行
*/
public function runWorker($processLimit = 10) {
$lock = new DistributedLock($this->redis, 'scheduler_lock');
while (true) {
// 获取分布式锁,防止多台机器同时消费
$requestId = uniqid();
if (!$lock->acquire($requestId)) {
echo "另一台机器正在处理,等待...\n";
sleep(1);
continue;
}
try {
// 获取到期的任务(score <= 当前时间)
$tasks = $this->redis->zRangeByScore("delay_tasks", 0, time(), ['LIMIT' => 0, $processLimit]);
foreach ($tasks as $taskData) {
$task = json_decode($taskData, true);
// 从 ZSet 移除(防止重复处理)
$removed = $this->redis->zRem("delay_tasks", $taskData);
if ($removed) {
$this->processTask($task);
}
}
} finally {
$lock->release($requestId);
}
usleep(500000); // 0.5秒
}
}
private function processTask($task) {
try {
echo "执行任务: {$task['id']} - {$task['task']}\n";
// 这里调用实际的业务逻辑
(new TaskHandler())->execute($task);
} catch (Exception $e) {
// 重试机制
$this->retryTask($task);
}
}
}
优点:
- 实现简单,不引入额外组件
- 对 Redis 性能影响小
缺点:
- 任务持久化不足,Redis 重启数据丢失
- 不适合处理大量任务
方案2:基于 RabbitMQ 延迟队列(推荐使用)
use PhpAmqpLib\Connection\AMQPStreamConnection;
use PhpAmqpLib\Message\AMQPMessage;
// 1. 生产者:添加延迟任务
class TaskProducer {
private $channel;
public function __construct($connection) {
$this->channel = $connection->channel();
}
public function addTask($taskName, $data, $delaySeconds = 0) {
// 声明正常队列
$this->channel->queue_declare(
'task_queue',
false, true, false, false
);
// 声明延迟交换机(使用死信交换机实现)
$this->channel->exchange_declare(
'delay_exchange',
'direct',
false, true, false
);
// 声明延迟队列(带 TTL)
$this->channel->queue_declare(
'delay_queue',
false, true, false, false,
false, [
'x-dead-letter-exchange' => ['S', 'task_exchange'],
'x-dead-letter-routing-key' => ['S', 'task_queue'],
'x-message-ttl' => ['I', $delaySeconds * 1000]
]
);
// 绑定延迟队列
$this->channel->queue_bind(
'delay_queue',
'delay_exchange',
'delay'
);
// 发送消息到延迟队列
$message = new AMQPMessage(json_encode([
'task' => $taskName,
'data' => $data
]), [
'delivery_mode' => AMQPMessage::DELIVERY_MODE_PERSISTENT,
'expiration' => $delaySeconds * 1000 // 消息级 TTL
]);
$this->channel->basic_publish($message, 'delay_exchange', 'delay');
echo "任务已添加,延迟 {$delaySeconds} 秒\n";
}
}
// 2. 消费者:执行任务
class TaskConsumer {
private $channel;
public function __construct($connection) {
$this->channel = $connection->channel();
}
public function consume() {
// 声明正常队列
$this->channel->queue_declare(
'task_queue',
false, true, false, false
);
$this->channel->exchange_declare(
'task_exchange',
'direct',
false, true, false
);
$this->channel->queue_bind(
'task_queue',
'task_exchange',
'task_queue'
);
echo "消费者启动,等待处理消息...\n";
$callback = function ($msg) {
$task = json_decode($msg->body, true);
echo "处理任务: {$task['task']}\n";
// 执行实际业务逻辑
$mq = MQ::getInstance();
$mq->ack($msg->delivery_info['delivery_tag']);
};
$this->channel->basic_qos(null, 10, null); // 每次取10个
$this->channel->basic_consume(
'task_queue',
'',
false,
false,
false,
false,
$callback
);
while ($this->channel->is_consuming()) {
$this->channel->wait();
}
}
}
优点:
- 消息持久化,保证可靠
- 支持消息确认、重试机制
- 支持集群扩展
缺点:
- 需要引入 RabbitMQ 组件
方案3:基于 Gearman/Job Server
// Gearman 客户端
class GearmanTask {
private $client;
public function __construct() {
$this->client = new GearmanClient();
$this->client->addServer('127.0.0.1', 4730);
}
// 添加延迟任务
public function addDelayTask($taskName, $data, $delay = 0) {
$task = new GearmanTask();
$task->function = $taskName;
$task->workload = json_encode($data);
$task->unique = uniqid();
// 添加回调
$this->client->addTask($taskName, json_encode($data), null, $unique);
// 使用定时器实现延迟
if ($delay > 0) {
$job = new GearmanJob();
$job->runAfter = time() + $delay;
}
$this->client->runTasks();
}
}
// Gearman Worker
class GearmanWorker {
private $worker;
public function __construct() {
$this->worker = new GearmanWorker();
$this->worker->addServer('127.0.0.1', 4730);
$this->worker->addFunction('send_email', function (GearmanJob $job) {
$data = json_decode($job->workload(), true);
// 执行邮件发送逻辑
echo "发送邮件到: " . $data['email'] . "\n";
});
}
public function run() {
while ($this->worker->work()) {
if ($this->worker->returnCode() != GEARMAN_SUCCESS) {
echo "错误: " . $this->worker->error() . "\n";
}
}
}
}
方案4:使用专业调度框架(最推荐)
使用 Swoole/Workerman 内置的定时器 + Redis 分布式锁:
// 基于 Swoole 的高性能定时器
use Swoole\Timer;
use Swoole\Coroutine;
class SwooleScheduler {
private $redis;
public function run() {
// 主进程运行 WebSocket 服务
$server = new Swoole\WebSocket\Server('0.0.0.0', 9502);
// 通过 WebSocket 发送指令控制定时任务
$server->on('message', function ($server, $frame) {
$cmd = json_decode($frame->data, true);
if ($cmd['type'] == 'schedule') {
// 添加定时任务
Timer::tick($cmd['interval'] * 1000, function () use ($cmd) {
$this->executeTask($cmd['task']);
});
}
});
// 同时设置普通的定时任务
Timer::tick(5000, function() {
// 每5秒执行一次
$this->checkTasks();
});
}
private function executeTask($taskName) {
// 模拟 Redis 分布式锁
$lockKey = "lock_" . $taskName;
$lockAcquired = $this->redis->set($lockKey, 1, ['NX', 'EX' => 60]);
if ($lockAcquired) {
echo "执行任务: {$taskName}\n";
// 释放锁
$this->redis->del($lockKey);
}
}
private function checkTasks() {
echo "检查任务...\n";
// 从数据库或 Redis 获取任务配置
$tasks = $this->getDueTasks();
foreach ($tasks as $task) {
$this->executeTask($task['name']);
}
}
}
实现分布式定时任务的通用模式
模式1:Leader 选举模式
// 只有一台机器(Leader)执行调度
class LeaderElection {
private $redis;
public function checkLeader() {
$isLeader = $this->redis->set('cron_leader', gethostname(), ['NX', 'EX' => 30]);
if ($isLeader) {
echo "我是 Leader,执行调度\n";
// 续期逻辑
Timer::tick(20000, function() {
$this->redis->expire('cron_leader', 30);
});
return true;
}
echo "我是从节点,不执行\n";
return false;
}
}
生产环境最佳实践
任务表设计
CREATE TABLE `scheduled_task` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`task_name` varchar(100) NOT NULL COMMENT '任务名称',
`task_handler` varchar(200) NOT NULL COMMENT '处理器类',
`task_params` json DEFAULT NULL COMMENT '任务参数',
`schedule_type` enum('ONCE','FIXED_RATE','CRON') NOT NULL,
`schedule_date` datetime DEFAULT NULL COMMENT '单次执行时间',
`cron_expr` varchar(50) DEFAULT NULL COMMENT 'cron表达式',
`next_run_at` datetime NOT NULL COMMENT '下次执行时间',
`status` enum('ENABLED','DISABLED') NOT NULL,
`last_exec_time` datetime DEFAULT NULL,
`last_exec_code` tinyint(4) DEFAULT NULL,
`retry_count` int(11) DEFAULT '0',
`created_at` datetime NOT NULL,
`updated_at` datetime NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
完整流程
任务配置(DB) -> 调度器(Leader) -> 任务分发(Message Queue) -> 消费者(Worker) -> 结果反馈(DB)
运维注意事项
- 监控:监控任务执行状态、耗时、失败率
- 告警:任务失败自动告警
- 重试:失败任务自动重试,最多N次
- 人为干预:支持手动触发、暂停、修改定时配置
总结推荐
| 场景 | 推荐方案 |
|---|---|
| 中小团队 | Redis + 分布式锁 |
| 需要可靠消息 | RabbitMQ 延迟队列 |
| 高性能并发 | Swoole + Redis |
| 复杂调度需求 | 专业调度(xxl-job、GoCron 等) |
生产环境强烈建议:使用成熟的分布式调度框架(如 xxl-job),它们已经解决了任务分发、失败重试、监控告警等一整套问题。
对于 PHP,最推荐的组合是:
- MySQL 存调度配置
- Redis 做分布式锁
- RabbitMQ 做消息队列
这样兼顾了可靠性、可扩展性和运维便捷性。
版权声明: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直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播