本文为Spring Boot2.1系列的第九篇,代码可以从github下载 https://github.com/yinww/demo-springboot2.git
应用做了集群后能提供系统的稳定性和可靠性,但是如果集群系统涉及到定时器,一个应用的多个服务器中同时触发了相同的任务,这会出问题的。此时分布式定时任务就可以用来解决这个问题,本文介绍SpringBoot集成当当开源的分布式定时任务框架Elastic-Job。
一、下载Zookeeper
Elastic-Job的失效转移机制是依赖于Zookeeper实现的,因此需要先下载Zookeeper,并且需要3.4.6以上的版本。下载后启动Zookeeper,这里就不详细介绍Zookeeper的安装和启动,请自行查阅相关资料。
二、创建工程demo009
pom.xml的内容为
4.0.0
com.yinww
demo-springboot2
0.0.1-SNAPSHOT
demo009
org.springframework.boot
spring-boot-starter-web
com.dangdang
elastic-job-lite-core
2.1.5
三、Java代码
主类
package com.yinww.demo.springboot2.demo009;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class Demo009Application {
public static void main(String[] args) {
SpringApplication.run(Demo009Application.class, args);
}
}
Zookeeper 配置类
package com.yinww.demo.springboot2.demo009.config;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import com.dangdang.ddframe.job.reg.zookeeper.ZookeeperConfiguration;
import com.dangdang.ddframe.job.reg.zookeeper.ZookeeperRegistryCenter;
@Configuration
@ConditionalOnExpression("'${regCenter.serverList}'.length() > 0")
public class ZookeeperRegistryCenterConfig {
@Bean(initMethod = "init")
public ZookeeperRegistryCenter regCenter(@Value("${regCenter.serverList}") final String serverList,
@Value("${regCenter.namespace}") final String namespace) {
return new ZookeeperRegistryCenter(new ZookeeperConfiguration(serverList, namespace));
}
}
Job配置类
package com.yinww.demo.springboot2.demo009.config;
import javax.annotation.Resource;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import com.dangdang.ddframe.job.api.dataflow.DataflowJob;
import com.dangdang.ddframe.job.config.JobCoreConfiguration;
import com.dangdang.ddframe.job.config.dataflow.DataflowJobConfiguration;
import com.dangdang.ddframe.job.lite.api.JobScheduler;
import com.dangdang.ddframe.job.lite.config.LiteJobConfiguration;
import com.dangdang.ddframe.job.reg.zookeeper.ZookeeperRegistryCenter;
import com.yinww.demo.springboot2.demo009.domain.Mail;
import com.yinww.demo.springboot2.demo009.job.MailSendJob;
@Configuration
public class DataflowJobConfig {
@Resource
private ZookeeperRegistryCenter regCenter;
@Bean
public DataflowJob mailSendJob(){
return new MailSendJob();
}
/**
* 普通邮件定时配置
* @param mailSendJob
* @param cron
* @param shardingTotalCount
* @param shardingItemParameters
* @return
*/
@Bean(initMethod = "init")
public JobScheduler mailSendJobScheduler(final DataflowJob mailSendJob, @Value("${mailSendJob.cron}") final String cron,
@Value("${shardingCategory.shardingTotalCount}") final int shardingTotalCount,
@Value("${shardingCategory.shardingItemParameters}") final String shardingItemParameters){
LiteJobConfiguration jobConfiguration = createJobConfiguration(mailSendJob.getClass(), cron, shardingTotalCount, shardingItemParameters);
return new JobScheduler(regCenter, jobConfiguration);
}
@SuppressWarnings("rawtypes")
private LiteJobConfiguration createJobConfiguration(Class extends DataflowJob> jobClass, String cron, int shardingTotalCount
, String shardingItemParameters) {
// 定义作业核心配置
JobCoreConfiguration coreConfig = JobCoreConfiguration.newBuilder(jobClass.getName(), cron, shardingTotalCount).shardingItemParameters(shardingItemParameters).build();
// 定义Dataflow类型配置
// false 非流式处理数据: 只会在每次作业执行过程中执行一次fetchData方法和processData方法,随即完成本次作业
DataflowJobConfiguration jobConfiguration = new DataflowJobConfiguration(coreConfig, jobClass.getCanonicalName(), false);
// 定义Lite作业根配置
LiteJobConfiguration rootConfig = LiteJobConfiguration.newBuilder(jobConfiguration).overwrite(true).build();
return rootConfig;
}
}
业务对象类
package com.yinww.demo.springboot2.demo009.domain;
public class Mail {
private Long id;
private String subject;
private String sendTo;
private String content;
// getter and setter
}
Job类
package com.yinww.demo.springboot2.demo009.job;
import java.util.List;
import com.dangdang.ddframe.job.api.ShardingContext;
import com.dangdang.ddframe.job.api.dataflow.DataflowJob;
import com.yinww.demo.springboot2.demo009.domain.Mail;
public class MailSendJob implements DataflowJob {
@Override
public List fetchData(ShardingContext shardingContext) {
System.out.println(shardingContext.getShardingItem() + "====" + shardingContext.getShardingTotalCount());
return null;
}
@Override
public void processData(ShardingContext shardingContext, List data) {
// TODO Auto-generated method stub
}
}
四、配置文件
application.properties
# zookeeper config
regCenter.serverList=localhost:2181
regCenter.namespace=demo009
# 每隔20秒执行
mailSendJob.cron=0/20 * * * * ?
# 总分片数
shardingCategory.shardingTotalCount=10
shardingCategory.shardingItemParameters=0=A,1=B,2=C,3=D,4=E,5=F,6=G,7=H,8=I,9=J
五、运行
执行
java -jar demo009-0.0.1-SNAPSHOT.jar
每隔20秒就能在控制台看到分布式定时任务的输出
0====10
1====10
2====10
3====10
4====10
5====10
6====10
7====10
8====10
9====10
如果结合 springboot2.1入门系列三 Springboot多环境配置 启动多个集群构成的应用,就能看到这10个分片信息在不同的应用控制台中输出
六、踩坑经验
Elastic-Job项目基于成熟的开源产品Quartz和Zookeeper及其客户端Curator进行二次开发,如果与新版的Spring Cloud一起使用,会出现Curator的版本冲突。因为Elastic-Job的最新版是2017年发布的,当当没有持续地对项目进行更新维护,因此在基于新版的Spring Cloud项目中需要谨慎使用Elastic-Job,报错内容为:
Caused by: java.lang.ClassNotFoundException: org.apache.curator.connection.StandardConnectionHandlingPolicy
at java.net.URLClassLoader.findClass(URLClassLoader.java:381) ~[na:1.8.0_172]
at java.lang.ClassLoader.loadClass(ClassLoader.java:424) ~[na:1.8.0_172]
at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:349) ~[na:1.8.0_172]
at java.lang.ClassLoader.loadClass(ClassLoader.java:357) ~[na:1.8.0_172]
... 40 common frames omitted
解决这个问题的办法是在pom.xml中强制使用低版本的Curator
org.apache.curator
curator-recipes
2.10.0
org.apache.curator
curator-framework
2.10.0
本文内容到此结束,更多内容可关注公众号: