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

这篇具有很好参考价值的文章主要介绍了SpringBoot 定时任务 @Scheduled 集群环境优化 (使用分布式锁, 注解形式)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

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;//4096
 
    private static final Logger log = LoggerFactory.getLogger(RedisLockAspect.class);
 
    @Autowired
    private 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
     */
    @Deprecated
    public 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 {
 
    @Autowired
    RedisTemplate 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;
    }
 
}

使用场景:文章来源地址https://www.toymoban.com/news/detail-557653.html

    @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()));
    }

到了这里,关于SpringBoot 定时任务 @Scheduled 集群环境优化 (使用分布式锁, 注解形式)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包赞助服务器费用

相关文章

  • @Scheduled 定时任务不执行

    启动类上加 @EnableScheduling 注解 定时任务类上加@Component 定时方法上加@Scheduled 解决:进行try…catch异常抛出 原因是: @Scheduled注解会在默认情况下以单线程的方式执行定时任务。 这个“单线程”指两个方面: 如果一个定时任务执行时间大于其任务间隔时间,那么下一次将会等

    2024年02月05日
    浏览(13)
  • JAVA 定时任务@Scheduled设置

    例1:每隔5秒执行一次:*/5 * * * * ? 例2:每隔5分执行一次:0 */5 * * * ? 在26分、29分、33分执行一次:0 26,29,33 * * * ? 例3:每天半夜12点30分执行一次:0 30 0 * * ? (注意日期域为0不是24) 每天凌晨1点执行一次:0 0 1 * * ? 每天上午10:15执行一次: 0 15 10 ? * * 或 0 15 10 * * ? 或 0 15

    2023年04月16日
    浏览(13)
  • @Scheduled定时器
定时任务调度:Unexpected error occurred in scheduled task错误

    @Scheduled定时器 定时任务调度:Unexpected error occurred in scheduled task错误

    目录 一、基本使用 二、参数详解 1. @Scheduled(fixedDelay = 5000) 2. @Scheduled(fixedRate = 5000) 3. @Scheduled(cron = “0 0 2 * * ?”) 4.cron表达式 案例 配置文件 写配置的时候,没有提示,解决方案 三、@Scheduled注意事项 四、 @Scheduled 的执行原理  1、加载使用 @Scheduled 注解的类及方法  2、解析

    2024年02月16日
    浏览(9)
  • @Scheduled Cron定时任务——表达式详解

    Cron表达式是一种用于定时任务调度的字符串表达式,它由6个或7个字段组成,分别表示秒、分、时、日、月、周和年。每个字段用空格分隔,字段之间用逗号分隔。 秒(0-59) 分(0-59) 时(0-23) 日(1-31) 月(1-12) 周(0-7,其中0和7都表示周日) 年(可选字段,1970-2099) Cron表达式的语法规则如

    2024年02月09日
    浏览(12)
  • Spring 定时任务@Scheduled 注解中的 Cron 表达式

    Spring 定时任务@Scheduled 注解中的 Cron 表达式

    Spring 框架提供了强大的定时任务功能,通过 @Scheduled 注解可以方便地定义和管理定时任务。其中,Cron 表达式作为定时任务触发的时间表达式,扮演着重要的角色。本篇博客将详细介绍和讲解 Cron 表达式的语法和常见用法,帮助各位更好地理解和使用 Spring 的定时任务功能。

    2024年02月11日
    浏览(10)
  • Spring Boot中的@Scheduled注解:定时任务的原理与实现

    Spring Boot中的@Scheduled注解:定时任务的原理与实现

    本文将详细探讨Spring Boot中@Scheduled注解的使用,包括其原理、实现流程、步骤和代码示例。通过本文,读者将能够了解如何在Spring Boot应用中轻松创建和管理定时任务。 在Spring框架中,@Scheduled注解用于标记一个方法,使其能够在固定的时间间隔内自动执行。这个注解主要基于

    2024年02月22日
    浏览(16)
  • 异常 :Unexpected error occurred in scheduled task 。 Spring Boot定时任务调度注解@scheduled

    异常 :Unexpected error occurred in scheduled task 。 Spring Boot定时任务调度注解@scheduled

           前言: @Scheduled 注解是 Spring Boot 提供的用于定时任务控制的注解,主要用于控制任务在某个指定时间执行,或者每隔一段时间执行        异常: Unexpected error occurred in scheduled task :计划任务发生意外错误 这个bug是偶然一次遇见的,当时觉得这个描述很简单,肯定

    2024年02月12日
    浏览(28)
  • Spring@Scheduled定时任务接入XXL-JOB的一种方案(基于SC Gateway)

    Spring@Scheduled定时任务接入XXL-JOB的一种方案(基于SC Gateway)

    目前在职的公司,维护着Spring Cloud分布式微服务项目有25+个。其中有10个左右微服务都写有定时任务逻辑,采用Spring @Scheduled这种方式。 Spring @Scheduled定时任务的缺点: 不支持集群:为避免重复执行,需引入分布式锁 死板不灵活:不支持手动执行,单次执行,补偿执行,修改

    2024年02月11日
    浏览(16)
  • springboot定时任务:同时使用定时任务和websocket报错

    springboot定时任务:同时使用定时任务和websocket报错

    项目使用了websocket,实现了消息的实时推送。后来项目需要一个定时任务,使用org.springframework.scheduling.annotation的@EnableScheduling注解来实现,启动项目之后报错 打断点 进入代码发现是这个定时任务的bean为null 由于先写的websocket推送消息,运行正常。之前一个项目只有一个定时任

    2024年02月11日
    浏览(15)
  • Java定时器 @Scheduled注解的使用

    Java定时器 @Scheduled注解的使用

    @Scheduled注解可以用于做定时任务,再方法上加上@Scheduled注解,可以将这个方法定义为一个任务发放,可以搭配cron表达式进行任务的控制。 开启定时任务时在类上加注解 @EnableScheduling 1.按顺序依次为 秒 分 时 天 月 周 年 表达式长度为6个或者7个 cron表达式是一个字符串,分为

    2024年02月04日
    浏览(10)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包