SpringBoot 定时任务 @Scheduled 集群环境优化 (使用分布式锁, 注解形式)

news/2024/11/28 18:42:47/

SpringBoot提供了 Schedule模块完美支持定时任务的执行

在实际开发中由于项目部署在分布式或集群服务器上 会导致定时任务多次触发

因此,使用redis分布锁机制可以有效避免多次执行定时任务

  核心方法是org.springframework.data.redis.core包下的

 setIfAbsent() 方法 返回值为布尔类型

  方法类似redis的SETNX命令 即”SET if Not Exists”

  服务器在执行邮件定时发送任务之前会向redis缓存中写入lock_key即任务锁 表明此服务器正在执行定时任务

  另一台服务器在写入锁时 由于锁已经存在就不做任何操作

  执行定时任务的服务器在执行完成后需释放任务锁
 

具体代码实现如下:

定义注解:

/*** redis锁注解* @author zhouzhou*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
@Documented
public @interface RedisLock {String lockPrefix() default "";String lockKey() default "";long timeOut() default 5;TimeUnit timeUnit() default TimeUnit.SECONDS;}

定义切面@Aspect, pointCut就是 RedisLock注解

/*** Description: redis锁拦截器实现* User: zhouzhou* Date: 2018-09-05* Time: 15:30*/
@Aspect
@Component
public class RedisLockAspect {private static final Integer MAX_RETRY_COUNT = 3;private static final String LOCK_PRE_FIX = "lockPreFix";private static final String LOCK_KEY = "lockKey";private static final String TIME_OUT = "timeOut";private static final int PROTECT_TIME = 2 << 11;//4096private static final Logger log = LoggerFactory.getLogger(RedisLockAspect.class);@Autowiredprivate CommonRedisHelper commonRedisHelper;@Pointcut("@annotation(com.shuige.components.cache.annotation.RedisLock)")public void redisLockAspect() {}@Around("redisLockAspect()")public void lockAroundAction(ProceedingJoinPoint proceeding) throws Exception {//获取redis锁Boolean flag = this.getLock(proceeding, 0, System.currentTimeMillis());if (flag) {try {proceeding.proceed();Thread.sleep(PROTECT_TIME);} catch (Throwable throwable) {throw new RuntimeException("分布式锁执行发生异常" + throwable.getMessage(), throwable);} finally {// 删除锁this.delLock(proceeding);}} else {log.info("其他系统正在执行此项任务");}}/*** 获取锁** @param proceeding* @return*/private boolean getLock(ProceedingJoinPoint proceeding, int count, long currentTime) {//获取注解中的参数Map<String, Object> annotationArgs = this.getAnnotationArgs(proceeding);String lockPrefix = (String) annotationArgs.get(LOCK_PRE_FIX);String key = (String) annotationArgs.get(LOCK_KEY);long expire = (long) annotationArgs.get(TIME_OUT);//String key = this.getFirstArg(proceeding);if (StringUtils.isEmpty(lockPrefix) || StringUtils.isEmpty(key)) {// 此条执行不到throw new RuntimeException("RedisLock,锁前缀,锁名未设置");}if (commonRedisHelper.setNx(lockPrefix, key, expire)) {return true;} else {// 如果当前时间与锁的时间差, 大于保护时间,则强制删除锁(防止锁死)long createTime = commonRedisHelper.getLockValue(lockPrefix, key);if ((currentTime - createTime) > (expire * 1000 + PROTECT_TIME)) {count ++;if (count > MAX_RETRY_COUNT){return false;}commonRedisHelper.delete(lockPrefix, key);getLock(proceeding,count,currentTime);}return false;}}/*** 删除锁** @param proceeding*/private void delLock(ProceedingJoinPoint proceeding) {Map<String, Object> annotationArgs = this.getAnnotationArgs(proceeding);String lockPrefix = (String) annotationArgs.get(LOCK_PRE_FIX);String key = (String) annotationArgs.get(LOCK_KEY);commonRedisHelper.delete(lockPrefix, key);}/*** 获取锁参数** @param proceeding* @return*/private Map<String, Object> getAnnotationArgs(ProceedingJoinPoint proceeding) {Class target = proceeding.getTarget().getClass();Method[] methods = target.getMethods();String methodName = proceeding.getSignature().getName();for (Method method : methods) {if (method.getName().equals(methodName)) {Map<String, Object> result = new HashMap<String, Object>();RedisLock redisLock = method.getAnnotation(RedisLock.class);result.put(LOCK_PRE_FIX, redisLock.lockPrefix());result.put(LOCK_KEY, redisLock.lockKey());result.put(TIME_OUT, redisLock.timeUnit().toSeconds(redisLock.timeOut()));return result;}}return null;}/*** 获取第一个String类型的参数为锁的业务参数** @param proceeding* @return*/@Deprecatedpublic String getFirstArg(ProceedingJoinPoint proceeding) {Object[] args = proceeding.getArgs();if (args != null && args.length > 0) {for (Object object : args) {String type = object.getClass().getName();if ("java.lang.String".equals(type)) {return (String) object;}}}return null;}}

CommonRedisHelper

/*** Description:* User: zhouzhou* Date: 2018-09-05* Time: 15:39*/
@Component
public class CommonRedisHelper {@AutowiredRedisTemplate redisTemplate;/*** 加分布式锁** @param track* @param sector* @param timeout* @return*/public boolean setNx(String track, String sector, long timeout) {ValueOperations valueOperations = redisTemplate.opsForValue();Boolean flag = valueOperations.setIfAbsent(track + sector, System.currentTimeMillis());// 如果成功设置超时时间, 防止超时if (flag) {valueOperations.set(track + sector, getLockValue(track, sector), timeout, TimeUnit.SECONDS);}return flag;}/*** 删除锁** @param track* @param sector*/public void delete(String track, String sector) {redisTemplate.delete(track + sector);}/*** 查询锁* @return 写锁时间*/public long getLockValue(String track, String sector) {ValueOperations valueOperations = redisTemplate.opsForValue();long createTime = (long) valueOperations.get(track + sector);return createTime;}}

使用场景:

