1. 背景介绍
Spring框架自带的定时任务scheduled如果部署多台机器时,同一个任务会执行多次,比如给用户计算收益定时任务,每天定时给用户计算收益,如果部署了多台,同一个用户将重复计算多次收益(业务错误),但如果只部署一台机器,无法保证高可用性,如果定时任务机器宕机,无法故障转移;
Elastic-Job基于Zookeeper、Quartz开发的Java分布式定时任务解决方案。
2. 优点
-
高可用性:可用于分布式系统,可在多台机器上部署同一套定时任务,由于是基于zookeeper注册中心的,如果分片只有一个,只会在其中一台机器上执行,不会出现同一任务执行多次的情况,如果该台机器故障,会将定时任务转移到其他正常的机器上实现故障转移。
-
热点分布:如果分片大于一个,调度中心会根据分片总数和部署的机器总数,会将分片均匀安排到各个机器上执行;每一个分片只执行一次;
-
故障转移:如果某台机器故障,会将定时任务转移到其他正常的机器上实现故障转移。
分片原理:根据同一注册中心同一命名空间下的相同的job全限定名进行分片的,分散到不同节点中携带的分片序列是不同的,可通过shardingContext.getShardingItem()获取。
3. SpringBoot整合SimpleJob作业
3.1引入依赖
<dependency>
<groupId>com.dangdang</groupId>
<artifactId>elastic-job-lite-core</artifactId>
<version>2.1.5</version>
</dependency>
<dependency>
<groupId>com.dangdang</groupId>
<artifactId>elastic-job-lite-spring</artifactId>
<version>2.1.5</version>
</dependency>
3.2 环境信息配置
📢注意:必须在同一注册中心,同一命名空间下的同一分组
# Zookeeper注册中心
regCenter.serverList = localhost:2181
# Zookeeper的命名空间
regCenter.namespace = vincent-job-springboot
# 定时
vincentJob.cron = 0/6 * * * * ?
# 作业分片总数
vincentJob.shardingTotalCount = 4
# 分片序列号和参数用等号分隔,多个键值对用逗号分隔
# 分片序列号从0开始,不可大于或等于作业分片总数
# 未指定分片参数,默认null
# 如:0=a,1=b,2=c
vincentJob.shardingItemParameters = 0=a,1=b
3.3 注册中心配置
import com.dangdang.ddframe.job.reg.zookeeper.ZookeeperConfiguration;
import com.dangdang.ddframe.job.reg.zookeeper.ZookeeperRegistryCenter;
@Configuration
public class ElasticRegCenterConfig {
@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));
}
}
注册中心是通过zookeeper实现的,其类型为:ZookeeperRegistryCenter而他需要一个zookeeper的配置ZookeeperConfiguration实体类;
参数说明:
-
serverLists:连接Zookeeper服务器的列表,包括IP地址和端口号,多个地址用逗号分隔,如: host1:2181,host2:2181
-
namespace:Zookeeper的命名空间,/如果你有多个不同 Elastic-Job集群 时,使用相同 Zookeeper,可以配置不同的 namespace 进行隔离
-
baseSleepTimeMilliseconds:等待重试的间隔时间的初始值 单位:毫秒
-
maxSleepTimeMilliseconds:等待重试的间隔时间的最大值 单位:毫秒
-
maxRetries:最大重试次数
-
sessionTimeoutMilliseconds:会话超时时间 单位:毫秒
-
connectionTimeoutMilliseconds:连接超时时间 单位:毫秒
-
digest:连接Zookeeper的权限令牌,缺省为不需要权限验证
3.4 数据库配置用于日志记录(可选)
/**
* 自定义数据源配置,一般只需要在application.yml文件中配置即可
*/
@Configuration
@AutoConfigureBefore(org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration.class)
@EnableConfigurationProperties(DataSourceProperties.class)
public class DataSourceAutoConfiguration {
@Resource
DataSourceProperties properties;
@Bean
public DataSource dataSource() {
DruidDataSource dataSource = new DruidDataSource();
dataSource.setUrl(properties.getUrl());
dataSource.setUsername(properties.getUsername());
dataSource.setPassword(properties.getPassword());
dataSource.setDriverClassName(properties.getDriverClassName());
return dataSource;
}
}
3.5 自定义注解
@Target({ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface ElasticSimpleJob {
String jobName() default "simpleJob";
String core() default "0/5 * * * * ?";
int shardingTotalCount() default 1;
String shardingItemParameters() default "";
String jobParameter() default "";
boolean overWriter() default false;
}
3.6 定时任务
实现SimpleJob接口
@Component
@ElasticSimpleJob(
jobName = "myElasticJob",
core = "0/10 * * * * ?",
shardingTotalCount = 4,
shardingItemParameters = "0=a,1=b",
overWriter = true
)
public class MyElasticJob implements SimpleJob {
@Override
public void execute(ShardingContext shardingContext) {
System.out.println(shardingContext.getShardingTotalCount() + " " + shardingContext.getShardingItem()
+" "+shardingContext.getShardingParameter());
switch (shardingContext.getShardingItem()) {
//总共分成四片,定义每个分片上执行的具体任务,根据部署的机器台数不同分片将在不同机器上执行
case 0:
System.out.println("hello one " + shardingContext.getShardingParameter());
break;
case 1:
System.out.println("hello two " + shardingContext.getShardingParameter());
break;
case 2:
System.out.println("hello three " + shardingContext.getShardingParameter());
break;
case 3:
System.out.println("hello fore " + shardingContext.getShardingParameter());
break;
}
}
}
@Component
@ElasticSimpleJob(
jobName = "simpleJobDemo",
core = "0/5 * * * * ?",
shardingTotalCount = 2,
shardingItemParameters = "0=a,1=b",
overWriter = true
)
public class SimpleJobDemo implements SimpleJob {
public void execute(ShardingContext shardingContext) {
//分片任务数2,没有区分分片任务,则两个分片上执行的任务一样,若只有一台机器则执行两次,若有两台机器则在两台机器上分别执行一次
System.out.println(String.format("------Thread ID: %s, %s,任务总片数: %s, " +"当前分片项: %s.当前参数: %s,"