当前位置:网站首页>使用注解方式实现 Redis 分布式锁
使用注解方式实现 Redis 分布式锁
2022-07-19 20:31:00 【编程大椰子】
优雅的使用 Redis 分布式锁。
本文使用Redisson
中实现的分布式锁。
引入 Redisson
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson-spring-boot-starter</artifactId>
<version>3.14.1</version>
</dependency>
初始化 Redisson
@Configuration
public class RedissonConfiguration {
// 此处更换自己的 Redis 地址即可
@Value("${redis.addr}")
private String addr;
@Bean
public RedissonClient redisson() {
Config config = new Config();
config.useSingleServer()
.setAddress(String.format("%s%s", "redis://", addr))
.setConnectionPoolSize(64) // 连接池大小
.setConnectionMinimumIdleSize(8) // 保持最小连接数
.setConnectTimeout(1500) // 建立连接超时时间
.setTimeout(2000) // 执行命令的超时时间, 从命令发送成功时开始计时
.setRetryAttempts(2) // 命令执行失败重试次数
.setRetryInterval(1000); // 命令重试发送时间间隔
return Redisson.create(config);
}
}
这样我们就可以在项目里面使用 Redisson 了。
编写 Redisson 分布式锁工具类
Redis 分布式锁的工具类,主要是调用 Redisson 客户端实现,做了轻微的封装。
@Service
@Slf4j
public class LockManager {
/**
* 最小锁等待时间
*/
private static final int MIN_WAIT_TIME = 10;
@Resource
private RedissonClient redisson;
/**
* 加锁,加锁失败抛默认异常 - 操作频繁, 请稍后再试
*
* @param key 加锁唯一key
* @param expireTime 锁超时时间 毫秒
* @param waitTime 加锁最长等待时间 毫秒
* @return LockResult 加锁结果
*/
public LockResult lock(String key, long expireTime, long waitTime) {
return lock(key, expireTime, waitTime, () -> new BizException(ResponseEnum.COMMON_FREQUENT_OPERATION_ERROR));
}
/**
* 加锁,加锁失败抛异常 - 自定义异常
*
* @param key 加锁唯一key
* @param expireTime 锁超时时间 毫秒
* @param waitTime 加锁最长等待时间 毫秒
* @param exceptionSupplier 加锁失败时抛该异常,传null时加锁失败不抛异常
* @return LockResult 加锁结果
*/
private LockResult lock(String key, long expireTime, long waitTime, Supplier<BizException> exceptionSupplier) {
if (waitTime < MIN_WAIT_TIME) {
waitTime = MIN_WAIT_TIME;
}
LockResult result = new LockResult();
try {
RLock rLock = redisson.getLock(key);
try {
if (rLock.tryLock(waitTime, expireTime, TimeUnit.MILLISECONDS)) {
result.setLockResultStatus(LockResultStatus.SUCCESS);
result.setRLock(rLock);
} else {
result.setLockResultStatus(LockResultStatus.FAILURE);
}
} catch (InterruptedException e) {
log.error("Redis 获取分布式锁失败, key: {}, e: {}", key, e.getMessage());
result.setLockResultStatus(LockResultStatus.EXCEPTION);
rLock.unlock();
}
} catch (Exception e) {
log.error("Redis 获取分布式锁失败, key: {}, e: {}", key, e.getMessage());
result.setLockResultStatus(LockResultStatus.EXCEPTION);
}
if (exceptionSupplier != null && LockResultStatus.FAILURE.equals(result.getLockResultStatus())) {
log.warn("Redis 加锁失败, key: {}", key);
throw exceptionSupplier.get();
}
log.info("Redis 加锁结果:{}, key: {}", result.getLockResultStatus(), key);
return result;
}
/**
* 解锁
*/
public void unlock(RLock rLock) {
try {
rLock.unlock();
} catch (Exception e) {
log.warn("Redis 解锁失败", e);
}
}
}
加锁结果状态枚举类。
public enum LockResultStatus {
/**
* 通信正常,并且加锁成功
*/
SUCCESS,
/**
* 通信正常,但获取锁失败
*/
FAILURE,
/**
* 通信异常和内部异常,锁状态未知
*/
EXCEPTION;
}
加锁结果类封装了加锁状态和RLock。
@Setter
@Getter
public class LockResult {
private LockResultStatus lockResultStatus;
private RLock rLock;
}
自此我们就可以使用分布式锁了,使用方式:
@Service
@Slf4j
public class TestService {
@Resource
private LockManager lockManager;
public String test(String userId) {
// 锁:userId, 锁超时时间:5s, 锁等待时间:50ms
LockResult lockResult = lockManager.lock(userId, 5000, 50);
try {
// 业务代码
} finally {
lockManager.unlock(lockResult.getRLock());
}
return "";
}
}
为了防止程序发生异常,所以每次我们都需要在finally
代码块里手动释放锁。为了更方便优雅的使用 Redis 分布式锁,我们使用注解方式实现下。
声明注解 @Lock
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Lock {
/**
* lock key
*/
String value();
/**
* 锁超时时间,默认5000ms
*/
long expireTime() default 5000L;
/**
* 锁等待时间,默认50ms
*/
long waitTime() default 50L;
}
注解解析类
@Aspect
@Component
@Slf4j
public class LockAnnotationParser {
@Resource
private LockManager lockManager;
/**
* 定义切点
*/
@Pointcut(value = "@annotation(Lock)")
private void cutMethod() {
}
/**
* 切点逻辑具体实现
*/
@Around(value = "cutMethod() && @annotation(lock)")
public Object parser(ProceedingJoinPoint point, Lock lock) throws Throwable {
String value = lock.value();
if (isEl(value)) {
value = getByEl(value, point);
}
LockResult lockResult = lockManager.lock(getRealLockKey(value), lock.expireTime(), lock.waitTime());
try {
return point.proceed();
} finally {
lockManager.unlock(lockResult.getRLock());
}
}
/**
* 解析 SpEL 表达式并返回其值
*/
private String getByEl(String el, ProceedingJoinPoint point) {
Method method = ((MethodSignature) point.getSignature()).getMethod();
String[] paramNames = getParameterNames(method);
Object[] arguments = point.getArgs();
ExpressionParser parser = new SpelExpressionParser();
Expression expression = parser.parseExpression(el);
EvaluationContext context = new StandardEvaluationContext();
for (int i = 0; i < arguments.length; i++) {
context.setVariable(paramNames[i], arguments[i]);
}
return expression.getValue(context, String.class);
}
/**
* 获取方法参数名列表
*/
private String[] getParameterNames(Method method) {
LocalVariableTableParameterNameDiscoverer u = new LocalVariableTableParameterNameDiscoverer();
return u.getParameterNames(method);
}
private boolean isEl(String str) {
return str.contains("#");
}
/**
* 锁键值
*/
private String getRealLockKey(String value) {
return String.format("lock:%s", value);
}
}
下面使用注解方式使用分布式锁:
@Service
@Slf4j
public class TestService {
@Lock("'test_'+#user.userId")
public String test(User user) {
// 业务代码
return "";
}
}
当然也可以自定义锁的超时时间和等待时间
@Service
@Slf4j
public class TestService {
@Lock(value = "'test_'+#user.userId", expireTime = 3000, waitTime = 30)
public String test(User user) {
// 业务代码
return "";
}
}
优雅永不过时
边栏推荐
猜你喜欢
自定义Dialog(包含头尾)
2022年全国最新消防设施操作员(初级消防设施操作员)模拟题及答案
树的性质
Array of daily questions for Niuke
[today in history] July 19: the father of IMAP agreement was born; Project kotlin made a public appearance; New breakthroughs in CT imaging
Cannot make QOpenGLContext current in a different thread : PyQt多线程崩溃的解决方法
会话存储sessionStorage与本地存储localStorage叙述与案例分析
VMware solves the problem of not recognizing USB
Leetcode 69: climb stairs / jump steps
机器人时代发展大趋势对民众的影响
随机推荐
开启创客教育课程建设的实体空间
LVGL 8.2 Span
英语句式参考纯享版 - 宾语从句
FFmpeg 视频解码
动态内存管理
适合送礼的蓝牙耳机有哪些?2022蓝牙耳机排行榜10强
Mysql8.0 new feature - persistence of self increasing variables
記錄一下十三届藍橋杯嵌入式省賽題目
LeetCode 69:爬楼梯 / 跳台阶
[today in history] July 19: the father of IMAP agreement was born; Project kotlin made a public appearance; New breakthroughs in CT imaging
Win11 体验
Code source du système vidéo court, séquence de chargement des fichiers principaux dans le projet uni app
Array of daily questions for Niuke
About the list loop (five ways of writing foreach)
金仓数据库 KingbaseES SQL 语言参考手册 (3.3. 类型转换表、3.4. 常量)
Project knowledge points
目前有哪些工具可以连接PolarDB for PostgreSQL数据库并对其进行管理?
Source code of short video system, loading order of main files in uni app project
Quels sont les écouteurs Bluetooth pour cadeaux? Top 10 Bluetooth Headset 2022
The application could not be installed: INSTALL_FAILED_USER_RESTRICTED