利用redis实现分布式定时任务

devtools/2024/10/17 6:20:59/

如果是微服务,两个服务都有定时,那么就出问题了,但是上分布式定时任务框架太麻烦怎么办,那么就用redis加个锁,谁先抢到锁谁执行

整个工具类

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;import java.util.Objects;/*** 实现分布式redis锁.** @author linzp* @version 1.0.0* CreateDate 2021/1/14 13:53*/
@Component
public class RedisLockUtils {/*** 锁名称前缀.*/public static final String TASK_LOCK_PREFIX = "TASK_LOCK_";/*** redis操作.*/@Autowiredprivate StringRedisTemplate redisTemplate;/*** 获取分布式redis锁.* 逻辑:* 1、使用setNX原子操作设置锁(返回 true-代表加锁成功,false-代表加锁失败)* 2、加锁成功直接返回* 3、加锁失败,进行监测,是否存在死锁的情况,检查上一个锁是否已经过期* 4、如果过期,重新让当前线程获取新的锁。* 5、这里可能会出现多个获取锁失败的线程执行到这一步,所以判断是否是加锁成功,如果没有,则返回失败** @param taskName       任务名称* @param lockExpireTime 锁的过期时间* @return true-获取锁成功 false-获取锁失败*/public Boolean getLock(String taskName, long lockExpireTime) {//锁的名称:前缀 + 任务名称String lockName = TASK_LOCK_PREFIX + taskName;return (Boolean) redisTemplate.execute((RedisCallback<?>) connection -> {// 计算此次过期时间:当前时间往后延申一个expireTImelong expireAt = System.currentTimeMillis() + lockExpireTime + 1;// 获取锁(setNX 原子操作)Boolean acquire = connection.setNX(lockName.getBytes(), String.valueOf(expireAt).getBytes());// 如果设置成功if (Objects.nonNull(acquire) && acquire) {return true;} else {//防止死锁,获取旧的过期时间,与当前系统时间比是否过期,如果过期则允许其他的线程再次获取。byte[] bytes = connection.get(lockName.getBytes());if (Objects.nonNull(bytes) && bytes.length > 0) {long expireTime = Long.parseLong(new String(bytes));// 如果旧的锁已经过期if (expireTime < System.currentTimeMillis()) {// 重新让当前线程加锁byte[] oldLockExpireTime = connection.getSet(lockName.getBytes(),String.valueOf(System.currentTimeMillis() + lockExpireTime + 1).getBytes());//这里为null证明这个新锁加锁成功,上一个旧锁已被释放if (Objects.isNull(oldLockExpireTime)) {return true;}// 防止在并发场景下,被其他线程抢先加锁成功的问题return Long.parseLong(new String(oldLockExpireTime)) < System.currentTimeMillis();}}}return false;});}/*** 删除锁.* 这里可能会存在一种异常情况,即如果线程A加锁成功* 但是由于io或GC等原因在有效期内没有执行完逻辑,这时线程B也可拿到锁去执行。* (方案,可以加锁的时候新增当前线程的id作为标识,释放锁时,判断一下,只能释放自己加的锁)** @param lockName 锁名称*/public void delLock(String lockName) {// 直接删除key释放redisredisTemplate.delete(lockName);}
}

然后弄个定时

	/*** 测试方法,用于分布式定时任务的加锁*/
//	@Scheduled(cron = "0/5 * * * * *")public void start(){try {Boolean lock = redisLockUtils.getLock("test", 3000);//获取锁if (lock) {log.info("机器【1】上的 demo 定时任务启动了!>> 当前时间 [{}]", LocalDateTime.now());//延迟一秒Thread.sleep(1000);}else {log.error("获取锁失败了,【其他微服务在执行此定时】此次不执行!");}}catch (Exception e){log.info("获取锁异常了!");}finally {//释放redisredisLockUtils.delLock("test");}}

但是写起来还要改定时任务代码,贼麻烦,直接用切面 环绕的那种,然后抢到锁执行就行.

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;/*** 自定义redis锁注解.* 目的:把加锁解锁逻辑与业务代码解耦.** CreateDate 2021/1/14 16:50*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface TaskRedisLock {/*** 定时任务名称.*/String taskName();/*** 定时任务锁过期时间.*/long expireTime();
}

切面

