在工作中,我们会经常需要用到定时任务。在分布式情况下,如在dubbo+zk这种框架下,elasticjob是一个非常好的选择。elasticjob的任务分片策略可以确保任务分布的合理性和均衡性。这里主要说一下springboot整合elasticjob。
1.引入依赖
<!-- Elastic-Job-Lite核心依赖 -->
<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>
2.appcation.yml配置
引入elasticjob依赖于zookeeper,所以在整合elastic之前需要先安装zk,安装启动zk后配置zk连接地址,和elastic在zk的namespace。
server:
port: 8888
elasticjob:
regCenter:
serverList: localhost:2181
namespace: elastic-job
simpleJob:
shardingTotalCount:
one: 1
five: 5
3.配置注册中心
package com.example.elsticjob.config;
import com.dangdang.ddframe.job.reg.zookeeper.ZookeeperConfiguration;
import com.dangdang.ddframe.job.reg.zookeeper.ZookeeperRegistryCenter;
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;
@Configuration
@ConditionalOnExpression("'${elasticjob.regCenter.serverList}'.length() > 0")
public class RegistryCenterConfig {
@Bean(initMethod = "init")
public ZookeeperRegistryCenter regCenter(@Value("${elasticjob.regCenter.serverList}") final String serverList, @Value("${elasticjob.regCenter.namespace}") final String namespace) {
return new ZookeeperRegistryCenter(new ZookeeperConfiguration(serverList, namespace));
}
}
4.配置定时任务
这里shardingTotalCount分片数量我们配置的是5,正常情况下一个分片就可以了。
package com.example.elsticjob.config;
import com.dangdang.ddframe.job.api.simple.SimpleJob;
import com.dangdang.ddframe.job.config.JobCoreConfiguration;
import com.dangdang.ddframe.job.config.simple.SimpleJobConfiguration;
import com.dangdang.ddframe.job.lite.api.JobScheduler;
import com.dangdang.ddframe.job.lite.config.LiteJobConfiguration;
import com.dangdang.ddframe.job.lite.spring.api.SpringJobScheduler;
import com.dangdang.ddframe.job.reg.zookeeper.ZookeeperRegistryCenter;
import com.example.elsticjob.work.DemoJob;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.annotation.Resource;
/**
* elastic-job配置
* @author murphy
* @date 2024/6/1
*/
@Configuration
public class ElasticJobConfig {
@Resource
private ZookeeperRegistryCenter regCenter;
private LiteJobConfiguration getLiteJobConfiguration(final Class<? extends SimpleJob> jobClass, final String cron,
final int shardingTotalCount, final String shardingItemParameters, String description, boolean disabled) {
return LiteJobConfiguration.newBuilder(new SimpleJobConfiguration(
JobCoreConfiguration.newBuilder(jobClass.getName(), cron, shardingTotalCount)
.shardingItemParameters(shardingItemParameters).failover(true).description(description).build(),
jobClass.getCanonicalName())).overwrite(true).disabled(disabled).build();
}
@Bean(initMethod = "init")
public JobScheduler simpleJobScheduler(final DemoJob demoJob,@Value("0/5 * * * * ?") final String cron,@Value("${simpleJob.shardingTotalCount.five}") final int shardingTotalCount) {
return new SpringJobScheduler(demoJob, regCenter,
getLiteJobConfiguration(demoJob.getClass(), cron, shardingTotalCount, null,
"定时任务", false));
}
}
5.任务实现类
假设我们的定时业务需要循环执行一个逻辑,或者是多租户的情况下,可以使用多分片策略执行任务。
代码中的list模仿了多租户的情况下,根据租户id对分片数量进行取模,如果取模值和分片相等,则执行业务逻辑。
@Component
public class DemoJob implements SimpleJob {
@Override
public void execute(final ShardingContext shardingContext) {
List<Integer> list = new ArrayList<>();
list.add(1);
list.add(2);
list.add(3);
list.add(4);
list.add(5);
for (Integer i : list){
if(i % shardingContext.getShardingTotalCount() == shardingContext.getShardingItem()){
System.out.println("当前执行分片:" + shardingContext.getShardingItem());
//todo 业务逻辑
// doJob(shardingContext.getShardingItem());
}
}
}
}
运行结果如下:
当前执行分片:0
当前执行分片:1
当前执行分片:2
当前执行分片:3
当前执行分片:4
6.优化定时任务中的耗时处理
在定时任务中,我们可能有一些逻辑是比较耗时的,我们可以采用异步的方式去解决问题。
CompletableFuture是 Java 8 引入的一个类,属于 java.util.concurrent
包,它提供了一种处理异步任务的方法,使得编写并发代码更加简洁和易于管理。
处理代码如下:
我们模拟一个耗时操作,需要6秒处理完成。如果5个循环去执行这块逻辑,执行完成需要30多秒。使用CompletableFuture异步执行,最后再合并异步任务,获取最终结果,5个任务只需要6秒多。大大节省了处理时间。
CompletableFuture.supplyAsync方法是有返回值的,CompletableFuture.runAsync方法沒有返回值,它提供了丰富的 API 以简化并发代码的编写和管理。有需要可以查看官方文档:https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/concurrent/CompletableFuture.html
private void doJob(int shardingItem){
List<Integer> result = new ArrayList<>();
CompletableFuture<List<Integer>>[] futureList = new CompletableFuture[5];
System.out.println("开始执行耗时任务....................");
long start = System.currentTimeMillis();
for (int i = 0; i < 5; i++){
int finalI = i;
CompletableFuture<List<Integer>> cf = CompletableFuture.supplyAsync(() -> {
List<Integer> list = new ArrayList<>();
list.add(finalI);
//模拟耗时操作
try {
Thread.sleep(6000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
return list;
});
futureList[i] = cf;
}
CompletableFuture<Void> allOfFuture = CompletableFuture.allOf(futureList);
allOfFuture.join();
CompletableFuture<List<Integer>> mergedFuture = allOfFuture.thenApply(v ->
Stream.of(futureList)
.map(CompletableFuture::join) // 获取每个 CompletableFuture 的结果
.flatMap(List::stream) // 将多个 List<T> 合并为一个流
.collect(Collectors.toList()) // 收集到一个 List<T>
);
try {
result = mergedFuture.get();
System.out.println("耗时任务执行完毕,耗时:" + (System.currentTimeMillis() - start));
} catch (Exception e) {
System.out.println("获取结果失败");
return;
}
System.out.println("分片"+shardingItem+"获取的结果:" + result);
}