通过Zookeeper与Redisson实现Spring注解式分布式锁
在分布式系统中,由于服务部署在多个节点中,在运行时数据层面上天生无法互通,因此在处理多线程敏感的数据时,无法像单机应用开发一样通过java内部实现的多线程方案来解决。此时我们就需要通过分布式锁的方式来处理数据。
本文将提供一个在spring-cloud基础上通过ZooKeeper或者Redisson实现的注解式的加锁方案。需要使用到Spring AOP,通过ZooKeeper加锁时需要ZooKeeper服务,使用Redisson加锁时需要使用Redis数据库。
依赖
当使用Redisson方式时需要引入Redisson依赖,版本号自行匹配替换。
<!-- https://mvnrepository.com/artifact/org.redisson/redisson -->
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>${redisson.version}</version>
</dependency>
当使用ZooKeeper方式时需要引入连接ZooKeeper的依赖,版本号自行匹配替换。
<!-- https://mvnrepository.com/artifact/org.apache.curator/curator-recipes -->
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-recipes</artifactId>
<version>${curator-recipes.version}</version>
</dependency>
配置访问锁服务的组件
Redisson
使用redisson作为锁服务的实现时,需要如下配置类配置RedissonClient组件。
@Configuration
public class RedissonLockConfig {
@Value("${cloud.lock.redis.url:redis://localhost:6379}")
private String redisUrl;
@Value("${cloud.lock.redis.password:}")
private String redisPassword;
@Bean
public RedissonClient redissonClient() {
Config config = new Config();
SingleServerConfig singleServerConfig = config.useSingleServer();
singleServerConfig.setAddress(redisUrl);
if (StringUtils.isNotEmpty(redisPassword)) {
singleServerConfig.setPassword(redisPassword);
}
return Redisson.create(config);
}
}
其中redisUrl为Redis服务的地址,redisPassword为Redis服务的密码。
RedissonClient连接配置可以按需进行调整。
ZooKeeper
使用ZooKeeper作为锁服务的实现时,需要如下配置类配置CuratorFramework组件。
@Configuration
public class ZooKeeperLockConfig {
@Value("${cloud.lock.zookeeper.url:localhost:2181}")
private String url;
@Value("${cloud.lock.zookeeper.timeout:1000}")
private int timeout;
@Value("${cloud.lock.zookeeper.retry:3}")
private int retry;
@Bean
public CuratorFramework zkClient() {
ExponentialBackoffRetry exponentialBackoffRetry = new ExponentialBackoffRetry(timeout, retry);
CuratorFramework curatorFramework = CuratorFrameworkFactory.newClient(url, exponentialBackoffRetry);
curatorFramework.start();
return curatorFramework;
}
}
其中url是ZooKeeper服务地址,timeout是连接超时时间,retry是连接失败重试次数。
CuratorFramework连接配置可以按需进行调整。
加锁逻辑
在分布式系统中,多系统之间的数据很可能不互通,因此加锁肯定不是通过原始数据进行加锁。
在加锁时,我们应该对应原始数据,生成唯一的对应该数据的token作为加锁依据的key,同时该key在无论何时对于该原始数据而言生成的都是这个key。这样一来,通过该key明确地指向原始数据进行加锁与解锁。
例如在系统中,存在张User表,该表中id列为其主键。那么在进行数据加锁时,则可以考虑以如下格式:“User-[id数据]”来进行加锁解锁操作。
加锁实现
在进行实现时,首先考虑到一次加锁时可能涉及到多个数据,因此使用key集合来进行加锁,timeout与timeunit用于指代锁的超时时长。
抽象出如下方法:
public abstract LockEntity doLock(List<String> keys, Long timeout, TimeUnit unit) throws Exception;
LockEntity为锁实例,用于进行解锁操作
public class LockEntity {
private final Object lock;
LockEntity(Object lock) {
this.lock = lock;
}
public void unlock() throws Exception {
if (lock instanceof RLock rLock) {
rLock.unlock();
} else if (lock instanceof InterProcessLock interProcessLock) {
interProcessLock.release();
}
}
}
针对不同的锁类型,采取不同的解锁方法
Redisson
逻辑:先通过RedissonClient组件遍历keys集合获取所有锁实例,再将以这些锁实例来获取一个MultiLock实例,来进行多锁的统一加锁解锁,再根据提供的timeout和unit加锁并返回LockEntity对象用来操作解锁。
源码:
public LockEntity doLock(Set<String> keys, Long timeout, TimeUnit unit) {
ArrayList<RLock> rLocks = new ArrayList<>(keys.size());
for (String key : keys) {
rLocks.add(redissonClient.getLock(key));
}
RLock multiLock = redissonClient.getMultiLock(rLocks.toArray(new RLock[0]));
if (timeout != null && timeout > 0 && unit != null) {
multiLock.lock(timeout, unit);
} else {
multiLock.lock();
}
return new LockEntity(multiLock);
}
ZooKeeper
逻辑:同Redisson类似,先构造多锁对象,然后使用该对象进行加锁并返回
源码:
public LockEntity doLock(Set<String> lockKeys, Long timeout, TimeUnit unit) throws Exception {
InterProcessMultiLock multiLock = new InterProcessMultiLock(zkClient, lockKeys.stream().toList());
if (timeout != null && timeout > 0 && unit != null) {
multiLock.acquire(timeout, unit);
} else {
multiLock.acquire();
}
return new LockEntity(multiLock);
}
实现通过注解加锁
定义注解
-
首先针对锁的实现方式,定义区分注解:
-
@RedisLock
@Target({ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) public @interface RedisLock { long timeout() default 0; TimeUnit unit() default TimeUnit.SECONDS; }
-
@ZkLock
@Target({ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) public @interface ZkLock { long timeout() default 0; TimeUnit unit() default TimeUnit.SECONDS; }
-
这两个注解作用于方法上,用于表明当前方法需要使用到分布式锁,以及需要使用到哪种分布式锁,并提供锁的超时时间。
- 其次针对方法参数中的的同步参数,提供一个注解@SyncArg。
@Target({ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
public @interface SyncArg {
/**
* @return 加锁依据,填入Spel表达式,用于生成锁key
* 应该确保每一个加锁对象能够生成一个固定的且独立的key
* 使用时可用上下文包括 arg:当前参数,method:当前运行的方法
*/
String value() default "#arg.hashCode()";
/**
* @return 是否采用多对象解析方式,多对象时将产生多个锁并同步加锁
* 当传入对象实现了 Iterable时,将遍历每一个子项,并且生成key时arg为当前遍历到的对象
* 当传入对象实现了Map时,将遍历每个Entry,生成key时arg为当前Entry
*/
boolean isMulti() default false;
}
该注解作用于方法参数,表明该参数需要参照value值生成key来加锁,isMulti用于标识当前参数是否是多对象参数。
处理注解
首先,通过一个上下文对象用来自动生成加锁的key。
@Getter
@Setter
public class LockContext {
/**
* 被加锁的参数对象
*/
private Object arg;
/**
* 生成key的Spel表达式
*/
private String keyExpression;
/**
* 是否为多对象
*/
private boolean multi;
/**
* 上下文参数
*/
@Getter(AccessLevel.PACKAGE)
private final Map<String, Object> params;
public LockContext(SyncArg syncArg, Object arg) {
this(arg, syncArg.value(), syncArg.isMulti());
}
public LockContext(Object arg, String keyExpression, boolean multi) {
this(arg, keyExpression, multi, new HashMap<>());
}
public LockContext(Object arg, String keyExpression, boolean multi, Map<String, Object> params) {
this.arg = arg;
this.keyExpression = keyExpression;
this.multi = multi;
this.params = params;
}
/**
* 添加上下文参数
*
* @param key key
* @param value value
* @return 用于链式调用
*/
public LockContext addParam(String key, Object value) {
params.put(key, value);
return this;
}
/**
* 删除上下文参数
*
* @param key key
* @return 用于链式调用
*/
public LockContext removeParam(String key) {
params.remove(key);
return this;
}
@Override
public String toString() {
return getKey(new SpelExpressionParser());
}
/**
* 生成key
*
* @param parser Spel表达式解析器
* @return key
*/
public String getKey(SpelExpressionParser parser) {
StandardEvaluationContext context = new StandardEvaluationContext();
HashMap<String, Object> contextMap = new HashMap<>(params);
if (!contextMap.containsKey("arg")) {
contextMap.put("arg", arg);
}
context.setVariables(contextMap);
return parser.parseExpression(keyExpression).getValue(context, String.class);
}
}
这里通过继承抽象类的方式在抽象类中提供一些公共方法,由子类进行加锁的实现。
public abstract class AbstractLockService {
/**
* 格式化生成的key,在生成加锁key时会经过该方法,并以返回的key作为最终加锁时的key
*
* @param generatedKey 通过上下文对象自动生成的key
* @return 格式化后的key
*/
public abstract String formatKey(String generatedKey);
/**
* 通过LockContext的形式生成锁key再加锁
*
* @param lockContexts 用于生成锁key
* @param timeout 锁超时时间
* @param unit 时间单位
* @return 锁对象
*/
protected LockEntity doLockByContext(List<LockContext> lockContexts, Long timeout, TimeUnit unit) throws Exception {
return doLock(getLockKeys(lockContexts), timeout, unit);
}
public LockEntity doLock(Set<String> keys) throws Exception {
return doLock(keys, null, null);
}
/**
* 通过锁keys进行加锁,由子类实现
*
* @param keys 锁key
* @param timeout 超时时间
* @param unit 时间单位
* @return 锁对象
*/
public abstract LockEntity doLock(Set<String> keys, Long timeout, TimeUnit unit) throws Exception;
/**
* 根据方法及入参解析出LockContext列表
*
* @param method 加锁的方法
* @param args 入参
* @return LockContext列表
*/
protected List<LockContext> getLockContexts(Method method, Object[] args) {
Annotation[][] parameterAnnotations = method.getParameterAnnotations();
ArrayList<LockContext> lockContexts = new ArrayList<>();
for (int i = 0; i < parameterAnnotations.length; i++) {
//获取参数注解,与参数一一对应
Annotation[] annotations = parameterAnnotations[i];
//找到@SyncArg注解
SyncArg syncArg = null;
for (Annotation annotation : annotations) {
if (annotation instanceof SyncArg syncArgA) {
syncArg = syncArgA;
break;
}
}
if (syncArg != null) {
//注解存在则通过注解构造上下文对象
LockContext lockContext = new LockContext(syncArg, args[i]);
lockContext.addParam("method", method);
lockContexts.add(lockContext);
}
}
return lockContexts;
}
/**
* 通过上下文对象列表生成key集合
*
* @param lockContexts 上下文对象列表
* @return key集合
*/
protected HashSet<String> getLockKeys(List<LockContext> lockContexts) {
//解析上下文列表,生成key
HashSet<String> keys = new HashSet<>();
SpelExpressionParser parser = new SpelExpressionParser();
for (LockContext lockContext : lockContexts) {
boolean multi = lockContext.isMulti();
Object arg = lockContext.getArg();
String keyExpression = lockContext.getKeyExpression();
if (multi && arg instanceof Iterable<?> iterable) {
//多对象模式且参数对象为Iterable时
for (Object o : iterable) {
LockContext singleContext = new LockContext(o, keyExpression, false, lockContext.getParams());
keys.add(formatKey(singleContext.getKey(parser)));
}
} else if (multi && arg instanceof Map<?, ?> map) {
//多对象模式且参数对象为map时
for (Map.Entry<?, ?> entry : map.entrySet()) {
LockContext singleContext = new LockContext(entry, keyExpression, false, lockContext.getParams());
keys.add(formatKey(singleContext.getKey(parser)));
}
} else {
keys.add(formatKey(lockContext.getKey(parser)));
}
}
return keys;
}
}
Redisson实现
@Slf4j
@Aspect
@Component
public class RedissonLockService extends AbstractLockService {
private final RedissonClient redissonClient;
@Autowired
public RedissonLockService(RedissonClient redissonClient) {
this.redissonClient = redissonClient;
}
@Pointcut("@annotation(org.joker.commons.cloud.lock.annotation.RedisLock)")
private void doLockByContext() {
}
@Around("doLockByContext()")
private Object around(ProceedingJoinPoint point) throws Throwable {
//获取方法对象
MethodSignature signature = (MethodSignature) point.getSignature();
Method method = signature.getMethod();
//解析用于生成锁的上下文
List<LockContext> lockContexts = getLockContexts(method, point.getArgs());
//加锁并执行
RedisLock lock = method.getAnnotation(RedisLock.class);
LockEntity lockEntity = doLockByContext(lockContexts, lock.timeout(), lock.unit());
try {
return point.proceed();
} finally {
lockEntity.unlock();
}
}
@Override
public String formatKey(String generatedKey) {
return "cloud:lock:" + generatedKey;
}
@Override
public LockEntity doLock(Set<String> keys, Long timeout, TimeUnit unit) {
ArrayList<RLock> rLocks = new ArrayList<>(keys.size());
for (String key : keys) {
rLocks.add(redissonClient.getLock(key));
}
RLock multiLock = redissonClient.getMultiLock(rLocks.toArray(new RLock[0]));
if (timeout != null && timeout > 0 && unit != null) {
multiLock.lock(timeout, unit);
} else {
multiLock.lock();
}
return new LockEntity(multiLock);
}
}
通过AOP的方法,在@RedisLock注解处创建切面,在执行该方法之前先对该方法的参数进行处理,产生上下文列表,并依据该列表生成对应的key集合,再通过该key集合尝试获取锁。在获取锁成功后再执行对应的方法,在finally代码块中释放锁。
ZooKeeper实现
@Slf4j
@Aspect
@Component
public class ZooKeeperLockService extends AbstractLockService {
private final CuratorFramework zkClient;
@Autowired
public ZooKeeperLockService(CuratorFramework zkClient) {
this.zkClient = zkClient;
}
@Pointcut("@annotation(org.joker.commons.cloud.lock.annotation.ZkLock)")
private void doLockByContext() {
}
@Around("doLockByContext()")
private Object around(ProceedingJoinPoint point) throws Throwable {
//获取方法对象
MethodSignature signature = (MethodSignature) point.getSignature();
Method method = signature.getMethod();
//解析用于生成锁的上下文
List<LockContext> lockContexts = getLockContexts(method, point.getArgs());
//加锁并执行
ZkLock zkLock = method.getAnnotation(ZkLock.class);
LockEntity lockEntity = doLockByContext(lockContexts, zkLock.timeout(), zkLock.unit());
try {
return point.proceed();
} finally {
lockEntity.unlock();
}
}
@Override
public String formatKey(String generatedKey) {
return "/cloud/lock/" + generatedKey;
}
@Override
public LockEntity doLock(Set<String> lockKeys, Long timeout, TimeUnit unit) throws Exception {
InterProcessMultiLock multiLock = new InterProcessMultiLock(zkClient, lockKeys.stream().toList());
if (timeout != null && timeout > 0 && unit != null) {
multiLock.acquire(timeout, unit);
} else {
multiLock.acquire();
}
return new LockEntity(multiLock);
}
}
同Redisson类似,在ZkLock处创建切面,先根据注解解析对应参数的锁key后,再获取锁,然后执行方法并释放锁。
完整代码
以下按照包结构排列
-
…
-
lock
-
annotation
-
RedisLock
@Target({ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) public @interface RedisLock { long timeout() default 0; TimeUnit unit() default TimeUnit.SECONDS; }
-
SyncArg
@Target({ElementType.PARAMETER}) @Retention(RetentionPolicy.RUNTIME) public @interface SyncArg { /** * @return 加锁依据,填入Spel表达式,用于生成锁key * 应该确保每一个加锁对象能够生成一个固定的且独立的key * 使用时可用上下文包括 arg:当前参数,method:当前运行的方法 */ String value() default "#arg.hashCode()"; /** * @return 是否采用多对象解析方式,多对象时将产生多个锁并同步加锁 * 当传入对象实现了 Iterable时,将遍历每一个子项,并且生成key时arg为当前遍历到的对象 * 当传入对象实现了Map时,将遍历每个Entry,生成key时arg为当前Entry */ boolean isMulti() default false; }
-
ZkLock
@Target({ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) public @interface ZkLock { long timeout() default 0; TimeUnit unit() default TimeUnit.SECONDS; }
-
-
config
-
RedissonLockConfig
@Configuration public class RedissonLockConfig { @Value("${cloud.lock.redis.url:redis://localhost:6379}") private String redisUrl; @Value("${cloud.lock.redis.password:}") private String redisPassword; @Bean public RedissonClient redissonClient() { Config config = new Config(); SingleServerConfig singleServerConfig = config.useSingleServer(); singleServerConfig.setAddress(redisUrl); if (StringUtils.isNotEmpty(redisPassword)) { singleServerConfig.setPassword(redisPassword); } return Redisson.create(config); } }
-
ZooKeeperLockConfig
@Configuration public class ZooKeeperLockConfig { @Value("${cloud.lock.zookeeper.url:localhost:2181}") private String url; @Value("${cloud.lock.zookeeper.timeout:1000}") private int timeout; @Value("${cloud.lock.zookeeper.retry:3}") private int retry; @Bean public CuratorFramework zkClient() { ExponentialBackoffRetry exponentialBackoffRetry = new ExponentialBackoffRetry(timeout, retry); CuratorFramework curatorFramework = CuratorFrameworkFactory.newClient(url, exponentialBackoffRetry); curatorFramework.start(); return curatorFramework; } }
-
-
AbstractLockService
public abstract class AbstractLockService { /** * 格式化生成的key,在生成加锁key时会经过该方法,并以返回的key作为最终加锁时的key * * @param generatedKey 通过上下文对象自动生成的key * @return 格式化后的key */ public abstract String formatKey(String generatedKey); /** * 通过LockContext的形式生成锁key再加锁 * * @param lockContexts 用于生成锁key * @param timeout 锁超时时间 * @param unit 时间单位 * @return 锁对象 */ protected LockEntity doLockByContext(List<LockContext> lockContexts, Long timeout, TimeUnit unit) throws Exception { return doLock(getLockKeys(lockContexts), timeout, unit); } public LockEntity doLock(Set<String> keys) throws Exception { return doLock(keys, null, null); } /** * 通过锁keys进行加锁,由子类实现 * * @param keys 锁key * @param timeout 超时时间 * @param unit 时间单位 * @return 锁对象 */ public abstract LockEntity doLock(Set<String> keys, Long timeout, TimeUnit unit) throws Exception; /** * 根据方法及入参解析出LockContext列表 * * @param method 加锁的方法 * @param args 入参 * @return LockContext列表 */ protected List<LockContext> getLockContexts(Method method, Object[] args) { Annotation[][] parameterAnnotations = method.getParameterAnnotations(); ArrayList<LockContext> lockContexts = new ArrayList<>(); for (int i = 0; i < parameterAnnotations.length; i++) { //获取参数注解,与参数一一对应 Annotation[] annotations = parameterAnnotations[i]; //找到@SyncArg注解 SyncArg syncArg = null; for (Annotation annotation : annotations) { if (annotation instanceof SyncArg syncArgA) { syncArg = syncArgA; break; } } if (syncArg != null) { //注解存在则通过注解构造上下文对象 LockContext lockContext = new LockContext(syncArg, args[i]); lockContext.addParam("method", method); lockContexts.add(lockContext); } } return lockContexts; } /** * 通过上下文对象列表生成key集合 * * @param lockContexts 上下文对象列表 * @return key集合 */ protected HashSet<String> getLockKeys(List<LockContext> lockContexts) { //解析上下文列表,生成key HashSet<String> keys = new HashSet<>(); SpelExpressionParser parser = new SpelExpressionParser(); for (LockContext lockContext : lockContexts) { boolean multi = lockContext.isMulti(); Object arg = lockContext.getArg(); String keyExpression = lockContext.getKeyExpression(); if (multi && arg instanceof Iterable<?> iterable) { //多对象模式且参数对象为Iterable时 for (Object o : iterable) { LockContext singleContext = new LockContext(o, keyExpression, false, lockContext.getParams()); keys.add(formatKey(singleContext.getKey(parser))); } } else if (multi && arg instanceof Map<?, ?> map) { //多对象模式且参数对象为map时 for (Map.Entry<?, ?> entry : map.entrySet()) { LockContext singleContext = new LockContext(entry, keyExpression, false, lockContext.getParams()); keys.add(formatKey(singleContext.getKey(parser))); } } else { keys.add(formatKey(lockContext.getKey(parser))); } } return keys; } }
-
LockContext
@Getter @Setter public class LockContext { /** * 被加锁的参数对象 */ private Object arg; /** * 生成key的Spel表达式 */ private String keyExpression; /** * 是否为多对象 */ private boolean multi; /** * 上下文参数 */ @Getter(AccessLevel.PACKAGE) private final Map<String, Object> params; public LockContext(SyncArg syncArg, Object arg) { this(arg, syncArg.value(), syncArg.isMulti()); } public LockContext(Object arg, String keyExpression, boolean multi) { this(arg, keyExpression, multi, new HashMap<>()); } public LockContext(Object arg, String keyExpression, boolean multi, Map<String, Object> params) { this.arg = arg; this.keyExpression = keyExpression; this.multi = multi; this.params = params; } /** * 添加上下文参数 * * @param key key * @param value value * @return 用于链式调用 */ public LockContext addParam(String key, Object value) { params.put(key, value); return this; } /** * 删除上下文参数 * * @param key key * @return 用于链式调用 */ public LockContext removeParam(String key) { params.remove(key); return this; } @Override public String toString() { return getKey(new SpelExpressionParser()); } /** * 生成key * * @param parser Spel表达式解析器 * @return key */ public String getKey(SpelExpressionParser parser) { StandardEvaluationContext context = new StandardEvaluationContext(); HashMap<String, Object> contextMap = new HashMap<>(params); if (!contextMap.containsKey("arg")) { contextMap.put("arg", arg); } context.setVariables(contextMap); return parser.parseExpression(keyExpression).getValue(String.class); } }
-
LockEntity
public class LockEntity { private final Object lock; LockEntity(Object lock) { this.lock = lock; } public void unlock() throws Exception { if (lock instanceof RLock rLock) { rLock.unlock(); } else if (lock instanceof InterProcessLock interProcessLock) { interProcessLock.release(); } } }
-
RedissonLockService
@Slf4j @Aspect @Component public class RedissonLockService extends AbstractLockService { private final RedissonClient redissonClient; @Autowired public RedissonLockService(RedissonClient redissonClient) { this.redissonClient = redissonClient; } @Pointcut("@annotation(org.joker.commons.cloud.lock.annotation.RedisLock)") private void doLockByContext() { } @Around("doLockByContext()") private Object around(ProceedingJoinPoint point) throws Throwable { //获取方法对象 MethodSignature signature = (MethodSignature) point.getSignature(); Method method = signature.getMethod(); //解析用于生成锁的上下文 List<LockContext> lockContexts = getLockContexts(method, point.getArgs()); //加锁并执行 RedisLock lock = method.getAnnotation(RedisLock.class); LockEntity lockEntity = doLockByContext(lockContexts, lock.timeout(), lock.unit()); try { return point.proceed(); } finally { lockEntity.unlock(); } } @Override public String formatKey(String generatedKey) { return "cloud:lock:" + generatedKey; } @Override public LockEntity doLock(Set<String> keys, Long timeout, TimeUnit unit) { ArrayList<RLock> rLocks = new ArrayList<>(keys.size()); for (String key : keys) { rLocks.add(redissonClient.getLock(key)); } RLock multiLock = redissonClient.getMultiLock(rLocks.toArray(new RLock[0])); if (timeout != null && timeout > 0 && unit != null) { multiLock.lock(timeout, unit); } else { multiLock.lock(); } return new LockEntity(multiLock); } }
-
ZooKeeper
@Slf4j @Aspect @Component public class ZooKeeperLockService extends AbstractLockService { private final CuratorFramework zkClient; @Autowired public ZooKeeperLockService(CuratorFramework zkClient) { this.zkClient = zkClient; } @Pointcut("@annotation(org.joker.commons.cloud.lock.annotation.ZkLock)") private void doLockByContext() { } @Around("doLockByContext()") private Object around(ProceedingJoinPoint point) throws Throwable { //获取方法对象 MethodSignature signature = (MethodSignature) point.getSignature(); Method method = signature.getMethod(); //解析用于生成锁的上下文 List<LockContext> lockContexts = getLockContexts(method, point.getArgs()); //加锁并执行 ZkLock zkLock = method.getAnnotation(ZkLock.class); LockEntity lockEntity = doLockByContext(lockContexts, zkLock.timeout(), zkLock.unit()); try { return point.proceed(); } finally { lockEntity.unlock(); } } @Override public String formatKey(String generatedKey) { return "/cloud/lock/" + generatedKey; } @Override public LockEntity doLock(Set<String> lockKeys, Long timeout, TimeUnit unit) throws Exception { InterProcessMultiLock multiLock = new InterProcessMultiLock(zkClient, lockKeys.stream().toList()); if (timeout != null && timeout > 0 && unit != null) { multiLock.acquire(timeout, unit); } else { multiLock.acquire(); } return new LockEntity(multiLock); } }
-
-
测试效果
采用如下Spring服务类进行效果测试,其中run中为测试代码,表达式“#arg.getClass().getSimpleName()+#arg.getId()”以id作为唯一主键生成key,当前示例下生成的key为Usertest01。
@Slf4j
@Service
public class TestService {
@Getter
@Setter
public static class User {
private String id = "test01";
private String name = "测试用户";
}
public void test(User user) throws InterruptedException {
log.info(user.getName());
Thread.sleep(1000);//模拟业务过程
}
@RedisLock
public void testRedisSync(
@SyncArg("#arg.getClass().getSimpleName()+#arg.getId()") User user
) throws InterruptedException {
test(user);
}
@ZkLock
public void testZooKeeperSync(
@SyncArg("#arg.getClass().getSimpleName()+#arg.getId()") User user
) throws InterruptedException {
test(user);
}
}
不加锁
@Service
public class TestRunner implements CommandLineRunner {
@Resource
private TestService testService;
@Override
public void run(String... args) throws Exception {
TestService.User user = new TestService.User();
for (int i = 0; i < 1000; i++) {
testService.test(user);
}
}
}
服务一:
服务二:
根据时间可以看到两个服务几乎同时在进行,如果存在变更数据的情况则可能存在出现数据不一致情况的风险。
使用Redis锁
@Service
public class TestRunner implements CommandLineRunner {
@Resource
private TestService testService;
@Override
public void run(String... args) throws Exception {
TestService.User user = new TestService.User();
for (int i = 0; i < 1000; i++) {
testService.testRedisSync(user);
}
}
}
服务一:
服务二:
根据时间可以看到两个服务在逐一有序地进行,能够实现锁的效果。
使用ZooKeeper锁
@Service
public class TestRunner implements CommandLineRunner {
@Resource
private TestService testService;
@Override
public void run(String... args) throws Exception {
TestService.User user = new TestService.User();
for (int i = 0; i < 1000; i++) {
testService.testZooKeeperSync(user);
}
}
}
服务一:
服务二:
同样能够实现锁的效果。