定时任务使用指南


 

定时任务常见的使用场景

指定时间点执行

  • 铁路定时放票,美团定时发放优惠券、红包
  • 商品定时上下架(开售/结束),活动定时开始/结束
  • 定时推送营销信息
  • 修复历史数据
     

间隔指定时间自动执行

  • 同步数据:从第三方拉取信息,同步到我们自己的库中,比如定时查询合作银行的转账进度、合作快递的物流进度。
  • 更新状态:超过三十分钟未支付自动取消订单,到期自动解冻账号。
  • 消息通知:扫描即将过期的优惠券,即将开始的车票、机票、电影票,通知用户;
  • 备份数据、统计账单流水、更新缓存
  • 检测节点心跳,确定节点状态、是否可用

 

定时任务常见组件

1、jdk自带的 java.util.Timer
使用简单、功能单一,对复杂任务、多任务并发执行支持差,适用于简单的单个任务,eg. 间隔指定时间清除本地缓存。
 

2、jdk自带的 java.util.concurrent.ScheduledExecutorService
支持线程池,对多任务支持好,适合执行多个单机任务。

 

3、springboot自带的Schedule
使用简单,与springboot无缝接入,适合用于springboot项目的简单单机任务。
 

4、Quartz
专业的定时任务框架,功能强大,使用略微麻烦,适合用于复杂的单机任务。
 

以上三个对分布式的支持都差,集群部署服务时容易出现多个节点重复执行定时任务的问题,通常只用于实现单机定时任务。可以自行实现分布式(eg.使用zk做分布式协调)、将任务状态持久化(db),但比较麻烦。
 

5、elastic-job(推荐)
当当网开源的弹性分布式任务调度框架,已成为apache ShardingSphere的子项目;功能丰富强大,提供web管理界面,新版界面使用 Vue + ElementUI 编写,漂亮美观,文档齐全,接入、使用都十分简便;但需要引入zk实现分布式协调,部署略微麻烦。
 

6、xxl-job
大众点评开源的弹性分布式任务调度框架,功能丰富强大,轻量,提供web管理界面,springboot + freemarker 界面一般,文档丰富,使用db持久化任务状态,无需额外引入组件。

elastic job、xxl-job 二者相比,不看github star的话,elastic job 的优势在于使用体验,管理界面比 xxl-job 漂亮、好用,接入也比 xxl-job 简便;xxl-job 的优势在于无需额外引入组件。如果项目本身要引入zk,更推荐使用 elastic-job lite实现分布式定时任务。

 

jdk自带的Timer

1、编写定时任务:继承抽象类TimerTask,实现run()方法

public class MyTask extends TimerTask {

    @Override
    public void run() {
		try {
			//...
		} catch (Exception e) {   //必须catch异常

		}
    }

}

 

2、调度、执行

//创建time实例:会创建 TimerThread、TaskQueue 实例,并调用 thread.start(); 启动线程
Timer timer = new Timer();

MyTask myTask1 = new MyTask();
MyTask myTask2 = new MyTask();

//第2个参数指定多少ms后开始初次执行,第3个参数执行间隔周期
timer.schedule(myTask1, 10000L, 60000L);
timer.scheduleAtFixedRate(myTask2, 5000L, 1000L);

//当前线程继续往下执行

Timer内部有2个成员变量:Thread、Queue,一个Time实例对应一个线程、队列,此Time实例所有的任务都会添加到队列中,由一个线程负责执行。如果某个任务执行时抛出异常,会造成线程执行中断,不会再执行后续批次的任务,所以实现 TimerTask#run 方法时,一定要catch异常。

schedule()、scheduleAtFixedRate()的区别

  • schedule:间隔周期从上次执行完毕时开始算,如果期间有任务执行耗时较长,耽搁了其它任务的执行,其它任务的执行时间会推迟、顺延。
  • scheduleAtFixedRate:间隔周期从上次执行开始时算,执行时间点是可预知的、固定的,如果期间有任务执行耗时较长,耽搁了其它任务的执行,其它任务会补上之前未执行的场次(连续执行多次)。

通常使用 schedule,不使用 scheduleAtFixedRate。

//取消指定的定时任务
myTask.cancel();

//取消此timer中所有的定时任务
timer.cancel();

 

jdk自带的ScheduledExecutorService

Timer位于 java.util 包下,使用单线程执行所有任务,多任务并发执行支持差,可靠性低,使用Timer时会提示“使用ScheduledExecutorService 替代Timer”。

