如何在虚拟主机上开发PHP定时任务

前面都是解决问题的过程,如果只需要最终解决方案可直接跳到本文最下方。

    前段时间需要做个带有每日积分转换代金券的项目,其核心功能是每日定时将所有用户的积分按比例转换成代金券额。本来不是什么难事,swoole、CronTab等一堆解决方案,但客户用的是非自有服务器(即虚拟主机,由服务器商托管,无远程桌面,无php.ini修改权限,只有FTP),所以只能通过纯PHP代码来实现。

    最开始网上找的方案是用pcntl_alarm()函数,但虚拟主机本身环境不支持pcntl扩展,也没有新的扩展权限,只能继续再找。

    然后找到有用while()+sleep()的方式,具体代码如下:

ignore_user_abort();//关掉浏览器后脚本继续执行
set_time_limit(0);//设置脚本执行时间无上限
ini_set('memory_limit','512M');//内存限制设置
$interval_time = 10;//间隔为10秒
do{
    /*
    *业务逻辑
    */
    sleep($interval_time);//程序暂时睡眠
}while(true);

    初期测试并没有什么问题,但时间一长就有很大概率程序中断,不能持续稳定的执行定时显然不行。

    在参考过一些网上资料与本地调试后,找到一个相较于前一个方法稳定的方式,使用采集函数file_get_contents()代替while()循环,具体代码如下:

ignore_user_abort();//关掉浏览器后脚本继续执行
set_time_limit(0);//设置脚本执行时间无上限
ini_set('memory_limit','512M');//内存限制设置
$interval_time = 10;//间隔为10秒
/*
*业务逻辑
*/
sleep($interval_time);//程序暂时睡眠
file_get_contents(demo.php)//采集本身,demo.php为本文件名

    还是一样,初期测试并没有什么问题,但后续出现两个问题:一是业务逻辑运行本身需要时间,单次耗时可能不算什么,但一旦累计好几天很可能就会造成时间大偏差,例如原本预计晚上12点运行的,经过几次定时任务后可能就会推后到1点甚至2点才运行。二是成功运行几次后(几天后),程序还是有可能被中断。

    经过再次的参考网上资料与本地调试后,发现结合MySql(换成Memcache或Redis也行,思路不变)使用可以解决问题,具体代码如下:

<?php
require(dirname(__FILE__)."/load.php");//load.php里包含了一些连接数据库的函数,例如执行SQL语句的函数sqlone()

ignore_user_abort();//关掉浏览器后脚本继续执行
set_time_limit(0);//设置脚本执行时间无上限
ini_set('memory_limit','512M');//内存限制设置
$interval_time = 86400;//循环执行时间间隔
$url="http://".$_SERVER['HTTP_HOST'].$_SERVER['REQUEST_URI'];//获取本页面URL
$now = time();//现在的时间戳

$everday = sqlone("select * from `everday` order by id desc limit 1","查询最后一次执行循环任务的信息");
if(!file_exists(dirname(__FILE__).'/sql_ecex_'.date("d",strtotime($everday['logic_date'])))){exit;}//验证日期文件夹名(作用:过滤多余执行脚本)
if(!$everday || !$everday['exec_time']){//如果没有执行过循环任务
	exec_everday();//则立即执行一次
}else{
	$sleep = $now-strtotime($everday['logic_date']);//计算出距上次执行时间已经过了多少秒
	if($sleep<$interval_time){//如果还没超过时间间隔
		sleep($interval_time-$sleep+rand(0,600));//则休眠对应时间(加上随机数延迟,防止并发)
	}else{//如果已超过时间间隔
		while($sleep>=$interval_time){
			$sleep-=$interval_time;
			exec_everday($sleep);//则执行一次或多次
		}
	}
}

function exec_everday($interval=0){
	global $now;
	$logic_date = $now-$interval;//逻辑时间戳=现在-时间差
	rename("sql_ecex_".date("d",strtotime("-1 day",$logic_date)),"sql_ecex_".date("d",$logic_date));//更改日期文件夹名
	sqlone("INSERT INTO `everday` VALUES (NULL,'".$now."','".date("Y-m-d H:i:s",$now)."','".date("Y-m-d H:i:s",$logic_date)."','0','');","插入定时任务记录");
	/*
	*业务逻辑
	*/
}
file_get_contents($url);//重新加载

    以上脚本程序如果需要正常运行,需要在同目录新建一个名为“sql_ecex_下次执行日期”的文件夹,例如下次执行是在是3月20日晚上11点30,那么就新建一个sql_ecex_20的文件夹,如果想中断下次循环,只需删除该文件夹即可。其中执行时间与逻辑时间分别指的是:程序实际执行任务的时间和程序本应该执行任务的时间。

    最终,我们将其简化,让其适用性不再局限每日,并且用文件记录的方式代替MySql来实现,具体代码如下:

<?php
ignore_user_abort();//关掉浏览器后脚本继续执行
set_time_limit(0);//设置脚本执行时间无上限
ini_set('memory_limit','512M');//内存限制设置
$interval_time = 86400;//循环执行时间间隔
$url="http://".$_SERVER['HTTP_HOST'].$_SERVER['REQUEST_URI'];//获取本页面URL
$now = time();//现在的时间戳
$everdaytime = file_exists("everdaytime.txt")?file_get_contents("everdaytime.txt"):"";//上次任务执行实际时间
$logictime = file_exists("logictime.txt")?file_get_contents("logictime.txt"):"";//上次任务执行逻辑时间
if(!$everdaytime){//如果没有执行过循环任务
    exec_everday();//则立即执行一次
}else{
    $sleep = $now-strtotime($logictime);//计算出距上次逻辑执行时间已经过了多少秒
    if($sleep<$interval_time){//如果还没超过时间间隔
        sleep($interval_time-$sleep+rand(0,600));//则休眠对应时间(加上随机数延迟,防止误操作执行了多次导致并发)
        $logictime_now = file_exists("logictime.txt")?file_get_contents("logictime.txt"):"";//休眠后的逻辑时间
        if($logictime!=$logictime_now){exit;}//如果两个逻辑时间不相等,则说明休眠时间段内有程序执行过,本程序为多余程序,遂过滤掉
    }else{//如果已超过时间间隔
        while($sleep>=$interval_time){
            $sleep-=$interval_time;
            exec_everday($sleep);//则执行一次或多次
        }
    }
}
/*
 * 执行每日定时任务与写入执行记录
 * $interval距上次逻辑执行时间的时间差
 */
function exec_everday($interval=0){
    global $now;//获取当前时间戳
    $logic_date = $now-$interval;//逻辑时间戳=现在-时间差
    file_put_contents("everdaytime.txt",date("Y-m-d H:i:s",$now));//写入本次实际执行时间
    file_put_contents("logictime.txt",date("Y-m-d H:i:s",$logic_date));//写入本次逻辑执行时间
    //业务逻辑...
}
file_get_contents($url);//循环下一次执行本程序
exit;//删除本文件后程序将停止运行

    定时任务每执行过一次后会在同目录新建或替换两个文件,everdaytime.txt记录上次任务实际执行时间,logictime.txt记录上次任务本应该执行时间。将两个文件删除后就不会执行下次任务了,也就达到了终止的目的。

    举一反三:两个执行时间其实可以用GET参数传值,最后file_get_contents也可以换成include。

    如需转载请注明出处:https://blog.csdn.net/zhouchang224/article/details/88706689

  • 3
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值