最近在做定时任务的时候,引发了许多思考。我们在springboot项目开发定时任务需求时,首先想到的是@Scheduled和启动类上的@EnableScheduling注解。无可厚非,这种注解方式的定时任务简单易操作。但我在想,如果有多个服务启动的话,会发生什么呐?我在idea中启动了两个端口不同的服务
然后等定时任务的执行,在定时任务执行的时候,发现两个服务都执行了定时任务,这样就相当于跑了两次定时任务,这样肯定是万万不行的。比如我下面的测试栗子:我在用户表中查询年龄=18的用户,然后把查询到的结果插入到student表中
本来我的表中符合条件就三条数据,插入到student表三条数据才合理,等执行完毕后,发现student表插入了六条数据,相当于定时任务重复执行了两遍,这样的执行结果肯定和本意是相悖的。那有什么办法在多个服务部署的情况下,可以避免不被同时执行呐,我首先想到了在开发中经常使用的redis分布式锁。
执行完毕后,student表数据确实只有三条数据,达到了理想中的效果。可是用锁的话,肯定会影响性能,如果在开发中,需要用到的定时任务比较多,而且执行的时间比较长的话,这样肯定会有响应性能的。如果我建一张第三方的表,以用户表的id为主键,这样用主键唯一的特性,会不会避免重复执行的发生呐
执行完毕后,student表数据确实只有三条数据,达到了理想中的效果。我个人觉得,如果在数据量比较小的情况下,用这样一种方式是没有问题的,但是如果数据量很大的话,那么建的第三方的表就会产生大量的数据。那在如今主流的开发中,有没有一种已经开发好的框架,可以避免重复执行并且又简单易操作的,我想到了现在比较主流的xxl-job。具体的xxl-job介绍大家可以查找资,我在这里只介绍如何在我们的项目中如何集成xxl-job。
1、安装调度中心:http://gitee.com/xuxueli0323/xxl-job(gitee地址)
下载之后,打开之后是这样的
找到 doc目录下的sql文件,在自己本地执行。然后修改xxl-job-admin下的配置文件
修改后,直接启动admin项目即可,另外两个项目暂时不用管。
启动成功后,访问http://localhost:8081/xxl-job-admin/。我这里把端口改成了8081,默认8080
账号密码:admin/123456。登录成功后页面是这样的
调度中心启动没问题后,就可以在我们的项目中整合调度中心配置,把我们的项目注册到调度中心中
2、引入xxl-job依赖
<dependency> <groupId>com.xuxueli</groupId> <artifactId>xxl-job-core</artifactId> <version>2.3.0</version> </dependency>
3、在我们的项目配置文件yml中配置调度中心的信息
# Xxl-Job分布式定时任务调度中心 xxl: job: admin: # 调度中心部署跟地址 [选填]:如调度中心集群部署存在多个地址则用逗号分隔。 addresses: http://localhost:8081/xxl-job-admin # addresses: http://192.168.110.2:9090/xxl-job-admin # 执行器通讯TOKEN [选填]:非空时启用 系统默认 default_token accessToken: default_token executor: # 执行器的应用名称 appname: mls-xxl-job # 执行器注册 [选填]:优先使用该配置作为注册地址 address: "" # 执行器IP [选填]:默认为空表示自动获取IP ip: "" # 执行器端口号 [选填]:小于等于0则自动获取;默认端口为9999 port: 9999 # 执行器运行日志文件存储磁盘路径 [选填] :需要对该路径拥有读写权限;为空则使用默认路径; logpath: D:\Work\xxlJob #logpath: /data/logs/mls/job # 执行器日志文件保存天数 [选填] : 过期日志自动清理, 限制值大于等于3时生效; 否则, 如-1, 关闭自动清理功能; logretentiondays: 7
4、执行器配置文件
@Configuration @Slf4j public class XxlJobConfig { @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() { log.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; } }
以上配置结束后,在调度中心新增我们配置的执行器
配置完毕后,启动项目,调度中心就可以查看注册成功的执行器了
5、在项目中添加我们想要执行定时任务的方法
@XxlJob("xxlJobTest") public void xxlJobTest() throws Exception { System.out.println("执行定时任务"); QueryWrapper<UserEntity> wrapper = new QueryWrapper<>(); wrapper.eq("age", 18); List<UserEntity> userEntityList = mqMapper.selectList(wrapper); Thread.sleep(5000); for (UserEntity entity : userEntityList) { StudentEntity studentEntity = new StudentEntity(); studentEntity.setName(entity.getName() + entity.getAge()); studentMapper.insert(studentEntity); entity.setAge(19); mqMapper.updateById(entity); } }
@XxlJob("xxlJobTest")中指定的名称为调度中心需要配置的名称
6、在调度中心配置我们上面添加的定时任务方法
保存后,我们的定时任务方法就添加成功了,下面只需要执行我们刚才添加的那个任务即可
这样,我们的定时任务就执行成功了。
以上,就是关于定时任务我的一些思考。当然,还有一些别的问题,当有多个服务启动时,我们的定时任务会不会重复执行,还有当有多个服务时,如何让定时任务像多线程那样执行,比如第一个服务的定时任务只查询表中id为奇数的数据进行处理。比如第二个服务的定时任务只查询表中id为偶数的数据进行处理等等问题,这样xxl-job做完一个分布式的定时任务已经都处理好了,我也会在下篇文章中介绍。
如果写的有什么问题,可留言。