说明
日常开发中常常会用到定时任务,在springBoot中我们通常使用@EnableScheduling和@Scheduled(cron = “0/10 * * * * ?”),就能完成定时任务的使用。
但是在集群环境中就出现一个任务同时执行多次,造成数据异常等严重问题。下面是一种分布式任务的解决方式
使用 quartz、curator和zk来解决问题
先来看看效果
启动zookeeper,再启动服务1,服务2
quartz1先启动,优先抢占到leader,执行任务
现在停掉quartz1,quartz2抢占leader开始执行任务
依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-quartz</artifactId>
</dependency>
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-framework</artifactId>
<version>5.2.0</version>
</dependency>
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-recipes</artifactId>
<version>5.2.0</version>
</dependency>
zk参数
zookeeper.connect.address=127.0.0.1:2181
# 连接超时时间 毫秒
zookeeper.connect.connection-time-out=10000
# session超时时间 毫秒
zookeeper.connect.session-time-out=10000
# 重试初试时间 毫秒
zookeeper.connect.sleep-time-out=3000
# 重试次数
zookeeper.connect.max-retries=3
# 连接等待时间 毫秒
zookeeper.connect.wait-time: 20
# 命名空间
zookeeper.connect.name-space: curator
quartz_leader=/test_quartz_leader
spring.task.scheduling.thread-name-prefix=task-schedule-quartz
spring.task.scheduling.pool.size=10
spring.task.scheduling.shutdown.await-termination=true
service-name=quartz1
创建CuratorFramework和LeaderLatch
@Configuration
public class CuratorFrameworkConfiguration {
@Value("${zookeeper.connect.address}")
private String address;
@Value("${zookeeper.connect.connection-time-out}")
private int connectionTimeoutMs;
@Value("${zookeeper.connect.session-time-out}")
private int sessionTimeoutMs;
@Value("${zookeeper.connect.sleep-time-out}")
private int baseSleepTimeMs;
@Value("${zookeeper.connect.max-retries}")
private int maxRetries;
@Value("${zookeeper.connect.name-space}")
private String namespace;
@Value("${quartz_leader}")
private String quartzLeader;
@Bean(destroyMethod = "close")
public CuratorFramework curatorFramework() {
CuratorFramework curatorFramework = CuratorFrameworkFactory
.builder()
.connectString(address)
.sessionTimeoutMs(sessionTimeoutMs)
.connectionTimeoutMs(connectionTimeoutMs)
.namespace(namespace)
.retryPolicy(new ExponentialBackoffRetry(baseSleepTimeMs, maxRetries))
.build();
curatorFramework.start();
return curatorFramework;
}
@Bean(destroyMethod = "close")
public LeaderLatch leaderLatch(CuratorFramework curatorFramework) {
return new LeaderLatch(curatorFramework, quartzLeader);
}
}
leader监听
public class QuartzLeaderLatchListener implements LeaderLatchListener {
// 用来控制定时任务启停
private SchedulerFactoryBean schedulerFactoryBean;
public QuartzLeaderLatchListener(SchedulerFactoryBean schedulerFactoryBean) {
this.schedulerFactoryBean = schedulerFactoryBean;
}
@Override
public void isLeader() {
System.out.println(System.getProperty("service-name") + " 抢占leader成功,开始执行任务。");
schedulerFactoryBean.setAutoStartup(true);
schedulerFactoryBean.start();
}
@Override
public void notLeader() {
System.out.println(System.getProperty("service-name") + " 抢占leader失败,停止执行任务。");
schedulerFactoryBean.setAutoStartup(false);
schedulerFactoryBean.stop();
}
}
重写SchedulerFactoryBean
public class ZkSchedulerFactoryBean extends SchedulerFactoryBean {
public ZkSchedulerFactoryBean() throws Exception {
// 关闭自动启动
this.setAutoStartup(false);
getLeaderLatch().addListener(new QuartzLeaderLatchListener(this));
getLeaderLatch().start();
}
@Override
protected void startScheduler(Scheduler scheduler, int startupDelay) throws SchedulerException {
// 成为leader才执行
if(this.isAutoStartup()) {
super.startScheduler(scheduler, startupDelay);
}
}
@Override
public void destroy() throws SchedulerException {
CloseableUtils.closeQuietly(getLeaderLatch());
super.destroy();
}
private LeaderLatch getLeaderLatch() {
return ApplicationContextProvider.getBean(LeaderLatch.class);
}
}
任务配置
@Configuration
public class DistributedQuartzConfiguration {
@Bean
public JobDetailFactoryBean jobDetailFactoryBean(){
JobDetailFactoryBean factoryBean=new JobDetailFactoryBean();
factoryBean.setJobClass(DistributedJobTest.class);
return factoryBean;
}
@Bean
public CronTriggerFactoryBean cronTriggerFactoryBean(@Qualifier("jobDetailFactoryBean") JobDetailFactoryBean jobDetailFactoryBean){
CronTriggerFactoryBean cronTriggerFactoryBean = new CronTriggerFactoryBean();
cronTriggerFactoryBean.setJobDetail(jobDetailFactoryBean.getObject());
cronTriggerFactoryBean.setCronExpression("0/10 * * * * ?");
return cronTriggerFactoryBean;
}
@Bean
public SchedulerFactoryBean schedulerFactoryBean(@Qualifier("cronTriggerFactoryBean") CronTriggerFactoryBean cronTriggerFactoryBean) throws Exception {
// 一定要使用重写的工厂
ZkSchedulerFactoryBean schedulerFactoryBean = new ZkSchedulerFactoryBean();
schedulerFactoryBean.setTriggers(cronTriggerFactoryBean.getObject());
return schedulerFactoryBean;
}
}
任务执行类
public class DistributedJobTest implements Job {
@Override
public void execute(JobExecutionContext jobExecutionContext) {
System.out.println(System.getProperty("service-name") + " 执行任务");
}
}
完成
附上ApplicationContextProvider
@Component
public class ApplicationContextProvider implements ApplicationContextAware {
private static ApplicationContext context;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.context = applicationContext;
}
public static ApplicationContext getApplicationContext() {
return context;
}
public static Object getBean(String name) {
return getApplicationContext().getBean(name);
}
public static <T> T getBean(Class<T> clazz) {
return getApplicationContext().getBean(clazz);
}
public static <T> T getBean(String name, Class<T> clazz) {
return getApplicationContext().getBean(name, clazz);
}
}