    @Scheduled(cron = "0,30 * * * * ? ")@RedisLock(lockPrefix = "hello",lockKey = "world")public void hello(){System.out.println("每隔30秒定时任务测试,当前时间为:" + new SimpleDateFormat("yyyy年MM月dd日 HH时mm分ss秒").format(new Date()));}


http://www.ppmy.cn/news/853773.html

相关文章

万字详解 Stream 流式编程,写代码也可以很优雅

一、引言 流式编程的概念和作用 Java 流(Stream)是一连串的元素序列&#xff0c;可以进行各种操作以实现数据的转换和处理。流式编程的概念基于函数式编程的思想&#xff0c;旨在简化代码&#xff0c;提高可读性和可维护性。 Java Stream 的主要作用有以下几个方面&#xff…

什么是卡式报表,如何制作卡式报表

什么是卡式报表&#xff0c;通俗的说&#xff0c;就是卡片样式的报表&#xff0c;如同名片一般&#xff0c;看起来简单明了。有时候&#xff0c;我们在浏览一些学生&#xff0c;员工信息表的时候&#xff0c;看到每个人的信息就是一行&#xff0c;然后密密麻麻的一行行的排下去…

科学计算机上的m怎么消除,如何消除得力计算器上的FIX

如何消除得力计算器上的FIX以下文字资料是由(历史新知网www.lishixinzhi.com)小编为大家搜集整理后发布的内容&#xff0c;让我们赶快一起来看一下吧&#xff01; 如何消除得力计算器上的FIX 按3次Mode 再按3 然后按1 OK 如何消除计算器上的m 按MC键,或有的是MRC键,有些科学计算…

家用计算机怎么关机,得力计算器怎么关机

得力计算器是可以自动关机的。 原因它是双太阳光能&#xff0b;5号电池&#xff0c;电量消耗比较少&#xff0c;而且有太阳能电池补充电能&#xff0c;当没有操作时&#xff0c;内部的关机程序就起作用&#xff0c;等时间一到就切断电源达到自动关机的目的。 扩展资料&#xff…

元宇宙虚拟农场牧场认养种植理财复利源码

元宇宙虚拟农场牧场认养种植理财复利源码——探索数字时代的新型投资方式 随着信息技术的快速发展和数字经济的崛起&#xff0c;元宇宙成为了一个备受关注的话题。元宇宙是一种虚拟现实的概念&#xff0c;它是一个模拟的数字世界&#xff0c;里面可以进行各种虚拟体验和交互。…

尚硅谷Docker实战教程-笔记13【高级篇,Docker轻量级可视化工具Portainer】

尚硅谷大数据技术-教程-学习路线-笔记汇总表【课程资料下载】视频地址&#xff1a;尚硅谷Docker实战教程&#xff08;docker教程天花板&#xff09;_哔哩哔哩_bilibili 尚硅谷Docker实战教程-笔记01【基础篇&#xff0c;Docker理念简介、官网介绍、平台入门图解、平台架构图解】…

--k和k--的区别

--k和k--区别 问题描述方法总结 问题描述 很多程序初学者都会有这个疑问&#xff0c;–k和k–有什么区别呢&#xff0c;简单的解释是&#xff1a; –k就是先让k减去1&#xff0c;再把k-1的结果给–k k–是先把k的结果给k–&#xff0c;然后k自己减1 我来通过实际代码解读一下。…

k++,++k

原理&#xff1a; k是先用,再加 k是先加,再用 案例&#xff1a; public class Test { public static void main(String[] args) { int k 0 ; int ret k k k k; // ret的值为多少 System.err.printl…