使用XXL-JOB时,如何避免多台服务器重复调度任务?|工作踩坑系列

1.XXL-JOB介绍

XXL-JOB是一个分布式任务调度平台,其核心设计目标是开发迅速、学习简单、轻量级、易扩展。支持通过Web页面对任务进行CRUD操作,操作简单,一分钟上手;支持动态修改任务状态、启动/停止任务,以及终止运行中任务,即时生效。

  官方地址中文版:https://www.xuxueli.com/xxl-job

  Gitee地址:https://gitee.com/xuxueli0323/xxl-job

2.应用场景介绍

我们将XXL-JOB调度系统部署在两台服务器实例上。使用XXL-JOB调度系统主要是执行各个业务系统的脚本,如发送短信、变更用户积分、生成BI报表等需求。

3.问题回顾

job系统每天都正常的运行着,没发生过什么大问题。

  有天早上测试妹子向研发部门反馈问题:用户的每日积分、每周积分怎么变多了?刚好多了一倍,你们快看看吧,急!

  好家伙,这还了得,立马咨询坐在我旁边的大佬,看之前是否发生过这样的问题,大佬说很少发生,几乎没有。

  于是我找到处理每日积分的地方,发现有一个定时脚本在执行,每天晚上快凌晨时会执行一次,我随后登录job系统后台,查询该脚本,搜索该日期,发现了一个奇怪的问题,如图:

WX20210703-173403@2x.png

通过该截图发现了两个问题:

  1.该脚本任务在统一时间被调度了两次,为什么被调度了两次呢?因为我们有两台服务器实例,同时触发了调度任务。

  2.该脚本在间隔不到一分钟内重复执行了两次。

4.解决方案

查看代码得知,这个脚本是每天只执行一次,但代码层面并没有判断是否已经执行过,多次调用就会多次计算。这就导致了计算数据时会出错,比如用户积分本应该减少10积分,由于调度系统调度了两次,这个用户的积分就减少了20积分。找到具体原因后就好解决了,其实就是避免脚本重复执行就可以了。

4.1使用XXL-JOB配置来避免重复调度(推荐)

官网推荐的处理办法是利用“任务调度锁表“来避免集群同时调度的情况。

4.2在代码层面避免重复调度

方案1:

在该脚本执行的地方判断下更新时间,判断今天是否已经执行过,这样不就可以搞定啦。
但这种处理会有另一个问题,还有一些脚本代码也没有判断是否已执行过。那需要修改的地方就很多了,需要向其他更便捷的办法。

方案2:

先在配置文件中定义哪些脚本不能被重复调度、定义Redis过期时间。使用Redis记录该脚本已经执行过,第二次执行时判断是否已执行,如果已执行则退出执行流程。

实现流程:

1.先定义配置文件

<?php  
    return [
        //定义延迟调用的接口
        'job_delay_request_api_config' => [
           'cache_time' => 1800,//过期时间 秒
           'list' => [
                'UserChangeLog/day',//每日积分统计
                'UserChangeLog/week',//每周积分统计
           ],
        ],
    ];
?>

2.在父类控制器中判断脚本是否已执行

<?php
    public function __construct()
    {
        //获取当前调度的控制器和方法名称,用来当作key值
        $apiUrl = CONTROLLER_NAME . '/' . ACTION_NAME;
        //获取配置项中定义延迟调用的接口
        $config = config('job_delay_request_api_config');
        //判断当前调用的接口不在延迟脚本配置数组中,可以执行,返回即可
        if (!in_array($apiUrl, $config['list'])) {
            return;
        }
        //判断是否已调用过该接口
        $key = 'job_delay_request_api:' . $apiUrl;
        $redis = new Redis();
        $result = $redis->get($key);
        //判断已调用,则不能再次调用,需等待redis过期,此处停止脚本运行
        if (!empty($result)) {
            exit('该调度任务已执行,执行时间:' . date('Y-m-d H:i:s', $result));
        }
        //标记job任务已执行,记录执行时间
        $redis->set($key, $config['cache_time'], time());
    }
?>

5.总结

目前采用的是4.2章节中的方案2,通过读取预先定义的配置,在代码层面判断做拦截,可以达到防止任务被重复调度的问题,并且可以指定哪些脚本需要被拦截。感谢阅读,我们下次再见。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值