ScheduledExecutorService 位于 java.util.concurrent 包下,支持线程池、多种时间单位,对多任务并发执行支持好。

//虽然ScheduledThreadPoolExecutor继承了ThreadPoolExecutor,但继承的线程池调优参数不会生效,指定核心线程数即可
ScheduledThreadPoolExecutor scheduledThreadPoolExecutor = new ScheduledThreadPoolExecutor(7);

//还可以指定 ThreadFactory、任务拒绝策略(默认AbortPolicy 直接抛异常)
ScheduledExecutorService scheduledThreadPoolExecutor = new ScheduledThreadPoolExecutor(7,
        new BasicThreadFactory.Builder().namingPattern("task-thread-pool-%d").daemon(false).build(),
        new ThreadPoolExecutor.AbortPolicy());


//从ThreadPoolExecutor继承的方法
scheduledThreadPoolExecutor.shutdown();
scheduledThreadPoolExecutor.shutdownNow();
//schedule系列方法返回值都是ScheduledFuture,可用于任务的取消、状态判断、返回值获取


//schedule只执行1次,指定延迟执行时间。支持Runnable(无返回值)、Callable(有返回值)
ScheduledFuture<?> scheduledFuture = scheduledThreadPoolExecutor.schedule(new MyRunnable(), 10, TimeUnit.SECONDS);
ScheduledFuture<String> scheduledFuture = scheduledThreadPoolExecutor.schedule(new MyCallable(), 10, TimeUnit.SECONDS);

//实质是调用 schedule(command, 0, NANOSECONDS) 立刻执行1次,无返回值,只支持 Runnable
scheduledThreadPoolExecutor.execute(new MyRunnable());


//固定频率执行:距上次执行开始时 固定时间后执行下一次。如果执行时间较长,超过了间隔时间,则会在执行完毕后立刻执行下一次,不会出现多次并发执行
ScheduledFuture<?> scheduledFuture = scheduledThreadPoolExecutor.scheduleAtFixedRate(new MyRunnable(), 10, 10, TimeUnit.SECONDS);
//固定延迟执行:距上次执行完毕时 固定时间后执行下一次
ScheduledFuture<?> scheduledFuture = scheduledThreadPoolExecutor.scheduleWithFixedDelay(new MyRunnable(), 10, 10, TimeUnit.SECONDS);

//需要多次执行的任务,通常使用 scheduleWithFixedDelay
//任务是执行完毕,正常执行完毕、异常终止、已取消 都算执行完毕
boolean isDone = scheduledFuture.isDone();

//取消任务,参数指定如果任务正在执行是否尝试中断,返回取消结果
//如果任务已经执行完毕、已经取消、或由于其它原因无法取消,返回false
boolean cancel = scheduledFuture.cancel(false);
boolean cancelled = scheduledFuture.isCancelled();

 

springboot自带的定时任务

参考:https://blog.csdn.net/chy_18883701161/article/details/120387438

 

Quartz

quartz的体系结构

3个核心概念

  • 任务 Job
  • 触发器 Trigger
  • 调度器 Scheduler
     

在这里插入图片描述
 
quartz提供了2种作业存储类型

  • RAMJobStore :默认使用的作业存储类型,将任务调度状态保存在内存中,性能好但不具备持久性,发生故障时会丢失所有的任务状态信息。
  • JDBC作业存储:将任务调度保存到数据库中,需要自行建一些表,表名、字段名都是固定的,quartz会自动持久化任务数据到数据库中,发生故障时也能恢复调度现场,性能略差。

 

springboot整合quartz

1、依赖
创建项目时勾选 I/O -> Quartz Scheduler,也可以手动添加依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-quartz</artifactId>
</dependency>

在yml中输入quartz即可查看quartz的配置项,一般使用默认配置即可。
 

2、编写定时任务,一个类对应一个定时任务

import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.springframework.scheduling.quartz.QuartzJobBean;

/**
  * 继承QuartzJobBean,重写executeInternal(),无需要放到spring容器中
  */
public class XxxJob extends QuartzJobBean {
    
    @Override
    protected void executeInternal(JobExecutionContext jobExecutionContext) throws JobExecutionException {
        //...
    }

}

 

3、配置定时任务,一个定时任务对应一个配置类

@Configuration
public class XxxJobConfig {

