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

集群环境下,非幂等操作的定时任务通常只能执行一次。但程序在分布在各个服务器上运行,这个时候就可以使用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);
  }

}

 

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
使用 Redis 分布式锁可以很好地解决同时调用多个接口的情况下出现的重复赠送问题,具体步骤如下: 1. 在 Redis 中创建一个键(key),作为分布式锁的标识符。可以使用 Redis 的 SETNX 命令来创建一个键,如果键不存在,则创建成功,并设置一个过期时间,表示在一定时间后自动释放锁。 2. 如果 SETNX 命令返回的结果是 1,则说明锁创建成功,可以进行赠送操作;如果返回的结果是 0,则说明锁已经被其他进程占用,需要等待一段时间后再次尝试获取锁。 3. 在进行赠送操作之后,需要使用 Redis 的 DEL 命令来删除该键,释放锁。如果在赠送操作过程中出现了异常或者程序崩溃等情况,也需要确保最终能够删除该键,避免出现死锁的情况。 以下是使用 Redis 分布式锁的示例代码: ```python import redis # 连接 Redis redis_conn = redis.Redis(host='localhost', port=6379, db=0) # 锁的标识符和过期时间 lock_key = 'gift_lock' lock_expire = 30 # 尝试获取锁 lock_acquired = False while not lock_acquired: # 使用 SETNX 命令创建键,如果返回 1,则说明创建成功 lock_acquired = redis_conn.setnx(lock_key, 1) if lock_acquired: # 设置过期时间,避免出现死锁的情况 redis_conn.expire(lock_key, lock_expire) else: # 等待一段时间后再次尝试获取锁 time.sleep(0.1) # 进行赠送操作,例如调用赠送接口等 # 释放锁 redis_conn.delete(lock_key) ``` 通过以上代码,我们可以实现在多个进程同时调用赠送接口时,确保只有一个进程能够进行赠送操作,避免出现重复赠送的情况。需要注意的是,分布式锁并不能完全解决并发问题,如果多个进程同时调用赠送接口的频率非常高,仍然有可能出现竞争条件,需要根据具体的业务场景进行优化。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值