Implementing Distributed Locks with Redis in Java Spring Applications

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);
    }
}

Tags: Redis distributed-locks java spring-boot Redisson

Posted on Sun, 06 Sep 2026 16:47:46 +0000 by phpcoder24july