分布式开发(7)-基于redisson分布式锁解决定时任务重复执行

  1. 定时任务在部署多台服务时会出现重复执行的问题,所以要借助基于reidis的redisson分布式锁来保证每次只执行一次。
  2. 配置
    2.1 引入redisson
<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>
<dependency>
            <groupId>org.redisson</groupId>
            <artifactId>redisson</artifactId>
            <version>3.14.0</version>
        </dependency>

2.2 redisson配置

import org.redisson.Redisson;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * @author yuwen
 * @date 2021-05-06
 * @description
 */
@Configuration
public class MyRedissonConfig {

    @Bean(destroyMethod = "shutdown")
    public RedissonClient redisson() {
        Config config = new Config();
        // 集群
//        config.useClusterServers().addNodeAddress("redis://10.12.34.53:6379");
        // 单例 如果时ssl安全连接,则使用rediss://
        config.useSingleServer().setAddress("redis://172.29.120.252:6379");
        config.useSingleServer().setPassword("123456");
        return Redisson.create(config);
    }
}

2.3 编写一个注解用于注释表示定时任务

import java.lang.annotation.*;
import java.time.temporal.ChronoUnit;
import java.util.concurrent.TimeUnit;

@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
/**
 * @author yuwen
 * @param lockTime 加锁时间,超过这个时间自动释放锁
 * @param minInterval 最小执行期间,如果前一次任务执行和这一次执行时间差小于这个区间,则跳过这次执行
 * @date 2021-05-06
 * @description
 */
public @interface ScheduleLock {
    String value();
    long lockTime();
    TimeUnit lockTimeUnit();
    long minInterval();
    ChronoUnit intervalTimeUnit();
}

2.4 编写一个切面,用于添加分布式锁

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.reflect.MethodSignature;
import org.redisson.api.RAtomicLong;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.time.Duration;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;

/**
 * @author yuwen
 * @date 2021-05-06
 * @description 通过aop方式给定时任务添加分布式锁并通过原子整长型来控制定时任务执行的次数
 */
@Aspect
@Component
@Slf4j
public class ScheduleLockAspect {

    @Autowired
    private RedissonClient redissonClient;

    @Around("@annotation(com.example.mybatistest.config.ScheduleLock) && " +
            "@annotation(org.springframework.scheduling.annotation.Scheduled)")
    public Object lockAndExecute(ProceedingJoinPoint pjp) throws Throwable {
        MethodSignature signature = (MethodSignature) pjp.getSignature();
        ScheduleLock annotation = signature.getMethod().getAnnotation(ScheduleLock.class);
        String scheduleName = annotation.value();
        String lockName = toLockName(scheduleName);
        // 获取锁
        RLock lock = redissonClient.getLock(lockName);
        try {
            // 锁住
            boolean locked = lock.tryLock(annotation.lockTime(), annotation.lockTime(), annotation.lockTimeUnit());
            if (!locked) {
                log.warn("ScheduleLock {}: getting lock failed", scheduleName);
                return null;
            }
        } catch (InterruptedException e) {
            log.error("ScheduleLock {}: lock interrupted", scheduleName, e);
            return null;
        }
        try {
            // 获取原子整长形
            RAtomicLong atomicUpdateTime = redissonClient.getAtomicLong(toTimestampName(scheduleName));
            Instant lastExecTime = Instant.ofEpochMilli(atomicUpdateTime.get());
            log.info(String.format("redisson原子时间:%s", LocalDateTime.ofInstant(lastExecTime, ZoneId.of("Asia/Shanghai"))));
            log.info(String.format("当前时间:%s", LocalDateTime.ofInstant(Instant.now(), ZoneId.of("Asia/Shanghai"))));
            // 获取当前时间和原子整长形区间
            Duration elapsed = Duration.between(lastExecTime, Instant.now());
            // 手动指定的区间
            Duration minInterval = Duration.of(annotation.minInterval(), annotation.intervalTimeUnit());
            if (elapsed.compareTo(minInterval) >= 0) {
                // 如果区间已经超过指定的区间,则执行当前定时任务
                log.debug("ScheduleLock {}: lock acquired, proceeding", scheduleName);
                Object result = pjp.proceed();
                atomicUpdateTime.set(Instant.now().toEpochMilli());
                log.debug("ScheduleLock {}: execution succeeded", scheduleName);
                return result;
            } else {
                // 如果时间没有达到指定的区间,则不执行当前定时任务
                log.info("ScheduleLock {}: Min execution interval doesn't meet, last exec: {}",
                        scheduleName, lastExecTime);
                return null;
            }
        } finally {
            log.info("解锁");
            lock.unlock();
        }
    }

    private String toLockName(String srcName) {
        return "_schedule_lock__" + srcName;
    }

    private String toTimestampName(String srcName) {
        return "_schedule_timestamp__" + srcName;
    }
}

3 定时任务

import com.example.mybatistest.config.ScheduleLock;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.Scheduled;

import java.time.temporal.ChronoUnit;
import java.util.concurrent.TimeUnit;

@Configuration
@Slf4j
/**
 * @author yuwen
 * @date 2021-05-06
 * @description 定时任务,这里添加两个注解,一个是定时任务注解,一个是自定义注解,自定义注解用来添加分布式锁的
 */
