背景
在 Java 中分布式锁的实现框架主要包括基于 数据库、Redis 和 Zookeeper 的实现方式。使用 Redis 实现的组件可以选择 Jedis API 或者 Redisson API,使用 Zookeeper 实现的组件可以选择 Zookeeper API 或者 Curator API。笔者在项目中看到不少这种混用 API 的情况,维护性较差。
目标
封装通用接口,屏蔽 API 细节,基于 Spring Boot 自动装配管理。
实现
首先,定义分布式锁接口。
public interface DistributedLock {
/**
* 锁类型
*
* @return 锁类型
*/
String lockType();
/**
* 加锁(阻塞)
*
* @param key 锁对象
*/
boolean lock(String key);
/**
* 加锁(阻塞直到超时)
*
* @param key 锁对象
* @param waitTime 等待时间
* @param timeUnit 时间单位
* @return 是否加锁成功
*/
boolean lock(String key, int waitTime, TimeUnit timeUnit);
/**
* 释放锁
*
* @param key 锁对象
*/
void unlock(String key);
}
使用 Jedis 实现接口。
@RequiredArgsConstructor
@Slf4j
public class JedisDistributedLock implements DistributedLock {
private static final String UNLOCK_LUA =
"if redis.call(\"get\",KEYS[1]) == ARGV[1] "
+ "then return redis.call(\"del\",KEYS[1])"
+ "else return 0 end";
private final TransmittableThreadLocal<String> threadLocal = new TransmittableThreadLocal<>();
private final Jedis jedis;
/**
* 锁类型
*
* @return 锁类型
*/
@Override
public String lockType() {
return DistributedLockType.JEDIS.name();
}
/**
* 阻塞加锁
*
* @param key 锁对象
*/
@Override
public boolean lock(@NonNull String key) {
log.debug("Jedis create lock '{}'", key);
String value = UUID.randomUUID().toString();
SetParams setParams = new SetParams();
setParams.ex(-1);
boolean isSuccess;
try {
String result = jedis.set(key, value, setParams);
isSuccess = StringUtils.isNotEmpty(result);
} catch (Exception e) {
log.error("Jedis create lock '{}', catch exception: {}", key, e.getMessage(), e);
throw new DistributedLockAcquireException(e);
}
if (isSuccess) {
threadLocal.set(value);
log.debug("Jedis create lock '{}' successfully", key);
} else {
log.warn("Jedis create lock '{}' failed", key);
}
return isSuccess;
}
/**
* 加锁
*
* @param key 锁对象
* @param waitTime 等待时间
* @param timeUnit 时间单位
* @return 加锁是否成功
*/
@Override
public boolean lock(@NonNull String key, int waitTime, TimeUnit timeUnit) {
log.warn("Jedis create lock '{}' not support waitTime", key);
return lock(key);
}
/**
* 释放锁
*
* @param key 锁对象
*/
@Override
public void unlock(@NonNull String key) {
log.debug("Jedis release lock '{}'", key);
String uuid = threadLocal.get();
if (uuid == null) {
log.warn("Jedis release lock '{}' failed due to thread local is null", key);
return;
}
List<String> args = Collections.singletonList(uuid);
List<String> keys = Collections.singletonList(key);
Long result;
try {
result = (Long) jedis.eval(UNLOCK_LUA, keys, args);
} catch (Exception e) {
log.error("Jedis release lock '{}', catch exception: {}", key, e.getMessage(), e);
throw new DistributedLockReleaseException(e);
}
if (result == null || result == 0L) {
log.warn("Jedis release lock '{}', but it not work", key);
} else {
log.debug("Jedis release lock '{}' successfully", key);
}
}
}
使用 Redisson 实现接口。
@RequiredArgsConstructor
@Slf4j
public class RedissonDistributedLock implements DistributedLock {
private static final TransmittableThreadLocal<RLock> threadLocal = new TransmittableThreadLocal<>();
private final RedissonClient redissonClient;
/**
* 锁类型
*
* @return 锁类型
*/
@Override
public String lockType() {
return DistributedLockType.REDISSON.name();
}
/**
* 阻塞加锁
*
* @param key 锁对象
*/
@Override
public boolean lock(@NonNull String key) {
log.debug("Redisson create lock '{}'", key);
RLock rLock = redissonClient.getFairLock(key);
boolean isSuccess;
try {
isSuccess = rLock.tryLock();
} catch (Exception e) {
log.error("Redisson create lock '{}', catch exception: {}", key, e.getMessage(), e);
throw new DistributedLockAcquireException(e);
}
return isSuccess;
}
/**
* 加锁
*
* @param key 锁对象
* @param waitTime 等待时间
* @param timeUnit 时间单位
* @return 加锁是否成功
*/
@Override
public boolean lock(@NonNull String key, int waitTime, TimeUnit timeUnit) {
log.debug("Redisson create lock '{}' with waitTime '{}'", key, waitTime);
RLock rLock = redissonClient.getFairLock(key);
boolean isSuccess;
try {
isSuccess = rLock.tryLock(waitTime, 1, timeUnit);
} catch (Exception e) {
log.error("Redisson create lock '{}' with waitTime '{}', catch exception: {}", key, waitTime, e.getMessage(), e);
throw new DistributedLockTimeoutException(e);
}
if (isSuccess) {
threadLocal.set(rLock);
log.debug("Redisson create lock '{}' with waitTime '{}' successfully", key, waitTime);
} else {
log.warn("Redisson create lock '{}' with waitTime '{}' failed", key, waitTime);
}
return isSuccess;
}
/**
* 释放锁
*
* @param key 锁对象
*/
@Override
public void unlock(@NonNull String key) {
log.debug("Redisson release lock '{}'", key);
RLock rLock = threadLocal.get();
if (rLock == null) {
log.warn("Redisson release lock '{}' failed due to thread local is null", key);
return;
}
if (!rLock.isHeldByCurrentThread()) {
log.warn("Curator release lock '{}' failed that is not held by current thread", key);
return;
}
try {
rLock.unlock();
threadLocal.remove();
} catch (Exception e) {
log.error("Redisson release lock '{}', catch exception: {}", key, e.getMessage(), e);
throw new DistributedLockReleaseException(e);
}
log.debug("Redisson release lock '{}' successfully", key);
}
}
使用 Zookeeper 实现接口。
@RequiredArgsConstructor
@Slf4j
public class ZookeeperDistributedLock implements DistributedLock {
private static final TransmittableThreadLocal<String> threadLocal = new TransmittableThreadLocal<>();
private static final byte[] EMPTY_DATA = new byte[0];
private final ZooKeeper zooKeeper;
/**
* 锁类型
*
* @return 锁类型
*/
@Override
public String lockType() {
return DistributedLockType.ZOOKEEPER.name();
}
/**
* 阻塞加锁
*
* @param key 锁对象
*/
@Override
public boolean lock(@NonNull String key) {
log.debug("Zookeeper create lock '{}'", key);
boolean isSuccess;
String result;
try {
result = zooKeeper.create(key, EMPTY_DATA, ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL);
isSuccess = StringUtils.isNotEmpty(result);
} catch (Exception e) {
log.error("Zookeeper create lock '{}', catch exception: {}", key, e.getMessage(), e);
throw new DistributedLockAcquireException(e);
}
if (isSuccess) {
threadLocal.set(result);
log.debug("Zookeeper create lock '{}' successfully", key);
} else {
log.warn("Zookeeper create lock '{}' failed", key);
}
return isSuccess;
}
/**
* 加锁
*
* @param key 锁对象
* @param waitTime 等待时间
* @param timeUnit 时间单位
* @return 加锁是否成功
*/
@Override
public boolean lock(@NonNull String key, int waitTime, TimeUnit timeUnit) {
log.warn("Zookeeper create lock '{}' not support waitTime", key);
return lock(key);
}
/**
* 释放锁
*
* @param key 锁对象
*/
@Override
public void unlock(@NonNull String key) {
log.debug("Zookeeper release lock '{}'", key);
String result = threadLocal.get();
if (result == null) {
log.warn("Zookeeper release lock '{}' failed due to thread local is null", key);
return;
}
try {
zooKeeper.delete(key, -1);
} catch (Exception e) {
log.error("Zookeeper release lock '{}', catch exception: {}", key, e.getMessage(), e);
throw new DistributedLockReleaseException(e);
}
log.debug("Zookeeper release lock '{}' successfully", key);
}
}
使用 Curator 实现接口。
@RequiredArgsConstructor
@Slf4j
public class CuratorDistributedLock implements DistributedLock {
private static final TransmittableThreadLocal<InterProcessMutex> threadLocal = new TransmittableThreadLocal<>();
private final CuratorFramework curatorFramework;
/**
* 锁类型
*
* @return 锁类型
*/
@Override
public String lockType() {
return DistributedLockType.CURATOR.name();
}
/**
* 阻塞加锁
*
* @param key 锁对象
*/
@Override
public boolean lock(@NonNull String key) {
log.debug("Curator create lock '{}'", key);
if (!key.startsWith(Strings.SLASH)) {
throw new DistributedLockAcquireException("Invalid curator lock: " + key);
}
InterProcessMutex interProcessMutex = new InterProcessMutex(curatorFramework, key);
try {
interProcessMutex.acquire();
threadLocal.set(interProcessMutex);
} catch (Exception e) {
log.error("Curator create lock '{}', catch exception: {}", key, e.getMessage(), e);
throw new DistributedLockAcquireException(e);
}
log.debug("Curator create lock '{}' successfully", key);
return true;
}
/**
* 加锁
*
* @param key 锁对象
* @param waitTime 等待时间
* @param timeUnit 时间单位
* @return 加锁是否成功
*/
@Override
public boolean lock(@NonNull String key, int waitTime, TimeUnit timeUnit) {
log.debug("Curator create lock '{}' with waitTime '{}'", key, waitTime);
if (!key.startsWith(Strings.SLASH)) {
throw new DistributedLockAcquireException("Invalid curator lock: " + key);
}
InterProcessMutex interProcessMutex = new InterProcessMutex(curatorFramework, key);
boolean isSuccess;
try {
isSuccess = interProcessMutex.acquire(waitTime, timeUnit);
} catch (Exception e) {
log.error("Curator create lock '{}' with waitTime '{}', catch exception: {}", key, waitTime, e.getMessage(), e);
throw new DistributedLockTimeoutException(e);
}
if (isSuccess) {
threadLocal.set(interProcessMutex);
log.debug("Curator create lock '{}' with waitTime '{} successfully", key, waitTime);
} else {
log.warn("Curator create lock '{}' with waitTime '{} failed", key, waitTime);
}
return isSuccess;
}
/**
* 释放锁
*
* @param key 锁对象
*/
@Override
public void unlock(@NonNull String key) {
log.debug("Curator release lock '{}'", key);
InterProcessMutex interProcessMutex = threadLocal.get();
if (interProcessMutex == null) {
log.warn("Curator release lock '{}' failed due to thread local is null", key);
return;
}
if (!interProcessMutex.isAcquiredInThisProcess()) {
log.warn("Curator release lock '{}' failed that is not acquired in process", key);
return;
}
try {
interProcessMutex.release();
threadLocal.remove();
} catch (Exception e) {
log.error("Curator release lock: {}, catch exception: {}", key, e.getMessage(), e);
throw new DistributedLockReleaseException(e.getMessage());
}
log.debug("Curator release lock '{}' successfully", key);
}
}
将具体实现放入 Spring Boot 管理。
@Setter
@Getter
@ConfigurationProperties(prefix = "distributed-lock")
public class DistributedLockProperties {
private boolean enabled;
private String primary;
private final Redisson redisson = new Redisson();
private final Jedis jedis = new Jedis();
private final Curator curator = new Curator();
private final ZooKeeper zooKeeper = new ZooKeeper();
@Setter
@Getter
public static class Redisson {
private boolean enabled;
}
@Setter
@Getter
public static class Jedis {
private boolean enabled;
}
@Setter
@Getter
public static class Curator {
private boolean enabled;
}
@Setter
@Getter
public static class ZooKeeper {
private boolean enabled;
}
}
@ConditionalOnProperty(prefix = "distributed-lock.jedis", name = "enabled", havingValue = true)
@ConditionalOnBean(Jedis.class)
@ConditionalOnClass(Jedis.class)
@Slf4j
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
@Configuration(proxyBeanMethods = false)
public class JedisDistributedLockAutoConfiguration {
@Bean
public DistributedLock distributedLock(Jedis jedis) {
log.debug("Autowired JedisDistributedLock");
return new JedisDistributedLock(jedis);
}
}
@ConditionalOnProperty(prefix = "distributed-lock.redisson", name = "enabled", havingValue = true)
@AutoConfigureAfter(RedissonAutoConfiguration.class)
@ConditionalOnBean(RedissonClient.class)
@ConditionalOnClass(Redisson.class)
@Slf4j
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
@Configuration(proxyBeanMethods = false)
public class RedissonDistributedLockAutoConfiguration {
@Bean
public DistributedLock distributedLock(RedissonClient redissonClient) {
log.debug("Autowired RedissonDistributedLock");
return new RedissonDistributedLock(redissonClient);
}
}
@ConditionalOnProperty(prefix = "distributed-lock.zookeeper", name = "enabled", havingValue = true)
@ConditionalOnClass(ZooKeeper.class)
@ConditionalOnBean(ZookeeperTemplate.class)
@Slf4j
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
@Configuration(proxyBeanMethods = false)
public class ZookeeperDistributedLockAutoConfiguration {
@Bean
public DistributedLock distributedLock(ZookeeperTemplate zookeeperTemplate) {
log.debug("Autowired ZookeeperDistributedLock");
return new ZookeeperDistributedLock(zookeeperTemplate.getZookeeper());
}
}
@ConditionalOnProperty(prefix = "distributed-lock.curator", name = "enabled", havingValue = true)
@ConditionalOnClass(CuratorFramework.class)
@Slf4j
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
@Configuration(proxyBeanMethods = false)
public class CuratorDistributedLockAutoConfiguration {
@Bean
public DistributedLock distributedLock(CuratorFramework curatorFramework) {
log.debug("Autowired CuratorDistributedLock");
return new CuratorDistributedLock(curatorFramework);
}
}
当项目配置 distributed-lock.redisson.enabled=true 时,开启 Redisson 作为分布式锁的底层实现。
distributed-lock:
redisson:
enabled: true # 开启 Redisson 作为分布式锁的底层实现
spring:
redis:
password: demo@123
timeout: 5000
database: 1
host: localhost
port: 6379
当项目配置 distributed-lock.curator.enabled=true 时,开启 Curator 作为分布式锁的底层实现。
distributed-lock:
curator:
enabled: true # 开启 Curator 作为分布式锁的底层实现
spring:
cloud:
zookeeper:
enabled: true
connectString: localhost:2181
业务代码只需要引入 DistributedLock 接口,调用 lock 和 unlock 方法完成分布式场景的加锁和解锁。
@RequiredArgsConstructor
@Slf4j
public cliass Demo {
private final DistributedLock distributedLock;
public void test() {
String key = "demo";
if (distributedLock.lock(key, 2, TimeUnit.SECONDS)) {
// do something...
distributedLock.unlock(key);
}
}
}
产出
通过 API 封装,简化了业务项目对分布式锁的繁琐编码工作,提高开发效率。
本文涉及的代码完全开源,感兴趣的伙伴可以查阅 eden-distributed-lock 和 eden-distributed-lock-spring-boot-starter。