【SpringBoot框架篇】31.基于分布式锁或xxx-job实现分布式任务调度
文章目录
- 1.简介
- 2.分布式锁实现
- 2.1.引用依赖
- 2.2.定义分布式锁注解
- 2.3.配置切入点和获取锁释放锁逻辑
- 2.4.测试任务
- 3.使用分布式任务调度平台xxx-job
- 3.1.下载源码并运行项目
- 3.2.springBoot项目集成xxl-job
- 4.项目配套代码
1.简介
定时任务使用场景一般为指定时间做数据统计,临时数据清理等等。
单节点部署的服务一般是通过下面方式实现即可:
- 在SpringBoot启动类上面添加@EnableScheduling注解开启spring定时任务功能
- 在定时任务方法上添加@Scheduled实现
@SpringBootApplication
@EnableScheduling
public class PlatformApplication{} @Component
@Slf4j
public class ScheduledServer {@Scheduled(cron = "0 0 1 * * ?")public void insertStatData() {log.info("-------------凌晨1点统计前一天的业务数据量--------------}
}
以上的配置如果是在服务需要部署多个节点的时候会出现重复执行定时任务导出数据重复的问题,这个时候可以通过分布式锁或使用xxx-job分布式任务调度平台避免这个任务重复执行的问题。
2.分布式锁实现
常用的三种实现如下
- 基于redis的单线程原子性
- 基于数据库的排它锁
- 基于ZooKeeper 文件节点实现
详细信息参考我写的这篇博客: 点我跳转
本文使用aop+redis优雅的使用分布式锁避免定时任务重复执行。
2.1.引用依赖
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId></dependency><dependency><groupId>org.aspectj</groupId><artifactId>aspectjweaver</artifactId><version>1.9.4</version></dependency>
2.2.定义分布式锁注解
@Target({ElementType.PARAMETER, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RedisLock {/*** 锁的名称(唯一标识),可选为""时候使用方法的名称*/String name() default "";/*** 重试重获取锁的次数,默认0 不重试*/int retry() default 0;/*** 占有锁的时间,避免程序宕机导致锁无法释放*/int expired() default 60;}
2.3.配置切入点和获取锁释放锁逻辑
- 下面定义了切入点为RedisLock注解类
- 在增强处理的环绕通知逻辑里面去执行获取锁和释放锁的逻辑
@Aspect
@Slf4j
@Component
public class RedisLockPointcut {@Autowiredprivate StringRedisTemplate stringRedisTemplate;@Pointcut("@annotation(com.ljm.boot.distributedjob.annotation.RedisLock)")public void redisLockPointCut() {}@Around("redisLockPointCut()")public Object doAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {Method method = currentMethod(proceedingJoinPoint);//获取到方法的注解对象RedisLock redisLock = method.getAnnotation(RedisLock.class);//获取锁的名称String methodName = redisLock.name();if (!StringUtils.hasLength(methodName)) {//如果注解里没有设置锁的名称,默认使用方法的名称methodName = method.getName();}//获取到锁的标识boolean flag = true;int retryCount = redisLock.retry();do {if (!flag && retryCount > 0) {Thread.sleep(1000L);retryCount--;}flag = stringRedisTemplate.opsForValue().setIfAbsent(methodName, "1", redisLock.expired(), TimeUnit.SECONDS);if (flag) {//获取到锁结束循环break;}//根据配置的重试次数,执行n次获取锁的方法,默认不重试} while (retryCount > 0);//result为连接点的返回结果Object result = null;if (flag) {try {result = proceedingJoinPoint.proceed();} catch (Throwable e) {/*异常通知方法*/log.error("异常通知方法>目标方法名{},异常为:{}", method.getName(), e);} finally {stringRedisTemplate.delete(methodName);}return result;}log.error("执行:{} 未获取锁,重试次数:{}", method.getName(), redisLock.retry());return null;}/*** 根据切入点获取执行的方法*/private Method currentMethod(JoinPoint joinPoint) {String methodName = joinPoint.getSignature().getName();//获取目标类的所有方法,找到当前要执行的方法Method[] methods = joinPoint.getTarget().getClass().getMethods();Method resultMethod = null;for (Method method : methods) {if (method.getName().equals(methodName)) {resultMethod = method;break;}}return resultMethod;}
}
2.4.测试任务
- 只需要在需要使用分布式锁的方法上面加@RedisLock注解即可优雅实现对应功能
- 在springboot启动类添加@EnableScheduling注解
@Slf4j
@Component
public class RedisLockScheduled {/*** 每分钟执行一次任务,设置分布式锁的名称insertStatData,过期时间为30秒,重试次数为3次*/@Scheduled(cron = "0 */1 * * * ?")@RedisLock(name="insertStatData",expired = 30,retry = 3)//@RedisLock 也可以不设置属性直接使用,默认分布锁的名称以函数名insertStatData命名,expired和retry用注解定义时候的默认值public void insertStatData() {try {//模拟业务处理线程休眠10秒Thread.sleep(10000L);}catch (Exception e){e.printStackTrace();}log.info("-------------每分钟打印一次日志--------------");}}
使用8031端口和8032端口启动服务两次,等运行一段时间可以看到同一时间端只有一个服务执行了定时任务内的逻辑。
8031端口服务日志打印
8032端口服务日志打印
由上面图片中日志可以看到基于分布式锁可以控制只有一个节点可以执行任务。
3.使用分布式任务调度平台xxx-job
xxx-job的github地址
本文使用的是2.3.1分支的代码。
架构图:
3.1.下载源码并运行项目
1.xxl-job默认使用的mysql数据库,需要先手动创建名称为xxl_job的数据库
2.导入项目 doc/db/tables_xxl_job.sql数据
3.修改配置文件数据库连接信息
4.需要配置token用于执行器注册时候鉴权认证使用,默认为default_token
5.启动xxl-job-admin项目后用 http://localhost:8080/xxl-job-admin/ 访问后台
账号: admin
密码: 123456
6.需要创建执行器
添写完执行器的名称后点击保存按钮
7.为执行器创建定时任务
上图中的JobHandler填写为xxlJobTask在执行端的代码里需要用到
- 8.启动定时任务
3.2.springBoot项目集成xxl-job
- 1.在pom文件中引入依赖
<dependency><groupId>com.xuxueli</groupId><artifactId>xxl-job-core</artifactId><version>2.3.1</version></dependency>
- 2.配置xxl-job服务端信息
在application.yml中添加下面配置
xxlJob:#xxl-job服务端配置文件中定义好的tokenaccessToken: default_token#xxl-job服务端地址(用于注册执行器使用)adminAddresses: http://127.0.0.1:8080/xxl-job-adminexecutor:# 执行器AppName [选填]:执行器心跳注册分组依据;为空则关闭自动注册appname: testJob# 执行器IP [选填]:默认为空表示自动获取IP,多网卡时可手动设置指定IPip:# 执行器端口号 [选填]:小于等于0则自动获取;默认端口为9999port: 0# 执行器运行日志文件存储磁盘路径 [选填]logpath: logs/xxlJob# 执行器日志文件保存天数 [选填] : 过期日志自动清理, 限制值大于等于3时生效; -1关闭自动清理功能;logretentiondays: 5
- 3.注册xxl-job执行器Bean实例
@Slf4j
@Configuration
public class XxlJobConfig {@Value("${xxlJob.accessToken}")private String accessToken;@Value("${xxlJob.adminAddresses}")private String adminAddresses;@Value("${xxlJob.executor.appname}")private String appName;@Value("${xxlJob.executor.ip}")private String ip;@Value("${xxlJob.executor.port}")private int port;@Value("${xxlJob.executor.logpath}")private String logPath;@Value("${xxlJob.executor.logretentiondays}")private int logRetentionDays;@Beanpublic XxlJobSpringExecutor xxlJobExecutor() {log.info("*****************xxlJobExecutor bean init*****************");XxlJobSpringExecutor xxlJobSpringExecutor = new XxlJobSpringExecutor();xxlJobSpringExecutor.setAdminAddresses(adminAddresses);xxlJobSpringExecutor.setAppname(appName);xxlJobSpringExecutor.setIp(ip);xxlJobSpringExecutor.setPort(port);xxlJobSpringExecutor.setAccessToken(accessToken);xxlJobSpringExecutor.setLogPath(logPath);xxlJobSpringExecutor.setLogRetentionDays(logRetentionDays);return xxlJobSpringExecutor;}
}
4.启动类添加@EnableScheduling注解
5.测试任务的代码
@Component
public class XxlJobScheduled {@XxlJob("xxlJobTask")public ReturnT<String> xxlJobTest(String date) {XxlJobContext xxlJobContext = XxlJobContext.getXxlJobContext();String jobParam = xxlJobContext.getJobParam();try {//模拟业务处理线程休眠10秒Thread.sleep(10000L);}catch (Exception e){e.printStackTrace();}log.info("xxlJobTest定时任务执行成功,jobParam:{}",jobParam);return ReturnT.SUCCESS;}
}
- 6.运行项目
使用8031端口和8032端口启动服务两次,看到下图的信息表示执行器注册成功。
运行一段时间观察日志打印情况
8031端口服务日志如下
8032端口服务如下
由上面图片中日志可以看到只有一个节点可以执行任务。
4.项目配套代码
github地址
创作不易,要是觉得我写的对你有点帮助的话,麻烦在github上帮我点下 Star
【SpringBoot框架篇】其它文章如下,后续会继续更新。
- 1.搭建第一个springboot项目
- 2.Thymeleaf模板引擎实战
- 3.优化代码,让代码更简洁高效
- 4.集成jta-atomikos实现分布式事务
- 5.分布式锁的实现方式
- 6.docker部署,并挂载配置文件到宿主机上面
- 7.项目发布到生产环境
- 8.搭建自己的spring-boot-starter
- 9.dubbo入门实战
- 10.API接口限流实战
- 11.Spring Data Jpa实战
- 12.使用druid的monitor工具查看sql执行性能
- 13.使用springboot admin对springboot应用进行监控
- 14.mybatis-plus实战
- 15.使用shiro对web应用进行权限认证
- 16.security整合jwt实现对前后端分离的项目进行权限认证
- 17.使用swagger2生成RESTful风格的接口文档
- 18.使用Netty加websocket实现在线聊天功能
- 19.使用spring-session加redis来实现session共享
- 20.自定义@Configuration配置类启用开关
- 21.对springboot框架编译后的jar文件瘦身
- 22.集成RocketMQ实现消息发布和订阅
- 23.集成smart-doc插件零侵入自动生成RESTful格式API文档
- 24.集成FastDFS实现文件的分布式存储
- 25.集成Minio实现文件的私有化对象存储
- 26.集成spring-boot-starter-validation对接口参数校验
- 27.集成mail实现邮件推送带网页样式的消息
- 28.使用JdbcTemplate操作数据库
- 29.Jpa+vue实现单模型的低代码平台
- 30.使用sharding-jdbc实现读写分离和分库分表
- 31.基于分布式锁或xxl-job实现分布式任务调度
- 32.基于注解+redis实现表单防重复提交
【SpringBoot框架篇】31.基于分布式锁或xxx-job实现分布式任务调度相关推荐
- 【SpringBoot框架篇】4.集成jta-atomikos实现分布式事务
文章目录 1.简介 1.1.分布式事务 1.2.JTA 1.3.atomikos 2.引入依赖 3.修改配置文件 4.添加配置读取类 5.创建多数据源,管理事务 6.数据持久化 7.数据库DLL文件 ...
- 【SpringBoot框架篇】11.Spring Data Jpa实战
文章目录 1.简介 1.1.JPA 1.2.Spring Data Jpa 1.3.Hibernate 1.4.Jpa.Spring Data Jpa.Hibernate三者之间的关系 2.引入依赖 ...
- 【SpringBoot框架篇】18.使用Netty加websocket实现在线聊天功能
文章目录 1.简介 2.最终功能实现的效果图 2.1.pc端 2.2.移动端 3.实战应用 3.1.引入依赖 3.2.配置文件 3.3.测试demo 3.3.1.消息内容实体类 3.3.2.处理请求的 ...
- redis setnx 分布式锁_手写Redis分布式锁
分布式锁使用场景 现在的系统都是集群部署,每个服务都不是单节点的了.比如库存服务,可能部署到3台机器上分别命名为节点1,节点2,节点3.库存服务需要扣减库存,扣减库存肯定需要锁吧,如果使用Lock或者 ...
- 分布式锁之三:Redlock实现分布式锁
之前写过一篇文章<如何在springcloud分布式系统中实现分布式锁?>,由于自己仅仅是阅读了相关的书籍,和查阅了相关的资料,就认为那样的是可行的.那篇文章实现的大概思路是用setNx命 ...
- 【最全】Spring Boot 实现分布式锁——这才是实现分布式锁的正确姿势!
ava世界的"半壁江山"--Spring早就提供了分布式锁的实现.早期,分布式锁的相关代码存在于Spring Cloud的子项目Spring Cloud Cluster中,后来被迁 ...
- 分布式锁的应用场景_分布式缓存技术Redis:高级应用(主从、事务与锁、持久化)...
安全性设置 设置客户端操作秘密 redis安装好后,默认情况下登陆客户端和使用命令操作时不需要密码的.某些情况下,为了安全起见,我们可以设置在客户端连接后进行任何操作之前都要进行密码验证.修改redi ...
- 分布式锁(一) Zookeeper分布式锁
什么是Zookeeper? Zookeeper(业界简称zk)是一种提供配置管理.分布式协同以及命名的中心化服务,这些提供的功能都是分布式系统中非常底层且必不可少的基本功能,但是如果自己实现这些功能而 ...
- redis 分布式锁 看门狗_漫谈分布式锁之Redis实现
笔耕墨耘,深研术道. 01写在前面Redis是一个高性能的内存数据库,常用于数据库.缓存和消息中间件.它提供了丰富的数据结构,更适合各种业务场景:基于AP模型,Redis保证了其高可用和高性能. 本文 ...
最新文章
- ​谷歌大神Jeff Dean领衔,万字展望5大AI趋势
- 【Flutter】自定义 Flutter 组件 ( 创建自定义 StatelessWidget、StatefulWidget 组件 | 调用自定义组件 )
- java socket程序_java 简单的java socket程序
- 本行没有输入值结余隐藏_仓库库存管理系统,内含逻辑公式,自动结余库存!操作简单易上手...
- java socket 全双工_java socket实现全双工通信
- sketchup作品_建环学院学生期末作品展第六站计算机辅助设计sketchup作业
- Map集合-根据宠物昵称查找宠物
- 单片机课设中期报告_毕业设计中期报告
- 白名单模板_亚马逊白名单申请流程全解析
- Ubuntu 桌面美化教程
- 光照 (4) 漫反射光照
- 基于Excel数据库的Cadence元件库管理
- 古代物流是如何进行的?
- 没有装php可以用phpmyadmin,phpMyAdmin 安装及问题总结
- python输出完全平方数_LeetCode 279*. 完全平方数(Python)
- CSDN(编程的开始)
- 算法学习--排序算法--插入排序
- 两线制4-20mA环路供电热电阻信号变送器(DIN导轨安装式)
- usb接口驱动_TCP/IP Over USB 用USB传输以太网数据,给你的MCU加个网卡
- 嵌入式Linux根文件系统制作
热门文章
- 51 单片机 (1) I/O口 按键控制LED之P0口排坑篇
- react native滑动输入高级版(刻度尺)
- 好书推荐:【A034】统计学大师之路:乔治·博克斯回忆录
- blender简单骨骼绑定
- Golang-Context扫盲与原理解析
- Python如何下载安装PCV
- fmincon 目标函数与非线性约束nonlcon带变参数
- 【WLAN】【软件】NXP芯片方案用户态和内核态通讯方式小结
- 【已解决】caused by: com.mysql.cj.exceptions.cjcommunicationsexception: communications link failure
- 高并发处理/服务器宕机处理