PHP长时间任务执行方案如何确保夜间清算稳定运行
当需要在PHP中执行长时间运行的任务(如夜间清算系统)时,主要面临三个核心挑战:
执行超时:PHP默认30秒脚本执行超时限制以下是经过生产验证的解决方案: 优势: 关键点: 架构: 清算触发器---》推送任务---》Redis/RabbitMQ---》Worker进程---》数据库 实现步骤: 安装Supervisor管理Worker进程 Worker处理脚本 日志记录: 性能优化: 安全考虑: 通过以上方案的综合应用,可以确保PHP长时间任务(如夜间清算)的稳定可靠执行。根据实际业务需求,可选择最适合的方案组合使用。一、基础配置调整方案
1. 修改PHP配置参数
; php.ini 关键配置
max_execution_time=0 ; 0表示无时间限制
memory_limit=1024M ; 根据需求调整内存限制
ignore_user_abort=On ; 即使客户端断开连接也继续执行2. 脚本内动态设置
<?php
// 脚本开始处设置
set_time_limit(0);// 取消时间限制
ini_set('memory_limit','1024M');
ignore_user_abort(true);// 防止用户中断导致脚本退出
// 确保脚本不会超时
if(function_exists('apache_setenv')){
apache_setenv('no-gzip','1');
}二、可靠执行方案
1. 使用CLI模式 + nohup
# 创建执行脚本 /path/to/nightly_clear.php
# 使用nohup在后台运行
nohup php /path/to/nightly_clear.php > /var/log/clear.log 2>&1&2. 任务分块处理
// 分块处理大数据示例
functionprocessInChunks($totalItems,$chunkSize=1000){
$processed=0;
while($processed<$totalItems){
// 每次处理一个数据块
$items=getItemsFromDB($processed,$chunkSize);
foreach($itemsas$item){
processItem($item);// 实际处理逻辑
$processed++;
}
// 释放内存
unset($items);
gc_collect_cycles();
// 记录进度
logProgress($processed,$totalItems);
}
}三、生产级解决方案
1. 消息队列 + 后台Worker
; /etc/supervisor/conf.d/clear.conf
[program:clear_worker]
command=php /path/to/worker.php
process_name=%(program_name)s_%(process_num)02d
numprocs=4
autorestart=true// worker.php
while(true){
$task=$queue->pop();// 从队列获取任务
if($task){
try{
processTask($task);
$queue->ack($task);// 确认任务完成
}catch(Exception$e){
logError($e);
$queue->retry($task);// 重试机制
}
}
sleep(1);// 避免CPU空转
}2. 定时任务 + 断点续传
// 清算脚本示例
classClearSystem{
constSTATE_FILE='/tmp/clear_state.json';
publicfunctionrun(){
$state=$this->loadState();
// 断点续传逻辑
while($batch=$this->getNextBatch($state)){
try{
$this->processBatch($batch);
$state['last_id']=end($batch)->id;
$this->saveState($state);
}catch(Exception$e){
logError($e);
break;
}
}
$this->cleanUp();
}
privatefunctionloadState(){
returnfile_exists(self::STATE_FILE)
?json_decode(file_get_contents(self::STATE_FILE),true)
:['last_id'=>0];
}
}四、高级保障方案
1. 双保险执行机制
# crontab -e
# 主任务
03 * * * /usr/bin/flock -xn /tmp/clear.lock -c'/usr/bin/php /path/to/clear.php'
# 备用检查(5分钟后检查是否执行成功)
53 * * * /usr/bin/flock -sn /tmp/clear.lock -c'/usr/bin/php /path/to/check_and_retry.php'2. 监控与报警集成
// 执行完成后发送通知
functionnotifyResult($success,$message=''){
$webhook='https://hooks.example.com/alert';
$data=[
'task'=>'nightly_clear',
'time'=>date('Y-m-d H:i:s'),
'status'=>$success?'success':'failed',
'message'=>$message
];
$ch=curl_init($webhook);
curl_setopt($ch,CURLOPT_POST,1);
curl_setopt($ch,CURLOPT_POSTFIELDS,json_encode($data));
curl_exec($ch);
curl_close($ch);
}五、最佳实践建议
functionlog($message,$context=[]){
file_put_contents(
'/var/log/clear.log',
json_encode([
'time'=>microtime(true),
'message'=>$message,
'context'=>$context
]).PHP_EOL,
FILE_APPEND
);
}json
格式便于分析unset()
释放内存六、完整示例方案
清算系统实现框架
<?php
classNightlyClear{
private$db;
private$logFile='/var/log/nightly_clear.log';
private$lockFile='/tmp/nightly_clear.lock';
publicfunction__construct(){
$this->init();
}
privatefunctioninit(){
// 检查是否已在运行
if(file_exists($this->lockFile)){
$this->log("任务已在运行中");
exit;
}
// 创建锁文件
file_put_contents($this->lockFile,getmypid());
// 注册信号处理器
pcntl_signal(SIGTERM,[$this,'shutdown']);
pcntl_signal(SIGINT,[$this,'shutdown']);
// 初始化数据库连接
$this->db=newPDO(/* 参数 */);
$this->db->setAttribute(PDO::ATTR_ERRMODE,PDO::ERRMODE_EXCEPTION);
}
publicfunctionrun(){
try{
$this->log("开始夜间清算");
// 分阶段处理
$this->clearStage1();
$this->clearStage2();
$this->generateReports();
$this->log("清算完成");
}catch(Exception$e){
$this->log("清算失败: ".$e->getMessage(),[
'trace'=>$e->getTraceAsString()
]);
throw$e;
}finally{
$this->shutdown();
}
}
privatefunctionclearStage1(){
// 分块处理逻辑
$offset=0;
$limit=1000;
while(true){
$stmt=$this->db->prepare("SELECT * FROM transactions LIMIT ? OFFSET ?");
$stmt->execute([$limit,$offset]);
$rows=$stmt->fetchAll(PDO::FETCH_ASSOC);
if(empty($rows))break;
$this->db->beginTransaction();
try{
foreach($rowsas$row){
// 处理每笔交易
$this->processTransaction($row);
}
$this->db->commit();
}catch(Exception$e){
$this->db->rollBack();
throw$e;
}
$offset+=$limit;
$this->log("已处理 {$offset} 条记录");
// 每处理10000条休息1秒
if($offset%10000===0){
sleep(1);
}
}
}
privatefunctionshutdown(){
// 清理资源
if(file_exists($this->lockFile)){
unlink($this->lockFile);
}
$this->log("关闭清算进程");
}
privatefunctionlog($message,$context=[]){
$entry=[
'time'=>date('Y-m-d H:i:s'),
'pid'=>getmypid(),
'message'=>$message,
'context'=>$context
];
file_put_contents($this->logFile,json_encode($entry).PHP_EOL,FILE_APPEND);
}
}
// 执行入口
$clear=newNightlyClear();
$clear->run();Supervisor配置示例
[program:nightly_clear]
command=php /path/to/NightlyClear.php
directory=/path/to
user=www-data
autostart=true
autorestart=false
startsecs=0
startretries=0
stdout_logfile=/var/log/nightly_clear.out.log
stdout_logfile_maxbytes=50MB
stderr_logfile=/var/log/nightly_clear.err.log
stderr_logfile_maxbytes=50MB七、应急处理方案
手动干预脚本
数据一致性检查// check_and_recover.php
$lastLog=file_get_contents('/var/log/nightly_clear.log');
$lastEntry=json_decode(end(explode(PHP_EOL,trim($lastLog))),true);
if(strtotime($lastEntry['time'])<strtotime('-1 hour')){
// 超过1小时无更新,认为任务卡死
exec('pkill -f NightlyClear.php');
exec('php /path/to/NightlyClear.php --resume');
sendAlert("清算任务已重启,最后记录时间: ".$lastEntry['time']);
}-- 清算后检查SQL
SELECT
(SELECTCOUNT(*)FROMtransactionsWHERE cleared =0)AS remaining,
(SELECTSUM(amount)FROMtransactions)AS total_amount,
(SELECTSUM(amount)FROM cleared_transactions)AS cleared_amount
FROM dual;
发表评论