Implementing Distributed Locks with Spring Integration (Redis and Zookeeper)
This article explains how to use Spring Integration to implement distributed locks across Redis and Zookeeper, detailing the unified lock API, required dependencies, configuration steps, and sample code for both storage backends.
Overview: Distributed locks are commonly implemented with Redisson (redlock) or Apache Curator (Zookeeper). This article introduces a third approach using Spring Integration, which provides a lightweight messaging framework and abstracts lock implementations across various stores.
Spring Integration supports global locks for Gemfire, JDBC, Redis, and Zookeeper via a unified API, allowing easy switching of the underlying storage without code changes.
Lock interface methods include lock() , lockInterruptibly() , tryLock() , tryLock(long time, TimeUnit unit) , and unlock() , each described in the table.
Implementation with Redis:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-integration</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.integration</groupId>
<artifactId>spring-integration-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>Configure Redis in application.yml and define a RedisLockRegistry bean.
@Configuration
public class RedisLockConfiguration {
@Bean
public RedisLockRegistry redisLockRegistry(RedisConnectionFactory redisConnectionFactory) {
return new RedisLockRegistry(redisConnectionFactory, "redis-lock");
}
}Test controller uses the registry to obtain a lock, try to acquire it with a timeout, perform business logic, and finally release the lock.
@RestController
@RequestMapping("lock")
@Log4j2
public class DistributedLockController {
@Autowired
private RedisLockRegistry redisLockRegistry;
@GetMapping("/redis")
public void test1() {
Lock lock = redisLockRegistry.obtain("redis");
try {
if (lock.tryLock(3, TimeUnit.SECONDS)) {
log.info("lock is ready");
TimeUnit.SECONDS.sleep(5);
}
} catch (InterruptedException e) {
log.error("obtain lock error", e);
} finally {
lock.unlock();
}
}
}Running multiple instances and accessing /lock/redis demonstrates that only one instance holds the lock.
Implementation with Zookeeper follows a similar pattern, adding the spring-integration-zookeeper dependency, configuring Zookeeper host, and defining a ZookeeperLockRegistry bean.
@Configuration
public class ZookeeperLockConfiguration {
@Value("${zookeeper.host}")
private String zkUrl;
@Bean
public CuratorFrameworkFactoryBean curatorFrameworkFactoryBean() {
return new CuratorFrameworkFactoryBean(zkUrl);
}
@Bean
public ZookeeperLockRegistry zookeeperLockRegistry(CuratorFramework curatorFramework) {
return new ZookeeperLockRegistry(curatorFramework, "/zookeeper-lock");
}
}The test controller mirrors the Redis example, using /lock/zookeeper to verify lock behavior.
@RestController
@RequestMapping("lock")
@Log4j2
public class DistributedLockController {
@Autowired
private ZookeeperLockRegistry zookeeperLockRegistry;
@GetMapping("/zookeeper")
public void test2() {
Lock lock = zookeeperLockRegistry.obtain("zookeeper");
try {
if (lock.tryLock(3, TimeUnit.SECONDS)) {
log.info("lock is ready");
TimeUnit.SECONDS.sleep(5);
}
} catch (InterruptedException e) {
log.error("obtain lock error", e);
} finally {
lock.unlock();
}
}
}Note: When using newer Spring Boot versions, ensure the Redis server supports the UNLINK command (Redis 4+) to avoid warnings during lock release.
Full-Stack Internet Architecture
Introducing full-stack Internet architecture technologies centered on Java
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.