    /**
     * 创建JobDetail实例,放到spring容器中,一个job对应一个JobDetail实例
     */
    @Bean
    public JobDetail xxxJobDetail() {
        //绑定Job
        return JobBuilder.newJob(XxxJob.class)
                //设置此job实例的唯一标识(name、所属分组)
                .withIdentity("xxxJob", "defaultJobGroup")
                //如果此JobDetail实例没有关联Trigger,也不删除此JobDetail实例
                .storeDurably()
                .build();
    }

    /**
     * 创建trigger实例,放到spring容器中,一个job对应一个trigger实例
     */
    @Bean
    public Trigger xxxTrigger() {
        return TriggerBuilder.newTrigger()
                //设置此trigger实例的唯一标识(name、所属分组)
                .withIdentity("xxxTrigger", "defaultTriggerGroup")
                //绑定要调度的job
                .forJob("xxxJob", "defaultJobGroup")
                //调度计划
                .withSchedule(CronScheduleBuilder.cronSchedule("*/10 * * * * ?"))
                .startNow()
                .build();
    }

}

 

Elastic Job(推荐)

官网:https://shardingsphere.apache.org/elasticjob/
github:https://github.com/apache/shardingsphere-elasticjob

ElasticJob包含2个子项目

  • ElasticJob-Lite:轻量级无中心化解决方案,只需要引入ZK,;
  • ElasticJob-Cloud:使用 ZK + Mesos + Docker 的解决方案,额外提供资源治理、应用分发以及进程隔离等服务,跟 Lite 的区别只是部署方式不同,它们使用相同的 API,只要开发一次。

通常使用 Lite 即可,下文只介绍 Lite 的使用方式

 

springboot整合elasticjob lite

1、添加依赖

<dependency>
    <groupId>org.apache.shardingsphere.elasticjob</groupId>
    <artifactId>elasticjob-lite-spring-boot-starter</artifactId>
    <version>3.0.0</version>
</dependency>

 

2、编写定时任务
elastic job提供了2种类型的定时任务:SimpleJob、DataflowJob,根据需要实现即可,都需要放到spring容器中

@Component
public class MySimpleJob implements SimpleJob {

    @Override
    public void execute(ShardingContext context) {
        //...
    }

}
@Component
public class MyDataflowJob implements DataflowJob<Order> {

    @Override
    public List<Order> fetchData(ShardingContext context) {
        //...
    }

    @Override
    public void processData(ShardingContext shardingContext, List<Order> orders) {
        // ...
    }

}

 

3、yml

建议 elastic-job 的配置放在单独的 elastic-job.yml 文件中,然后在 application.yml 中引入

elasticjob:
  #注册中心配置
  regCenter:
    #zk节点
    serverLists: localhost:2181
    #命名空间,即存储elastic job数据要zk node
    namespace: elasticjob-task
  #定时任务配置
  jobs:
    #自定义的任务名称
    mySimpleJob:
      #对应的类
      elasticJobClass: com.chy.mall.job.MySimpleJob
      cron: 0/10 * * * * ?
      #自定义参数
      jobParameter: xxx
      #分片总数
      shardingTotalCount: 3
      #自定义的分片参数
      shardingItemParameters: 0=Beijing,1=Shanghai,2=Guangzhou
    myDataFlowJob:
      elasticJobClass: com.chy.mall.job.MyDataflowJob
      cron: 0/10 * * * * ?
      shardingTotalCount: 3

如果定时任务已经注册到zk,后续在yml中修改cron表达式、自定义参数、分片总数这些任务配置项是不会生效的,需要到web控制台修改,来更新zk保存的任务信息。

 

邮件预警(可选)

定时任务执行出错时,自动发送邮件通知指定人员

<dependency>
    <groupId>org.apache.shardingsphere.elasticjob</groupId>
    <artifactId>elasticjob-error-handler-email</artifactId>
     <version>3.0.0</version>
</dependency>
elasticjob:
  regCenter:
    ...
  jobs:
    ...
    jobErrorHandlerType: EMAIL 
  props:
    email:
      host: host
      port: 465
      username: username
      password: password
      useSsl: true
      subject: ElasticJob error message
      from: from@xxx.xx
      to: to1@xxx.xx,to2@xxx.xx
      cc: cc@xxx.xx
      bcc: bcc@xxx.xx
      debug: false

 

web控制台的部署

1、下载 lite ui 的二进制tar包(不是源码包),版本尽量与 maven 引入的 lite 版本保持一致
在这里插入图片描述
 

2、解压,根据需要修改 conf/application.properties

#使用的端口
server.port=8088

