【业务功能篇17】Springboot +shedlock锁 实现定时任务

2023-10-29 02:10

本文主要是介绍【业务功能篇17】Springboot +shedlock锁 实现定时任务,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

业务场景:我们在业务开发过程时,有时需要用到一些定时功能,定期的执行一些数据处理,比如每天固定时间去执行数据,判断是否有符合逻辑的情况,就生成一个告警单,提供给业务查看。

这里接着上一篇技术帖 继续补充定时任务的设计开发  【业务功能篇16】Springboot+mybatisplus+ShedLock框架根据一定的逻辑数据处理规则,定时任务生成告警单

Shedlock是个分布式锁,大致实现,就是针对多个服务,提供一个公有的存储,来维护这个锁(类似悲观锁机制)官方解释是他永远只是一个锁,并非是一个分布式任务调度器。一般shedLock被使用的场景是,你有个任务,你只希望他在单个节点执行,而不希望他并行执行,而且这个任务是支持重复执行的。

        ShedLock的作用,确保任务在同一时刻最多执行一次。如果一个任务正在一个节点上执行,则它将获得一个锁,该锁将阻止从另一个节点(或线程)执行同一任务。如果一个任务已经在一个节点上执行,则在其他节点上的执行不会等待,只需跳过它即可
ShedLock使用Mongo,JDBC数据库,Redis,Hazelcast,ZooKeeper或其他外部存储进行协调,即通过外部存储来实现锁机制。

        当第一个微服务执行定时任务的时候,会定时任务进行锁操作,然后其他的定时任务就不会再执行,锁操作有一定的时长,超过这个时长以后,再一次,所有的定时任务进行争抢下一个定时任务的执行权限,如此循环。保证了即使是其中的一个定时任务挂掉了,到一定的时间以后,锁也会释放,其他的定时任务依旧会进行执行权的争夺,执行定时任务。

        

* 分布式锁,保障多节点部署定时任务只执行一次。
* 本质上是通过对主键进行抢占,因此需要确保数据库中存在shedlock表

 一、配置POM依赖

<!--        分布式定时任务锁--><dependency><groupId>net.javacrumbs.shedlock</groupId><artifactId>shedlock-spring</artifactId><version>4.14.0</version></dependency><dependency><groupId>net.javacrumbs.shedlock</groupId><artifactId>shedlock-provider-jdbc-template</artifactId><version>4.43.0</version></dependency>

二、配置启动类 

import lombok.extern.slf4j.Slf4j;import net.javacrumbs.shedlock.spring.annotation.EnableSchedulerLock;import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.scheduling.annotation.EnableScheduling;@Slf4j
@EnableCaching
@SpringBootApplication(exclude = {org.springframework.boot.actuate.autoconfigure.security.servlet.ManagementWebSecurityAutoConfiguration.class,SecurityAutoConfiguration.class,DataSourceAutoConfiguration.class
}
)
// 开启定时任务注解
@EnableScheduling
// 开启定时任务锁,默认设置锁最大占用时间为30分钟
@EnableSchedulerLock(defaultLockAtMostFor = "30m")
public class Application extends SpringBootServletInitializer {@Overrideprotected SpringApplicationBuilder configure(SpringApplicationBuilder application) {return application.sources(Application.class);}public static void main(String[] args) {SpringApplication.run(Application.class, args);}
}

三、创建分布式锁配置类

