xxl-job简介
xxl-job是一个分布式任务调度平台,其核心设计目标是开发迅速、学习简单、轻量级、易扩展。现已开放源代码并接入多家公司线上产品线,开箱即用。详细见官网。
xxl-job 体系结构
见下图,图片摘自官网
调度中心:
其实就是一个单独的web服务(源码中的xxl-job-admin模块),主要是用来设置一些调度任务参数和执行调度任务。
提供了可视化界面,可以更加直观和方便的操作查看调度任务,且调度中心依赖数据库,所有的数据都存储在数据中。
调度中心支持集群模式,但是它们所依赖的数据库必须是同一个,所以同一个集群中的调度中心实例之间是没有任何通信的,数据都是通过数据库共享的
执行器:
执行器是用来执行具体的任务逻辑的,执行器你可以理解为就是平时开发的服务,一个服务实例对应一个执行器实例,每个执行器有自己的名字,为了方便,你可以将执行器的名字设置成服务名
任务:
就是具体调度执行的最小单元,一个执行器中也是可以有多个任务的
总的来说,调用中心是用来控制定时任务的触发逻辑,而执行器是具体执行任务的,这是一种任务和触发逻辑分离的设计思想,这种方式的好处就是使任务更加灵活,可以随时被调用,还可以被不同的调度规则触发。
xxl-job-admin
git上拉下来代码之后结构如下:
首先将文档中的sql文件tables_xxl_job.sql
导入到数据当中去,目前只支持MySQL数据库。
然后修改xxl-job-admin
模块中的application.properties
文件中的数据库连接信息,这个项目本质就是一个spring boot
项目,可以根据自己的需求更改。
修改完成之后,可以直接运行该项目也可以打包之后在运行,运行成功之后访问
http://localhost:8080/xxl-job-admin,如果在配置文件修改了配置则根据修改的访问url。默认的用户名是admin,密码是123456
到此xxl-job调度中心部署完毕。
Spring Boot整合xxl-job
导入依赖
<dependency>
<groupId>com.xuxueli</groupId>
<artifactId>xxl-job-core</artifactId>
<version>2.4.0</version>
</dependency>
修改配置文件,增加xxl-job配置
xxl:
job:
accessToken: default_token #根据xxl-job-admin中设置的配置这里
admin:
addresses: http://127.0.0.1:8080/xxl-job-admin
executor:
appname: xbx-dev # 这里需要再xxl-admin中的执行器名称,如果没有需要现在xxl-job-admin中添加。
ip: 127.0.0.1
port: 9999
编写配置类
@Configuration
public class XxlJobConfiguration {
private final Logger logger = LoggerFactory.getLogger(XxlJobConfiguration.class);
@Value("${xxl.job.admin.addresses}")
private String adminAddresses;
@Value("${xxl.job.executor.appname}")
private String appname;
@Value("${xxl.job.executor.port}")
private int port;
@Value("${xxl.job.executor.ip}")
private String ip;
@Value("${xxl.job.accessToken}")
private String accessToken;
@Bean
public XxlJobSpringExecutor xxlJobExecutor() {
logger.info(">>>>>>>>>>> xxl-job config init. <<<<<<<<<<<");
XxlJobSpringExecutor xxlJobSpringExecutor = new XxlJobSpringExecutor();
xxlJobSpringExecutor.setAdminAddresses(adminAddresses);
xxlJobSpringExecutor.setAppname(appname);
xxlJobSpringExecutor.setIp(ip);
xxlJobSpringExecutor.setPort(port);
xxlJobSpringExecutor.setAccessToken(accessToken);
return xxlJobSpringExecutor;
}
}
到此Spring Boot导入xxl-job完成,运行项目提示xxl-job remoting server start success, nettype = class com.xxl.job.core.server.EmbedServer, port = 9999
则运行成功。
BEAN模式
业务代码
@XxlJob("test")
public void test(){
LocalDateTime now = LocalDateTime.now();
System.out.println(now + " 简单任务执行了。。。。");
}
调度中心增加任务
设置完成后开启任务,并且启动代码程序。查看控制台信息,这是就会根据设置的时间定时执行所设置的任务方法。
GLUE模式
根据上边介绍,克制如果想要一个方法让xxl-job调度,就必须要@XxlJob
注解修饰方法,但是,有时会有需求在不改代码的基础上,让某个方法完成xxl-job调度。这是BEAN模式就不在适应,接着导入这一小节主题GLUE模式。
GLUE模式分为多种语言类型,这里仅使用java语言类型。
编写业务方法
@Service
public class GLUETestJobHandler {
private final Logger logger = LoggerFactory.getLogger(GLUETestJobHandler.class);
public void testGlue(){
LocalDateTime now = LocalDateTime.now();
logger.info("测试xxl-job GLUE模式 time => {}", now);
}
}
xxl-job调度中心任务配置中任务模式选择“GLUE(java)”模式,其余的设置和BEAN模式中的相同。设置完了之后选择任务的 GLUE IDE。
进入到IDE中去,这里代码是通过继承IJobHandler
模版,并重写模版方法,从而实现我们需要的功能。
路由策略
上边设置任务时路由策略都是默认的,就是第一个,但是当微服务集群的时候,只是第一个就不行了,需要把任务执行分配到集群的各个服务上,这就需要另一个路由策略,分片广播策略。
集群分片的模式主要有两种,取模分片和范围分片。
这里通过一个案例来解释集群分片策略。如:当前有一个需求需要定时的给一些用户发送短信信息。
正常的思路是,查询出需要发送消息的用户,根据查询出的信息给用户推送消息,但是如何设置路由策略为第一个或i这轮询等等,都是一次任务调度都指定到一个微服务上,这样集群就毫无作用了。所以采用集群分片路由策略
业务代码:
@Data
public class UserMobilePlan {
private Long id;
private String username;
private String nickname;
private String phone;
private String info;
}
/**
* 调度任务
**/
@Component
public class TestJobHandler {
private final Logger logger = LoggerFactory.getLogger(TestJobHandler.class);
@Autowired
private UserMobilePlanMapper userMobilePlanMapper;
@XxlJob("sendMsgHandler")
public void sendMsgHandler() throws InterruptedException {
//当前分片数
int shardIndex = XxlJobHelper.getShardIndex();
//分片总数
int shardTotal = XxlJobHelper.getShardTotal();
System.out.println("shardIndex:"+shardIndex+";shardTotal:"+shardTotal);
List<UserMobilePlan> userMobilePlans = new ArrayList<>();
//取模分片 查询数据
if (shardTotal == 1){
userMobilePlans= userMobilePlanMapper.selectAll();
}else {
userMobilePlans = userMobilePlanMapper.selectByMod(shardIndex,shardTotal);
}
System.out.println("任务开始时间:"+LocalDateTime.now()+",处理任务的数量:"+userMobilePlans.size());
long startTime = System.currentTimeMillis();
userMobilePlans.forEach(userMobilePlan -> {
try {
//模拟短信发送
TimeUnit.MILLISECONDS.sleep(100);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
});
System.out.println("任务结束时间:"+LocalDateTime.now());
System.out.println("任务耗时:"+(System.currentTimeMillis() - startTime)+"毫秒");
}
}
@Mapper
public interface UserMobilePlanMapper {
/**
* 根据id和分片总数取模的值等于分片数查询出当前服务需要处理的数据,这样集群中的所有服务就不会执行冲突
**/
@Select("select * from user_mobile_plan where mod(id,#{shardingTotal} = #{shardingIndex})")
List<UserMobilePlan> selectByMod(@Param("shardingIndex") Integer shardingIndex,@Param("shardingTotal") Integer shardingTotal);
}
运行服务可以看到结果