#账号、密码,root账号可修改配置,guest游客账号只能看、不能修改
auth.root_username=root
auth.root_password=root
auth.guest_username=guest
auth.guest_password=guest

#事件追踪数据源配置,这个不是必需的,数据库连接后续可以在web界面配置
#配置了elastic job会自动创建几张表,用于记录任务执行的历史记录、结果、总结摘要,便于分析,建议配上
#默认值引入了h2、postgresql的数据库驱动,如果要配置为mysql等其它数据库,可以把对应的驱动jar包放到 ext-lib目录下
spring.datasource.default.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.default.url=jdbc:mysql://localhost:3306/mall?serverTimezone=UTC
spring.datasource.default.username=root
spring.datasource.default.password=root
spring.jpa.show-sql=false

 

3、执行bin下的start脚本启动应用,浏览器访问 127.0.0.1:8088,输入账号、密码

4、添加注册中心,信息与yml配置的注册中心保持一致

 

常见操作说明

1、状态

  • 分片待调整:新的定时任务注册上去,尚未触发过即为此状态,到cron表达式指定周期时自动触发过1次,状态就正常了。
  • 失效:暂时下线该定时任务,后续还可以上线。
  • 终止:永久下线该定时任务,不能再上线。
     

2、分片总数、参数
这些配置参数都可以从上下文中获取到,用于向定时任务传递额外的配置,均可以在web控制台进行配置

#自定义参数
jobParameter: xxx
#分片总数,此定时任务每次触发时使用几个分片来执行
shardingTotalCount: 3
#自定义的分片参数,index=value,数量与分片总数保持一致
shardingItemParameters: 0=Beijing,1=Shanghai,2=Guangzhou
@Component
public class MySimpleJob implements SimpleJob {

    @Override
    public void execute(ShardingContext context) {
        //自定义参数
        String jobParameter = context.getJobParameter();
        //分片总数
        int shardingTotalCount = context.getShardingTotalCount();
        //当前分片的index
        int shardingItem = context.getShardingItem();
        //当前分片项对应的值
        String shardingParameter = context.getShardingParameter();

        //...
    }

}

在这里插入图片描述
分片总数:指定该定时任务触发1次时,需要分配给几个副本执行。假设分片总数设置为3,定时任务所在服务

  • 部署了5个实例,分片分散情况往往是 1+1+1 ,触发1次时这3个实例会分别执行1次;
  • 部署了2个实例,分片分散情况往往时 1+2,触发1次时1个实例执行1次,另一个实例执行2个;
  • 部署了1个实例,这个定时任务的所有分片都集中在这个实例上,触发1次时这个实例会执行3次;

分片总数通常设置为1,避免触发时重复执行。

 

如果定时任务要处理的数据量级很大,可以将数据分配给多个分片共同处理,shardingItemParameters 指定分段(id分段、状态划分等),定时任务中判断当前分片对应的 shardingItemParameters,做相应处理

shardingTotalCount: 3
shardingItemParameters: 0=待付款,1=待发货,2=待退款
@Component
public class MySimpleJob implements SimpleJob {

    @Override
    public void execute(ShardingContext context) {
        switch (context.getShardingParameter()) {
            case "待付款":
                //...
                break;
            case "待发货":
                //...
                break;
            case "待退款":
                //...
                break;
            default:
        }
    }

}

 

xxl-job

官网:https://www.xuxueli.com/xxl-job
github:https://github.com/xuxueli/xxl-job
 

web控制台的部署

1、下载最新稳定版的压缩包,解压,在IDEA中导入

父项目 xxl-job包含3个子项目

  • xxl-job-core:核心模块,提供公共依赖
  • xxl-job-admin:调度中心,提供web界面的控制台
  • xxl-job-executor-samples:执行器(定时任务)示例,不需要的可以删除这个模块。包含2个子项目,一个是springboot版本的示例,一个是frameless无框架版本的示例。
     

2、执行doc/db下的初始化sql脚本
 

3、修改 xxl-job-admin 的配置文件

  • application.properties:根据需要调整应用使用的端口、访问路径、数据库配置、报警邮箱配置。邮箱配置可以不管,但不能注释掉。
  • logback.xml 日志配置

xxl-job-admin 集群部署时,各 xxl-job-admin 节点务必连接同一个mysql数据库、节点机器的时钟务必保持一致;如果 mysql 做主从,xxl-job-admin 各节点务必强制走主库。
 

4、打包部署,访问 http://localhost:8080/xxl-job-admin 登录系统,默认账号密码 admin/123456
 

