后端——》 使用redis分布式锁解决集群环境下定时任务重复执行的问题

  • Post author:
  • Post category:其他


集群环境下,非幂等操作的定时任务通常只能执行一次。但程序在分布在各个服务器上运行,这个时候就可以使用redis分布式锁来保证定时任务只能被执行一次。以下为demo:

我在本地启动了两个程序,每个程序包含一个内容完全相同的定时任务 ,用来模拟在分布式环境。

可以看见,以一分钟执行一轮的频率,可以看到 每轮定时任务只有一个程序 在执行。

那具体的逻辑就是在定时任务中加入 判断是否执行业务逻辑的操作。这个判断的标准就是 通过redis+key来实现:

在业务逻辑被执行之前通过setIfAbsent方法判断redis中是否有key,如果没有key则设置key,设置成功返回true,此时的key是未加锁的状态,如果包含key则返回false。

定时任务代码如下:以下的代码是最简洁的版本,并不是一个很完善的分布式锁,有出bug的可能,此处只为举例用,不细究

import com.baoji.service.DistributeLocker;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import java.text.ParseException;

/**
 * Created by dearx on 2021/01/21.
 */
@Component
public class TestGenerater1 {
    private static final Logger LOGGER = LoggerFactory.getLogger(TestGenerater1.class);

    @Autowired
    private DistributeLocker distributeLocker;

    //每隔1分钟执行一次
    @Scheduled(cron = "0 */1 * * * ?")
    private void configureTasks() throws ParseException {
        boolean locked = distributeLocker.lock("test-DingShiRengWu", distributeLocker.LOCK_TIME_ONE_HOUR);
        if(!locked){
            LOGGER.info("定时任务aaa已经被执行");
            distributeLocker.unlock("test-DingShiRengWu");
        }else{
            LOGGER.info("定时任务aaa开始执行");
            distributeLocker.lock("test-DingShiRengWu");
            try {
                Thread.currentThread().sleep(5000);
                /*业务逻辑处理*/
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            distributeLocker.unlock("test-DingShiRengWu");
            LOGGER.info("定时任务aaa执行完毕");
        }
        LOGGER.info("----------------这一轮执行结束,开始下一轮-------------");
    }

}

redis相关代码如下:

import java.util.concurrent.TimeUnit;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.BoundValueOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;


@Component
public class DistributeLocker {

  private static final Logger LOGGER = LoggerFactory.getLogger(DistributeLocker.class);

  private static final String LOCK = "TRUE";

  private static final long DEFAULT_LOCK_TIME = 60000L;
  
  public static final long LOCK_TIME_ONE_HOUR = 60 * 60000L;

  public static final long LOCK_TIME_HALF_HOUR = 30 * 60000L;

  public static final long LOCK_TIME_FIVE_MINS = 5 * 60000L;
  
  @Autowired
  private RedisTemplate<String, String> stringRedisTemplate;

  /**
   * lock the operation with the default time, the default lock time is 60 seconds
   * @param key the given key
   * @return
   */
  public boolean lock(String key) {
    return lock(key, DEFAULT_LOCK_TIME);
  }
  
  /**
   * lock the operation with the given key
   * @param key the given key
   * @param lockTime unit is milliseconds, the lock time should greater than estimated operation time
   * @return true if lock success
   */
  public boolean lock(String key, long lockTime) {
    BoundValueOperations<String, String> operations = stringRedisTemplate.boundValueOps(key);
    boolean lockSuccess = operations.setIfAbsent(LOCK);
    if(lockSuccess) {
      operations.expire(lockTime, TimeUnit.MILLISECONDS);
    }

    return lockSuccess;

//    RedisConnection connection = null;
//    try {
//      connection = stringRedisTemplate.getConnectionFactory().getConnection();
//      lockSuccess = connection.setNX(key.getBytes(Charset.forName("UTF-8")), LOCK.getBytes(Charset.forName("UTF-8")));
//      if(lockSuccess) {
//        connection.expire(key.getBytes(Charset.forName("UTF-8")), lockTime);
//      }
//    } finally {
//      connection.close();
//    }
  }

  /**
   * unlock the operation with the given key
   * @param key
   */
  public void unlock(String key) {
    stringRedisTemplate.delete(key);
  }

}



版权声明:本文为nienianzhi1744原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。