import com.xhsoft.common.constant.TaskRedisLock;
import com.xhsoft.util.RedisLockUtils;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;import java.lang.reflect.Method;/*** 定时任务锁切面,对加了自定义redis锁注解的任务进行拦截.** @author linzp* @version 1.0.0* CreateDate 2021/1/14 16:59*/
@Component
@Aspect
@Slf4j
public class RedisLockAspect {//加锁工具类@Autowiredprivate RedisLockUtils redisLockUtils;/*** 拦截自定义的redis锁注解.*/@Pointcut("@annotation(com.xhsoft.common.constant.TaskRedisLock)")public void pointCut(){}/*** 环绕通知.*/@Around("pointCut()")public Object Around(ProceedingJoinPoint pjp) throws Throwable {//获取方法MethodSignature methodSignature = (MethodSignature) pjp.getSignature();Method method = methodSignature.getMethod();//获取方法上的注解TaskRedisLock annotation = method.getAnnotation(TaskRedisLock.class);//获取任务名称String taskName = annotation.taskName();//获取失效时间long expireTime = annotation.expireTime();try {//获取锁Boolean lock = redisLockUtils.getLock(taskName, expireTime);if (lock) {return pjp.proceed();}else {log.error("[{} 任务] 获取redis锁失败了,此次不执行...", taskName);}}catch (Exception e){log.error("[{} 任务]获取锁异常了!", taskName, e);}finally {//释放redisredisLockUtils.delLock(taskName);}return null;}
}

最后试试

	/*** 使用自定义TaskRedisLock注解,通过aop来加锁.*/@TaskRedisLock(taskName = "task_1", expireTime = 4000)@Scheduled(cron = "0/5 * * * * *")public void run(){log.info("task_1 定时任务启动了!>> 当前时间 [{}]", LocalDateTime.now());try {//延迟一秒Thread.sleep(3000);} catch (InterruptedException e) {e.printStackTrace();}}


http://www.ppmy.cn/devtools/119165.html

相关文章

Spring Boot 进阶-Spring Boot 如何实现自定义的过滤器详解

在上一篇文章中我们讲解了关于拦截器的相关内容,并且通过一个防抖的例子来讲解了拦截器在实际开发中的使用。这篇文章我们为大家带来的就是关于过滤器的相关内容的分享。下面我们首先来介绍一下什么是过滤器。 什么是过滤器? 过滤器Filter,是Servlet技术中最常用的技术,开…

Motion open Heart 详细动画化开放式心脏解剖

详细和动画的心脏直视解剖。 具有真实的运动和精确的心动周期动画。 包括真实阀门动画序列。 配备高清纹理2048x2048和高清法线贴图,可在教育和游戏方面获得更好、更真实的效果。为(VR)虚拟现实场景和增强现实(AR)做好准备。 下载:​​Unity资源商店链接资源下载链接 …

C++ 排序算法

快速排序 思想&#xff1a; 分而治之&#xff0c;或者说递归&#xff0c;即大问题拆解成类似的小问题&#xff0c;把所有的小问题解决&#xff0c;就解决了大问题&#xff1b; 应用在快排&#xff08;默认从小到大排序&#xff09;上&#xff0c;就是取一基准点&#xff0c;遍…

0基础跟德姆(dom)一起学AI 机器学习02-KNN算法

【理解】KNN算法思想 K-近邻算法&#xff08;K Nearest Neighbor&#xff0c;简称KNN&#xff09;。比如&#xff1a;根据你的“邻居”来推断出你的类别 KNN算法思想&#xff1a;如果一个样本在特征空间中的 k 个最相似的样本中的大多数属于某一个类别&#xff0c;则该样本也属…

从零到 Go 语言开发:你可能需要了解下 5 个关键技巧

你有没有想过,要在 Go 语言中开发一个简单的 Web 应用其实没那么难?或者说,你是不是在学习 Go 语言的时候,常常觉得各种框架和概念让人头疼?今天,我就带你一步一步看看 Go 语言 Web 开发中最核心的几个点,揭开它那看似复杂的面纱,实际操作起来,可能比你想象中简单得多…

【Webpack】使用 Webpack 和 LocalStorage 实现静态资源的离线缓存

基本流程 1&#xff09;使用 Webpack 进行资源打包&#xff1a; 安装 Webpack 及其相关插件。配置 Webpack&#xff0c;将静态资源打包到特定目录。 2&#xff09;配置 Service Worker&#xff1a; 安装 workbox-webpack-plugin 插件。配置 Service Worker&#xff0c;通过…

初识Linux · 地址空间

目录 前言&#xff1a; 代码现象 快速理解该现象 理解部分细节问题 细节1 拷贝和独立性 细节2 如何理解地址空间 细节3 为什么存在地址空间 细节4 如何进一步理解页表和写时拷贝 前言&#xff1a; 本文介绍的是有关地址空间&#xff0c;咱们的介绍的大体思路是&#x…

Python发送邮件教程:如何实现自动化发信?

Python发送邮件有哪些方法&#xff1f;如何利用python发送邮件&#xff1f; 无论是工作汇报、客户通知还是个人提醒&#xff0c;邮件都能快速传递信息。Python发送邮件的自动化功能就显得尤为重要。AokSend将详细介绍如何使用Python发送邮件&#xff0c;实现自动化发信&#x…