5、初始化系统数据

  • 在用户管理中修改密码、增删用户
  • 在任务管理中删除示例任务
  • 在执行器管理中增删、修改执行器

 

编写定时任务

从springboot版本的示例中copy所需内容

1、添加依赖,最好与控制台的版本保持一致

<dependency>
    <groupId>com.xuxueli</groupId>
    <artifactId>xxl-job-core</artifactId>
    <version>2.3.1</version>
</dependency>

 

2、复制 application.properties 的相关配置项

### 调度中心部署根地址 [选填]:如调度中心集群部署存在多个地址则用逗号分隔。执行器将会使用该地址进行"执行器心跳注册"和"任务结果回调";为空则关闭自动注册;
xxl.job.admin.addresses=http://127.0.0.1:8080/xxl-job-admin

### 执行器通讯TOKEN [选填]:非空时启用;
xxl.job.accessToken=

### 执行器AppName [选填]:执行器心跳注册分组依据;为空则关闭自动注册
xxl.job.executor.appname=xxl-job-executor-sample

### 执行器注册 [选填]:优先使用该配置作为注册地址,为空时使用内嵌服务 ”IP:PORT“ 作为注册地址。从而更灵活的支持容器类型执行器动态IP和动态映射端口问题。
xxl.job.executor.address=

### 执行器IP [选填]:默认为空表示自动获取IP,多网卡时可手动设置指定IP,该IP不会绑定Host仅作为通讯实用;地址信息用于 "执行器注册" 和 "调度中心请求并触发任务";
xxl.job.executor.ip=

### 执行器端口号 [选填]:小于等于0则自动获取;默认端口为9999,单机部署多个执行器时,注意要配置不同执行器端口;
xxl.job.executor.port=9999

### 执行器运行日志文件存储磁盘路径 [选填] :需要对该路径拥有读写权限;为空则使用默认路径;
xxl.job.executor.logpath=/data/applogs/xxl-job/jobhandler

### 执行器日志文件保存天数 [选填] : 过期日志自动清理, 限制值大于等于3时生效; 否则, 如-1, 关闭自动清理功能;
xxl.job.executor.logretentiondays=30

 

3、复制配置类

import com.xxl.job.core.executor.impl.XxlJobSpringExecutor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * xxl-job config
 *
 * @author xuxueli 2017-04-28
 */
@Configuration
public class XxlJobConfig {
    private Logger logger = LoggerFactory.getLogger(XxlJobConfig.class);

    @Value("${xxl.job.admin.addresses}")
    private String adminAddresses;

    @Value("${xxl.job.accessToken}")
    private String accessToken;

    @Value("${xxl.job.executor.appname}")
    private String appname;

    @Value("${xxl.job.executor.address}")
    private String address;

    @Value("${xxl.job.executor.ip}")
    private String ip;

    @Value("${xxl.job.executor.port}")
    private int port;

    @Value("${xxl.job.executor.logpath}")
    private String logPath;

    @Value("${xxl.job.executor.logretentiondays}")
    private int logRetentionDays;


    @Bean
    public XxlJobSpringExecutor xxlJobExecutor() {
        logger.info(">>>>>>>>>>> xxl-job config init.");
        XxlJobSpringExecutor xxlJobSpringExecutor = new XxlJobSpringExecutor();
        xxlJobSpringExecutor.setAdminAddresses(adminAddresses);
        xxlJobSpringExecutor.setAppname(appname);
        xxlJobSpringExecutor.setAddress(address);
        xxlJobSpringExecutor.setIp(ip);
        xxlJobSpringExecutor.setPort(port);
        xxlJobSpringExecutor.setAccessToken(accessToken);
        xxlJobSpringExecutor.setLogPath(logPath);
        xxlJobSpringExecutor.setLogRetentionDays(logRetentionDays);

        return xxlJobSpringExecutor;
    }

    /**
     * 针对多网卡、容器内部署等情况,可借助 "spring-cloud-commons" 提供的 "InetUtils" 组件灵活定制注册IP;
     *
     *      1、引入依赖:
     *          <dependency>
     *             <groupId>org.springframework.cloud</groupId>
     *             <artifactId>spring-cloud-commons</artifactId>
     *             <version>${version}</version>
     *         </dependency>
     *
     *      2、配置文件,或者容器启动变量
     *          spring.cloud.inetutils.preferred-networks: 'xxx.xxx.xxx.'
     *
     *      3、获取IP
     *          String ip_ = inetUtils.findFirstNonLoopbackHostInfo().getIpAddress();
     */


}

 
4、仿照 service.jobhandler.SampleXxlJob 写定时任务

