In a Spring Boot单体应用, standard locking mechanisms work well within a single JVM. How ever, when deploying to a Spring Cloud微服务集群 enviroment, these traditional locks become insufficient since multiple service instances can execute the same scheduled task simultaneously.
A practical scenario involves microservices using Spring's @Scheduled annotations for cron-based jobs. After deploying as a cluster, each server instance triggers its own execution, causing unintended duplicate task runs. The solution requiers distributed locking to ensure only one instance executes the task at a time. For production environments, consider leveraging scheduling frameworks like Quartz or XXL-Job instead.
Basic Redis Implementation
Redis provides a straightforward approach to implementing distributed locks using the SETNX (set if not exists) operation.
@Service
public class TaskSchedulerService {
private static final Logger log = LoggerFactory.getLogger(TaskSchedulerService.class);
@Autowired
private StringRedisTemplate redisTemplate;
@Value("${server.port}")
private String serverPort;
@Scheduled(cron = "0 0/1 6-23 * * ?")
public void runScheduledTask() {
ValueOperations<String, String> lockOps = redisTemplate.opsForValue();
String lockKey = "task-cron-" + DateUtils.format(new Date(), "yyyyMMdd");
String lockValue = UUID.randomUUID().toString();
long expirySeconds = 24 * 60 * 60;
Boolean acquired = lockOps.setIfAbsent(lockKey, lockValue, expirySeconds, TimeUnit.SECONDS);
log.debug("{} attempted lock acquisition: {}", serverPort, acquired);
try {
if (acquired) {
// Business logic execution
processTaskData();
}
} finally {
// Only release if we own the lock
if (lockValue.equals(lockOps.get(lockKey))) {
redisTemplate.delete(lockKey);
log.debug("{} completed task, released lock", serverPort);
}
}
}
private void processTaskData() throws InterruptedException {
Thread.sleep(5000);
}
}
Important: Ensure all cluster servers have synchronized system clocks. Otherwise, tasks may consistently execute on the server with the earliest timestamp.
Using Redisson for Robust Locking
The Redisson library provides a more sophisticated distributed lock implementation with built-in features like auto-renewal and deadlock prevention.
1. Maven Dependency
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson-spring-boot-starter</artifactId>
<version>3.23.0</version>
</dependency>
2. Basic Distributed Lock Implementation
@Service
public class TaskSchedulerService {
private static final Logger log = LoggerFactory.getLogger(TaskSchedulerService.class);
@Value("${server.port}")
private String serverPort;
@Autowired
private RedissonClient redissonClient;
@Scheduled(cron = "0 0/1 6-23 * * ?")
public void executeWithRedissonLock() {
String lockKey = "task-cron-" + DateUtils.format(new Date(), "yyyyMMdd");
long lockTimeout = 24 * 60 * 60;
RLock distributedLock = redissonClient.getLock(lockKey);
boolean lockAcquired = false;
try {
lockAcquired = distributedLock.tryLock();
log.debug("{} acquired lock [{}]: {}", serverPort, lockKey, lockAcquired);
if (lockAcquired) {
distributedLock.lock(lockTimeout, TimeUnit.SECONDS);
// Auto-unlock when timeout expires
processScheduledJob();
}
} catch (Exception e) {
log.error("Lock acquisition failed", e);
} finally {
if (lockAcquired && distributedLock.isHeldByCurrentThread()) {
distributedLock.unlock();
log.debug("{} released lock [{}]", serverPort, lockKey);
}
}
}
private void processScheduledJob() throws InterruptedException {
Thread.sleep(10000);
}
}
3. Lock Auto-Renewal Feature
Redisson can automatically extend lock expiration time for long-running tasks, preventing premature lock release.
@Service
public class TaskSchedulerService {
private static final Logger log = LoggerFactory.getLogger(TaskSchedulerService.class);
@Value("${server.port}")
private String serverPort;
@Autowired
private RedissonClient redissonClient;
@Scheduled(cron = "0 0/1 6-23 * * ?")
public void executeWithAutoRenewal() {
String lockKey = "task-cron-" + DateUtils.format(new Date(), "yyyyMMdd");
long waitTimeSeconds = 10;
RLock distributedLock = redissonClient.getLock(lockKey);
boolean lockAcquired = false;
try {
lockAcquired = distributedLock.tryLock(waitTimeSeconds, TimeUnit.SECONDS);
log.debug("{} acquired lock [{}]: {}", serverPort, lockKey, lockAcquired);
if (lockAcquired) {
log.debug("{} lock [{}] TTL: {} seconds",
serverPort, lockKey, distributedLock.remainTimeToLive() / 1000);
distributedLock.lock();
processHeavyTask();
log.debug("{} lock [{}] remaining TTL: {} seconds",
serverPort, lockKey, distributedLock.remainTimeToLive() / 1000);
}
} catch (Exception e) {
log.error("Execute with renewal failed", e);
} finally {
if (lockAcquired && distributedLock.isHeldByCurrentThread()) {
distributedLock.unlock();
log.debug("{} released lock [{}]", serverPort, lockKey);
}
}
}
private void processHeavyTask() throws InterruptedException {
Thread.sleep(40000);
}
}