XXL-JOB分布式任务调度平台
定时任务的实现方式
-
Timer
java自带的java.util.Timer类,这个类允许你调度一个java.util.TimerTask任务。使用这种方式可以让你的程序按照某一个频度执行,但不能在指定时间运行。一般用的较少
javapublic void timer(){ Timer timer = new Timer(); TimerTask task =new TimerTask(){ public void run(){ system.out.print1n("开始运行"); } timer.schedule(task, 2000);//2秒后开始启动任务 timer.schedule(task,2000, 5000);//2秒后开始任务,然后每隔五秒执行一次 }
-
scheduledExecutorService
jdk自带的一个类;是基于线程池设计的定时任务类,每个调度任务都会分配到线程池中的一个线程去执行,也就是说,任务是并发执行,互不影响。
javapublic static void main(String[] args) { ScheduledExecutorService task = Executors.newScheduledThreadPool(5); task.scheduleAtFixedRate(new Runnable() { @Override public void run() { } },5,30,TimeUnit.SECONDS); //5秒后开始执行线程,然后每隔30分钟执行一次 }
-
Spring Task
Spring3.0以后自带的task,可以将它看成一个轻量级的Quartz,而且使用起来比Quartz简单许多。
-
启动类添加注解 @EnableScheduling 开启任务调度
java@SpringBootApplication @EnableScheduling //开启任务调度 public class SkyApplication { public static void main(String[] args) { SpringApplication.run(SkyApplication.class, args); log.info("server started"); } }
-
定义定时任务类
java/** * 自定义定时任务 */ @Component @Slf4j public class Task { @Scheduled(cron = "0 * * * * ?") public void processTimeoutOrder(){ // 定时任务代码 } }
-
-
Quartz(用的比较少)
一个功能比较强大的调度器,可以让你的程序在指定时间执行,也可以按照某个频率执行,配置稍微复杂,不支持分片,没有界面。
存在的问题
-
执行一次
如果想让它马上执行一次,这个时候可能就需要额外再写一个Rest接口或者再另外写一个单独的Job
-
更改执行时间
需要修改代码,提交测试,然后打包上线
-
暂停任务
比如一些定时报警的需求,当报警突然变得很多,这个时候需要暂停一下让其停止发送报警,我们可以用
配置的开关
去做,再逻辑中判断定时任务开关是否打开来做。这样做虽然也比较简单,但是我们这样需要新添加一些与任务无关的逻辑 -
监控
没有管理界面,不方便查看任务执行情况
-
分片执行
单台服务处理大批量数据时间太长、效率低下,需要其他机器协同执行。
解决方案
有如下需求:
- 支持任务分片
- 文档完善
- 提供管理台
- 接入简单
- 弹性扩容
- .....
XXL-JOB
调度中心通过获取DB锁来保证集群中执行任务的唯一性,如果短任务很多,随着调度中心集群数量增加,那么数据库的锁竞争会比较厉害,性能不好。
Elastic-Job
需要引入zookeeper
分片原理
项目源码
源码仓库地址:可以先看看github源码,上面有示例代码
源码仓库地址 | Release Download |
---|---|
github.com/xuxueli/xxl... | Download |
gitee.com/xuxueli0323... | Download |
解压源码,按照maven格式将源码导入IDE, 使用maven进行编译即可,源码结构如下:
markdown
xxl-job-admin:调度中心
xxl-job-core:公共依赖
xxl-job-executor-samples:执行器Sample示例(选择合适的版本执行器,可直接使用,也可以参考其并将现有项目改造成执行器)
:xxl-job-executor-sample-springboot:Springboot版本,通过Springboot管理执行器,推荐这种方式;
:xxl-job-executor-sample-frameless:无框架版本;
数据库导入sql文件。
启动xxl-job-admin调度中心,登录后台,默认账户是admin,密码:123456
自己的项目如何使用
-
引入依赖
xml<!-- xxl-job-core --> <dependency> <groupId>com.xuxueli</groupId> <artifactId>xxl-job-core</artifactId> <version>2.4.0</version> </dependency>
-
配置yml
yamlserver: port: 8881 xxl: job: #执行器通讯TOKEN,同xxl-job-admin配置相同 accessToken: default_token admin: #调度中心部署跟地址:如调度中心集群部署存在多个地址则用逗号分隔。 #执行器将会使用该地址进行"执行器心跳注册"和"任务结果回调" addresses: http://127.0.0.1:8080/xxl-job-admin executor: #AppName执行器心跳注册分组依据,为空则关闭自动注册 appname: xxl-job-demo #默认使用address注册,为空则使用ip : port address: ip: port: 9989 #执行器Log文件定期清理功能,指定日志保存天数,日志文件过期自动删除。限制至少保持3天,否则功能不生效; logpath: ./data/applogs/xxl-job/jobhandler logretentiondays: 30 logging: config: src/main/resources/logback.xml
-
配置文件
java/** * xxl-job config * */ @Configuration public class XxlJobConfig { private Logger logger = LoggerFactory.getLogger(XxlJobConfig.class); @Value("${xxl.job.admin.addresses}") private String adminAddresses; @Value("${xxl.job.accessToken}") private String accessToken; @Value("${xxl.job.executor.appname}") private String appname; @Value("${xxl.job.executor.address}") private String address; @Value("${xxl.job.executor.ip}") private String ip; @Value("${xxl.job.executor.port}") private int port; @Value("${xxl.job.executor.logpath}") private String logPath; @Value("${xxl.job.executor.logretentiondays}") private int logRetentionDays; @Bean public XxlJobSpringExecutor xxlJobExecutor() { logger.info(">>>>>>>>>>> xxl-job config init."); XxlJobSpringExecutor xxlJobSpringExecutor = new XxlJobSpringExecutor(); xxlJobSpringExecutor.setAdminAddresses(adminAddresses); xxlJobSpringExecutor.setAppname(appname); xxlJobSpringExecutor.setAddress(address); xxlJobSpringExecutor.setIp(ip); xxlJobSpringExecutor.setPort(port); xxlJobSpringExecutor.setAccessToken(accessToken); xxlJobSpringExecutor.setLogPath(logPath); xxlJobSpringExecutor.setLogRetentionDays(logRetentionDays); return xxlJobSpringExecutor; } /** * 针对多网卡、容器内部署等情况,可借助 "spring-cloud-commons" 提供的 "InetUtils" 组件灵活定制注册IP; * * 1、引入依赖: * <dependency> * <groupId>org.springframework.cloud</groupId> * <artifactId>spring-cloud-commons</artifactId> * <version>${version}</version> * </dependency> * * 2、配置文件,或者容器启动变量 * spring.cloud.inetutils.preferred-networks: 'xxx.xxx.xxx.' * * 3、获取IP * String ip_ = inetUtils.findFirstNonLoopbackHostInfo().getIpAddress(); */ }
-
将自己的服务注册进来
如果项目是集群部署的话,集群每一个节点都会注册进去。
-
新增任务
java/** * XxlJob开发示例(Bean模式) * * 开发步骤: * 1、任务开发:在Spring Bean实例中,开发Job方法; * 2、注解配置:为Job方法添加注解 "@XxlJob(value="自定义jobhandler名称", init = "JobHandler初始化方法", destroy = "JobHandler销毁方法")", * 注解value值对应的是调度中心新建任务的JobHandler属性的值。 * 3、执行日志:需要通过 "XxlJobHelper.log" 打印执行日志; * 4、任务结果:默认任务结果为 "成功" 状态,不需要主动设置; * 如有诉求,比如设置任务结果为失败,可以通过 "XxlJobHelper.handleFail/handleSuccess" 自主设置任务结果; */ @Component public class SampleXxlJob { private static Logger logger = LoggerFactory.getLogger(SampleXxlJob.class); /** * 2、分片广播任务 */ @XxlJob("sendMessage") public void shardingJobHandler() throws Exception { // 分片参数 int shardIndex = XxlJobHelper.getShardIndex();//当前分片索引 int shardTotal = XxlJobHelper.getShardTotal();//分片总数 logger.info("分片参数:当前分片序号 = {}, 总分片数 = {}", shardIndex, shardTotal); } }
阻塞处理策略:调度过于密集执行器来不及处理时的处理策略;
单机串行(默认):调度请求进入单机执行器后,调度请求进入FIFO队列并以串行方式运行;
丢弃后续调度:调度请求进入单机执行器后,发现执行器存在运行的调度任务,本次请求将会被丢弃并标记为失败;
覆盖之前调度:调度请求进入单机执行器后,发现执行器存在运行的调度任务,将会终止运行中的调度任务并清空队列,然后运行本地调度任务;
-
执行
具体实现-代码分片
通过分片总数和当前分片索引对数据库主键id取余,这样我们就可以在每个分片节点获取不一样的数据。
项目中mapper文件sql语句:
mysql
select user_id from `user_info` where MOD(id,#{shardTotal}) = #{shardIndex}
假设分片总数为3,当前节点获取到的分片索引为0,那么查询SQL如下:
sql
select user_id from `user_info` where MOD(id,3) = 0
另外两个节点收到的分片索引分别是1、2
sql
select user_id from `user_info` where MOD(id,3) = 1
select user_id from `user_info` where MOD(id,3) = 2
从而就实现了将大量数据分散到不同服务器上去执行的功能。