public class TestTask {
    /**
     * 定时10分钟执行一次
     * lockTime 锁的时长,这里锁2min来执行任务,2min后不管任务执行完成与否,都释放锁
     * minInterval 最小执行时间,如果任务在5min内又执行了,则跳过这次执行
     */
    @Scheduled(cron = "0 0/10 * * * ?")
    @ScheduleLock(
            value = "lower_limit_alarm_lock",
            lockTime = 2,
            lockTimeUnit = TimeUnit.MINUTES,
            minInterval = 5,
            intervalTimeUnit = ChronoUnit.MINUTES
    )
    public void testTask() {
        log.info("test");
    }
}
  1. 当然有更好的开源方案,比如XXL-JOB分布式调度平台,很多大公司都在用,个人还是倾向于使用成熟的平台来实现微服务中的定时任务
### 回答1: Redis 支持通过使用 Lua 脚本实现分布式锁。可以使用 Redis 的 SET 命令,并在设置锁时设置过期时间,以避免死锁。 以下是 Redisson 库中实现分布式锁的示例代码: ```java // 创建 RedissonClient 客户端 RedissonClient redisson = Redisson.create(); // 获取锁对象 RLock lock = redisson.getLock("lockName"); // 加锁,设置锁最长占用时间为10s lock.lock(10, TimeUnit.SECONDS); try { // do something } finally { // 释放锁 lock.unlock(); } ``` 注意: 1. 获取锁时需要捕获异常,确保释放锁,防止死锁 2. 为了保证锁能尽可能少时间持有,建议锁的持有时间尽可能短 以上代码使用redission来实现分布式锁,用于解决定时任务重复执行的问题。 ### 回答2: Redission是一个开源的分布式锁框架,可以用于解决定时任务重复执行的问题。下面是一个用Redission实现分布式锁的示例。 在使用Redission之前,需要在项目中添加对Redission依赖的引用。可以在项目的构建文件中添加相关依赖,然后按照文档的指示进行安装和配置。 首先,创建一个任务类,用于执行定时任务的逻辑。在任务类中,可以使用Redission获取分布式锁,并在获取到锁之后执行任务逻辑。在任务逻辑执行完成后,释放锁,以便其他节点能够获取锁执行任务。 下面是一个示例的任务类: ```java import org.redisson.Redisson; import org.redisson.api.RLock; import org.redisson.api.RedissonClient; import org.redisson.config.Config; public class ScheduledTask implements Runnable { private static final String LOCK_NAME = "myLock"; private static final String REDIS_HOST = "127.0.0.1"; private static final int REDIS_PORT = 6379; @Override public void run() { // 创建 Redisson 配置 Config config = new Config(); config.useSingleServer().setAddress("redis://" + REDIS_HOST + ":" + REDIS_PORT); // 创建 Redisson 客户端 RedissonClient redissonClient = Redisson.create(config); // 获取分布式锁 RLock lock = redissonClient.getLock(LOCK_NAME); try { // 尝试获取锁 if (lock.tryLock()) { // 执行任务逻辑 System.out.println("执行定时任务"); } } finally { // 释放锁 lock.unlock(); } } } ``` 在任务逻辑中,首先创建一个Redission的配置对象,并指定Redis的连接地址。然后创建一个Redission客户端对象。 接下来,通过Redission客户端对象获取一个分布式锁,使用tryLock方法尝试获取锁。如果成功获取到锁,则执行任务逻辑。 任务执行完成后,需要手动释放锁,以便其他节点能够获取锁执行任务。 在实际项目中,可以结合Spring的定时任务功能,将上述代码作为一个定时任务执行逻辑。这样就可以实现定时任务分布式环境中的不重复执行。 希望以上内容对您有帮助! ### 回答3: Redission是一个基于Redis的分布式Java框架,可用于解决分布式系统中的各种问题,包括分布式锁。要使用Redission解决定时任务重复执行的问题,可以按照以下步骤进行操作: 1. 引入Redission依赖:在项目的pom.xml文件中添加Redission的依赖,确保项目可以使用Redission相关的类和方法。 2. 创建Redission客户端:使用Redission提供的配置信息,创建一个Redission客户端对象,并与Redis实例建立连接。 3. 获取分布式锁:在每个定时任务开始执行前,通过调用Redission的分布式锁方法获取锁。 4. 判断锁的状态:在获取锁成功后,在定时任务的代码中加入判断锁的逻辑,确保只有第一个获取到锁的任务可以继续执行,其他任务则放弃执行并等待下次定时。 5. 释放锁:在定时任务执行完成后,调用Redission的释放锁方法,释放之前获取的锁资源,以便其他任务可以获取到锁并执行。 通过以上步骤,使用Redission可以实现分布式锁,确保定时任务不会被重复执行。需要注意的是,在使用分布式锁时,应该关注锁的超时时间,以避免某个任务异常退出或崩溃导致锁一直占用而无法释放。另外,还要确保Redis的高可用性,以及Redission客户端与Redis实例之间的网络通信稳定性,以保证分布式锁的可靠性和正确性。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值