package com.xxx.config;import net.javacrumbs.shedlock.core.LockProvider;
import net.javacrumbs.shedlock.provider.jdbctemplate.JdbcTemplateLockProvider;
import net.javacrumbs.shedlock.spring.annotation.EnableSchedulerLock;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.scheduling.annotation.EnableScheduling;import javax.annotation.Resource;
import javax.sql.DataSource;
import java.util.TimeZone;/*** 分布式锁,保障多节点部署定时任务只执行一次。* 本质上是通过对主键进行抢占,因此需要确保数据库中存在shedlock表。MySql建表语句如下:* CREATE TABLE shedlock(name VARCHAR(64) NOT NULL, lock_until TIMESTAMP(3) NOT NULL,* locked_at TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), locked_by VARCHAR(255) NOT NULL, PRIMARY KEY (name));*/
@Configuration
@EnableScheduling
@EnableSchedulerLock(defaultLockAtMostFor = "30m")
public class ShedLockConfig {@ResourceDataSource dataSource;/*** 配置锁的提供者*/@Beanpublic LockProvider lockProvider() {return new JdbcTemplateLockProvider(JdbcTemplateLockProvider.Configuration.builder().withJdbcTemplate(new JdbcTemplate(dataSource)).withTableName("shedlock").withTimeZone(TimeZone.getDefault()).build());}
}

 四、创建对应的定时任务表

CREATE TABLE shedlock(name VARCHAR(64) NOT NULL, lock_until TIMESTAMP(3) NOT NULL,locked_at TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), locked_by VARCHAR(255) NOT NULL, PRIMARY KEY (name));

五、创建定时任务

  • 定时任务类上加入注解  @Component @EnableScheduling 注册bean,开启定时任务
  • 定时方法上加入注解:
  • SchedulerLock  分布式锁对象 name值,会插入shedlock表中的name字段,PT2H表示不超过2个小时的锁
  • Scheduled  定时任务每天 早上9点执行一次

    @SchedulerLock(name = "keyword_warning", lockAtLeastFor = "PT2H", lockAtMostFor = "PT2H")
    @Scheduled(cron = "0 0 09 * * ?")

  • @EnableAsync 注解在类上

    开启异步,还需要在具体的任务方法加@Async表示需要异步 ,定时任务默认是同步的,主要是为了定时可以按时跑起来,比如说我们任务是每10秒执行一次,但是任务逻辑跑完不止10秒,这样就需要等任务执行完再接着跑,这样频率就没有达到预期效果,所以我们开启异步,多线程去每隔10秒运行任务,即使任务10秒内没跑完也会开启其他线程来跑,而这里的线程池的数量,是系统默认给的,这里可以通过配置参数在yaml文件自定义spring.task.execution.pool.core-size=5  .max-size=20 进行设置

    看需求来判断是否使用异步:如果任务执行几分钟完成,然后每天执行一次,那大可不必使用异步
     

  •  @Asnyc 注解在对应的定时任务的方法上,表示需要异步执行
package com.xxx.task;import com.xxx.ProdMesKeywordWarnRuleService;
import com.xxx.utils.SpringBeanUtils;
import lombok.extern.slf4j.Slf4j;
import net.javacrumbs.shedlock.spring.annotation.SchedulerLock;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;import javax.annotation.Resource;/*** 告警单预警*/
//开启异步,还需要在具体的任务方法加@Async表示需要异步 ,定时任务默认是同步的,主要是为了定时可以按时跑起来,比如说我们任务是每10秒执行一次,但是任务逻辑跑完不止10秒,这样就需要等任务执行完再接着跑,这样频率就没有达到预期效果,所以我们开启异步,多线程去每隔10秒运行任务,即使任务10秒内没跑完也会开启其他线程来跑,而这里的线程池的数量,是系统默认给的,这里可以通过配置参数在yaml文件自定义
//spring.task.execution.pool.core-size=5  .max-size=20 进行设置
//@EnableAsync   看需求来判断 如果任务执行几分钟完成,然后每天执行一次,那大可不必使用异步//开启定时功能  还需要在具体扥任务方法加@Scheduled表示定时频率
@EnableScheduling
@Component
@EnableScheduling
@Slf4j
public class ProdMesKeywordWarnSchedule {@Resourceprivate ProdMesKeywordWarnRuleService warnService;//@Async  需要异步,则开启即可@SchedulerLock(name = "keyword_warning", lockAtLeastFor = "PT2H", lockAtMostFor = "PT2H")@Scheduled(cron = "0 0 09 * * ?")public void createPreWarning() {if (!SpringBeanUtils.isTestProfile()) {//非测试环境时,运行告警单生成的方法warnService.createPreWarningPeriodically();warnService.sendMsOnTen();}}
}

