集群环境下,非幂等操作的定时任务通常只能执行一次。但程序在分布在各个服务器上运行,这个时候就可以使用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 版权协议,转载请附上原文出处链接和本声明。