import com.xxl.job.core.context.XxlJobHelper;
import com.xxl.job.core.handler.annotation.XxlJob;
import org.springframework.stereotype.Component;

@Component  //放到容器中
public class XxxJob {

    /**
     * 简单任务示例
     */
    @XxlJob("sampleJobHandler")
    public void sampleJobHandler() throws Exception {
        //执行日志:需要通过 "XxlJobHelper.log" 打印执行日志;
        XxlJobHelper.log("sampleJobHandler start");

        //可通过 XxlJobHelper.handleFail()/handleSuccess() 指定任务执行结果,未设置时默认默认为 handleSuccess() 成功
        try {
            int i = 1 / 0;
        } catch (Exception e) {
            XxlJobHelper.handleFail();
        }
    }

    /**
     * 分片广播任务示例
     */
    @XxlJob("shardingJobHandler")
    public void shardingJobHandler() throws Exception {
        // 分片参数
        int shardIndex = XxlJobHelper.getShardIndex();
        int shardTotal = XxlJobHelper.getShardTotal();
        XxlJobHelper.log("分片参数:当前分片序号 = {}, 总分片数 = {}", shardIndex, shardTotal);
        //...
    }

}

 

5、启动应用,调度中心 -> 任务管理 -> 增删、修改任务实例

在这里插入图片描述
运行模式使用 BEAN,JobHandler 与 @XxlJob 指定的 jobName 保持一致。

 

策略说明

执行器集群部署时,调度中心支持的路由策略如下

  • ROUND(轮询)
  • FIRST(第一个):固定选择第一个节点
  • LAST(最后一个):固定选择最后一个节点
  • RANDOM(随机):随机选择在线的节点
  • CONSISTENT_HASH(一致性HASH):每个任务按照Hash算法固定选择某个节点,且所有任务均匀散列在不同节点上。
  • LEAST_FREQUENTLY_USED(最不经常使用):优先选择使用频率最低的节点。
  • LEAST_RECENTLY_USED(最近最久未使用):优先选择最久未使用的节点。
  • FAILOVER(故障转移):按照顺序依次进行心跳检测,第一个心跳检测成功的节点选定为目标执行器并发起调度。
  • BUSYOVER(忙碌转移):按照顺序依次进行空闲检测,第一个空闲检测成功的节点选定为目标执行器并发起调度。
  • SHARDING_BROADCAST(分片广播):广播触发对应集群中所有节点执行一次任务,同时系统自动传递分片参数;可根据分片参数开发分片任务。

 

调度过于密集,执行器来不及处理任务实例时,调度中心提供的阻塞处理策略如下

  • 单机串行(默认):调度请求进入单机执行器后,调度请求进入FIFO队列并以串行方式运行。
  • 丢弃后续调度:调度请求进入单机执行器后,发现执行器存在运行的调度任务,本次请求将会被丢弃并标记为失败。
  • 覆盖之前调度:调度请求进入单机执行器后,发现执行器存在运行的调度任务,将会终止运行中的调度任务并清空队列,然后运行本地调度任务。

 

分批处理数据

前提:id有序递增

1、直接 limit 分页,where… order by id asc limit 1000
数据量大时可能存在深分页性能问题。

2、select min(id), max(id) where… 先查出待处理记录的id区间,再根据id分批处理 where… and id > #{minId} and id < #{minId} + 1000。
由于物理删除记录、使用雪花算法等非自增id,id可能是不连续的、存在很大的跳跃,难以确定每批次处理的记录数,可能出现大量无效批次。

3、起始id + limit,where id > #{startId} order by id asc limit 1000,推荐。示例

final int limitSize = 500;
long startId = 0L;
while (true) {
    //查询需要处理的记录
    List<UserPo> userPos = userDao.listByQo(GoldCardQuery.builder()
            .startId(startId)
            .orderBy("id asc")
            .limitSize(limitSize)
            .build());

    //批量处理
    for (UserPo userPo : userPos) {
        try {
            //...
        } catch (Exception e) {
            log.error("xxx执行异常,userPo={}", JSON.toJSONString(userPo), e);
        }
    }

    //判断循环条件
    if (userPos.size() < limitSize) {
        break;
    }
    startId = userPos.get(userPos.size() - 1).getId();
}
  • 3
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值