六、定时任务的参数配置:

SchedulerLock 参数

  • @SchedulerLock
    只有带注释的方法被锁定,库忽略所有其他计划的任务。您还必须指定锁的名称。同一时间只能执行一个任务。
  • name
    分布式锁名称,注意 锁名称必须唯一。
  • lockAtMostFor & lockAtMostForString
    指定在执行节点死亡时应将锁保留多长时间。这只是一个备用选项,在正常情况下,任务完成后立即释放锁定。 您必须将其设置lockAtMostFor为比正常执行时间长得多的值。如果任务花费的时间超过 lockAtMostFor了所导致的行为,则可能无法预测(更多的进程将有效地持有该锁)。
    lockAtMostFor 单位 毫秒
    lockAtMostForString 使用“ PT14M” 意味着它将被锁定不超过14分钟。
  • lockAtLeastFor & lockAtLeastForString
    该属性指定应保留锁定的最短时间。其主要目的是在任务很短且节点之间的时钟差的情况下,防止从多个节点执行。

ShedLock支持两种模式的Spring集成,分别是 预定方法代理、TaskScheduler代理。我们默认选择最简单也是最实用的方式:预定方法代理(即 @SchedulerLock 的形式),ShedLock会围绕每个带有@SchedulerLock注释的方法创建AOP代理。这种方法的主要优点是它不依赖于Spring调度。缺点是即使直接调用该方法也会应用锁定。还应注意,当前仅支持返回void的方法,如果您注释并调用具有非void返回类型的方法,则会引发异常。

Scheduled参数 cron定时写法

import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;import java.util.Date;@Component
public class Jobs {//表示方法执行完成后5秒@Scheduled(fixedDelay = 5000)public void fixedDelayJob() throws InterruptedException {System.out.println("fixedDelay 每隔5秒" + new Date());}//表示每隔3秒@Scheduled(fixedRate = 3000)public void fixedRateJob() {System.out.println("fixedRate 每隔3秒" + new Date());}//表示每天8时30分0秒执行@Scheduled(cron = "0 0,30 0,8 ? * ? ")public void cronJob() {System.out.println(new Date() + " ...>>cron....");}
}

  • cron表达式:比如你要设置每天什么时候执行,就可以用它
    cron表达式,有专门的语法,而且感觉有点绕人,不过简单来说,大家记住一些常用的用法即可,特殊的语法可以单独去查。
    cron一共有7位,但是最后一位是年,可以留空,所以我们可以写6位
  • * 第一位,表示秒,取值0-59
    * 第二位,表示分,取值0-59
    * 第三位,表示小时,取值0-23
    * 第四位,日期天/日,取值1-31
    * 第五位,日期月份,取值1-12
    * 第六位,星期,取值1-7,星期一,星期二...,注:不是第1周,第二周的意思
              另外:1表示星期天,2表示星期一。
    * 第7为,年份,可以留空,取值1970-2099
     
  •  (*)星号:可以理解为每的意思,每秒,每分,每天,每月,每年...
  • (?)问号:问号只能出现在日期和星期这两个位置。
  • (-)减号:表达一个范围,如在小时字段中使用“10-12”,则表示从10到12点,即10,11,12
  • (,)逗号:表达一个列表值,如在星期字段中使用“1,2,4”,则表示星期一,星期二,星期四
  • (/)斜杠:如:x/y,x是开始值,y是步长,比如在第一位(秒) 0/15就是,从0秒开始,每15秒,最后就是0,15,30,45,60    另:*/y,等同于0/y
  •  0 0 3 * * ?     每天3点执行
  • 0 5 3 * * ?     每天3点5分执行
  • 0 5 3 ? * *     每天3点5分执行,与上面作用相同
  • 0 5/10 3 * * ?  每天3点的 5分,15分,25分,35分,45分,55分这几个时间点执行
  • 0 10 3 ? * 1    每周星期天,3点10分 执行,注:1表示星期天    
  • 0 10 3 ? * 1#3  每个月的第三个星期,星期天 执行,#号只能出现在星期的位置

