定时任务调度解决方案

概述

定时任务平台可以在后台自动检测数据并进行操作。主要应用在订单状态改变、后台统计、定时发送邮件或短信等

传统的定时任务调度

可以通过Timertask、可定时线程池Quartz、spring-schedule方式来进行处理。相关依赖和代码如下面所示

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.4.4</version>
    <relativePath/>
</parent>
<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <!-- quartz -->
    <dependency>
        <groupId>org.quartz-scheduler</groupId>
        <artifactId>quartz</artifactId>
        <version>2.2.1</version>
    </dependency>
    <dependency>
        <groupId>org.quartz-scheduler</groupId>
        <artifactId>quartz-jobs</artifactId>
        <version>2.2.1</version>
    </dependency>
</dependencies>

 TimerTask用法

import java.util.Timer;
import java.util.TimerTask;

public class TaskscheduleTest {    	    
    public static void main(String[] args) {
		TimerTask timerTask = new TimerTask() {
			@Override
			public void run() {
				System.out.println(Thread.currentThread().getName() + "TimerTask-定时任务触发");
			}
		};
		Timer timer = new Timer();
		long delay = 0;// 天数
		long period = 1000;// 毫秒数
		timer.scheduleAtFixedRate(timerTask, delay, period);
	}
}

线程池定时任务

public static void main(String[] args) {
	Runnable runnable = new Runnable() {
		public void run() {
			System.out.println("线程池-定时任务触发..");
		}
	};
	ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(10);
	scheduledExecutorService.scheduleAtFixedRate(runnable, 0, 2, TimeUnit.SECONDS);
}

Quartz框架任务调度

public static void main(String[] args) throws SchedulerException {
	//1.创建Scheduler的工厂
	SchedulerFactory sf = new StdSchedulerFactory();
	//2.从工厂中获取调度器实例
	Scheduler scheduler = sf.getScheduler();
	//3.创建JobDetail
	JobDetail jb = JobBuilder.newJob(MyJob.class)
			.withDescription("this is a ram job") //job的描述
			.withIdentity("ramJob", "ramGroup") //job 的name和group
			.build();

	//任务运行的时间,SimpleSchedle类型触发器有效
	long time = System.currentTimeMillis() + 3 * 1000L; //3秒后启动任务
	Date statTime = new Date(time);
	//4.创建Trigger
	//使用SimpleScheduleBuilder或者CronScheduleBuilder
	Trigger t = TriggerBuilder.newTrigger()
			.withDescription("")
			.withIdentity("ramTrigger", "ramTriggerGroup")
			//.withSchedule(SimpleScheduleBuilder.simpleSchedule())
			.startAt(statTime)  //默认当前时间启动
			.withSchedule(CronScheduleBuilder.cronSchedule("0/2 * * * * ?")) //两秒执行一次
			.build();
	//5.注册任务和定时器
	scheduler.scheduleJob(jb, t);
	//6.启动 调度器
	scheduler.start();

	}
}

spring-schedule定时任务调度

@Component
public class UserScheduled {
    @Scheduled(cron = "0/1 * * * * *")
    public void taskUserScheduled() {
        System.out.println("定时任务触发...");
    }
}

传统的定时任务方式对比

spring-schedule方式可以减少代码量,不过需要依赖spring相关的注解。

可定时任务线程池可以完全脱离spring注解来执行定时任务。

quartz的代码相对繁琐些。

同时他们底层的原理都是死循环来进行实现的

传统定时任务不足

  • 没有补偿机制,一旦出现异常,那么就不会执行,需要等到第二次的任务才能执行。
  • 不支持集群,不支持路由策略
  • 没有管理平台,无法做统计
  • 没有报警邮箱,状态监控
  • 业务逻辑与定时任务逻辑放入在同一个Jar包中,如果定时任务逻辑挂了也会影响到业务逻辑,即没有实现解耦

分布式定时任务框架XXL-job

详细步骤可参考官网:分布式任务调度平台XXL-JOB

系统组成

  • 调度中心:将执行器和任务注册至调度中心,由调度中心根据配置发出调度请求。先配置执行器,然后创建任务,因为任务是由执行器统一管理。
  • 执行器:负责接收调度请求的服务器,接收之后调用JobHandler去执行任务逻辑,执行器在启动的时候会将他的IP和端口号信息注册到调度中心
  • 任务:JobHandler,即是要实现的具体业务代码

注意:中心式调度器和分布式执行器在使用时需要分别启动,在调度中心启动时需要配置所依赖的 db 配置 

项目整合XXL-job任务调度平台+java实现定时任务实践

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.4.4</version>
    <relativePath/> <!-- lookup parent from repository -->
</parent>
<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

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

JobConfig-项目相关的执行器服务Config配置

@Configuration
public class demoJobConfig {
    private Logger logger = LoggerFactory.getLogger(demoJobConfig.class);
    @Value("${demo.job.admin.addresses}")
    private String adminAddresses;
    @Value("${demo.job.executor.appname}")
    private String appName;
    @Value("${demo.job.executor.ip}")
    private String ip;
    @Value("${demo.job.executor.port}")
    private int port;
    @Value("${demo.job.accessToken}")
    private String accessToken;
    @Value("${demo.job.executor.logpath}")
    private String logPath;
    @Value("${demo.job.executor.logretentiondays}")
    private int logRetentionDays;


    @Bean(initMethod = "start", destroyMethod = "destroy")
    public demoJobSpringExecutor demoJobExecutor() {
        logger.info(">>>>>>>>>>> demo-job-executor config init.");
        demoJobSpringExecutor demoJobSpringExecutor = new demoJobSpringExecutor();
        demoJobSpringExecutor.setAdminAddresses(adminAddresses);
        demoJobSpringExecutor.setAppName(appName);
        
        if(ip == null || ip.isEmpty()) {
        	// 如果没有指定IP,則使用Spring cloud 获取当前的IP
        	InetUtils inetUtils = new InetUtils(new InetUtilsProperties());
        	ip = inetUtils.findFirstNonLoopbackHostInfo().getIpAddress();
        	inetUtils.close();
        }
        logger.info("current job executor ip: " + ip);
        
        demoJobSpringExecutor.setIp(ip);
        demoJobSpringExecutor.setPort(port);
        demoJobSpringExecutor.setAccessToken(accessToken);
        demoJobSpringExecutor.setLogPath(logPath);
        demoJobSpringExecutor.setLogRetentionDays(logRetentionDays);

        return demoJobSpringExecutor;
    }
}

项目中具体定时任务DemoJob实现

@JobHandler(value = "DemoJob")
@Component
public class DemoJob extends IJobHandler {

	private static Logger logger = LoggerFactory.getLogger(DemoJob.class);

	@Autowired
	private EmailTemplateService emailTemplateService;
	
	@Autowired
	private MailSendRecordService mailSendRecordService;

	@Override
	public void execute(String param) throws Exception {
		//具体的任务实现
		......
		System.out.print("定时任务发送成功");
	}
}

扩展

如果在应用集群情况下,分布式job怎么解决幂等性?

  • 使用分布式锁,(ZK,redis)保证只有一台服务器在执行定时job。
  • 使用配置文件,配置文件上加上开关,flag=true的情况下才执行,false不执行。打包的时候,打不一样的配置文件的包。
  • 使用数据库的唯一标识,谁插入进去,谁就执行,但是效率低,一般不会采用。 

猜你喜欢

转载自blog.csdn.net/qq_34020761/article/details/128822777
今日推荐