这篇关于【业务功能篇17】Springboot +shedlock锁 实现定时任务的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/297555

相关文章

JAVA保证HashMap线程安全的几种方式

《JAVA保证HashMap线程安全的几种方式》HashMap是线程不安全的,这意味着如果多个线程并发地访问和修改同一个HashMap实例,可能会导致数据不一致和其他线程安全问题,本文主要介绍了JAV... 目录1. 使用 Collections.synchronizedMap2. 使用 Concurren

Java Response返回值的最佳处理方案

《JavaResponse返回值的最佳处理方案》在开发Web应用程序时,我们经常需要通过HTTP请求从服务器获取响应数据,这些数据可以是JSON、XML、甚至是文件,本篇文章将详细解析Java中处理... 目录摘要概述核心问题:关键技术点:源码解析示例 1:使用HttpURLConnection获取Resp

python实现svg图片转换为png和gif

《python实现svg图片转换为png和gif》这篇文章主要为大家详细介绍了python如何实现将svg图片格式转换为png和gif,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录python实现svg图片转换为png和gifpython实现图片格式之间的相互转换延展:基于Py

Python利用ElementTree实现快速解析XML文件

《Python利用ElementTree实现快速解析XML文件》ElementTree是Python标准库的一部分,而且是Python标准库中用于解析和操作XML数据的模块,下面小编就来和大家详细讲讲... 目录一、XML文件解析到底有多重要二、ElementTree快速入门1. 加载XML的两种方式2.

Java的栈与队列实现代码解析

《Java的栈与队列实现代码解析》栈是常见的线性数据结构,栈的特点是以先进后出的形式,后进先出,先进后出,分为栈底和栈顶,栈应用于内存的分配,表达式求值,存储临时的数据和方法的调用等,本文给大家介绍J... 目录栈的概念(Stack)栈的实现代码队列(Queue)模拟实现队列(双链表实现)循环队列(循环数组

C++如何通过Qt反射机制实现数据类序列化

《C++如何通过Qt反射机制实现数据类序列化》在C++工程中经常需要使用数据类,并对数据类进行存储、打印、调试等操作,所以本文就来聊聊C++如何通过Qt反射机制实现数据类序列化吧... 目录设计预期设计思路代码实现使用方法在 C++ 工程中经常需要使用数据类,并对数据类进行存储、打印、调试等操作。由于数据类

Python实现图片分割的多种方法总结

《Python实现图片分割的多种方法总结》图片分割是图像处理中的一个重要任务,它的目标是将图像划分为多个区域或者对象,本文为大家整理了一些常用的分割方法,大家可以根据需求自行选择... 目录1. 基于传统图像处理的分割方法(1) 使用固定阈值分割图片(2) 自适应阈值分割(3) 使用图像边缘检测分割(4)

Android实现在线预览office文档的示例详解

《Android实现在线预览office文档的示例详解》在移动端展示在线Office文档(如Word、Excel、PPT)是一项常见需求,这篇文章为大家重点介绍了两种方案的实现方法,希望对大家有一定的... 目录一、项目概述二、相关技术知识三、实现思路3.1 方案一:WebView + Office Onl

Java中Switch Case多个条件处理方法举例

《Java中SwitchCase多个条件处理方法举例》Java中switch语句用于根据变量值执行不同代码块,适用于多个条件的处理,:本文主要介绍Java中SwitchCase多个条件处理的相... 目录前言基本语法处理多个条件示例1:合并相同代码的多个case示例2:通过字符串合并多个case进阶用法使用

Java中的Lambda表达式及其应用小结

《Java中的Lambda表达式及其应用小结》Java中的Lambda表达式是一项极具创新性的特性,它使得Java代码更加简洁和高效,尤其是在集合操作和并行处理方面,:本文主要介绍Java中的La... 目录前言1. 什么是Lambda表达式?2. Lambda表达式的基本语法例子1:最简单的Lambda表