Redis 分布式锁实现
AI-摘要
WenXi GPT
AI初始化中...
介绍自己
生成本文简介
推荐相关文章
前往主页
前往tianli博客
RedisLock, reids分布式锁工具类
package com.emdata.lowvis.common.redislock;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import java.util.concurrent.TimeUnit;
/**
* reids分布式锁工具类
*
* @version 1.0
* @date 2020/12/8 14:37
*/
@Slf4j
@Component
public class RedisLock {
private static final String SPLIT = "_";
@Autowired
private StringRedisTemplate stringRedisTemplate;
/**
* 加锁解锁工具类
* @param lockKey 加锁的key
* @param uuid 线程的标志
* @param timeout 超时时间
* @param timeUnit 超时时间粒度
* @return true:获取成功
*/
public boolean lock(String lockKey, String uuid, long timeout, TimeUnit timeUnit) {
// 根据key获取值
String currentLock = stringRedisTemplate.opsForValue().get(lockKey);
// 值为:uuid_时间
String value = uuid + SPLIT + (timeUnit.toMillis(timeout) + System.currentTimeMillis());
// 如果为空,则设置值
if (StringUtils.isEmpty(currentLock)) {
if (stringRedisTemplate.opsForValue().setIfAbsent(lockKey, value, timeout, timeUnit)) {
// 对应setnx命令,可以成功设置,也就是key不存在,获得锁成功
return true;
} else {
return false;
}
} else {
// 可重入锁,如果是这个uuid持有的锁,则更新时间
if (currentLock.startsWith(uuid)) {
stringRedisTemplate.opsForValue().set(lockKey, value, timeout, timeUnit);
return true;
} else {
return false;
}
}
}
/*
* 保存lua脚本
*/
private DefaultRedisScript<List> getRedisScript;
@PostConstruct
public void init(){
// 定义lua脚本资源
// 也可以放到文件中,加载进来: new ResourceScriptSource(new ClassPathResource("redis/demo.lua"))
String luaStr = "if redis.call('get',KEYS[1]) == ARGV[1] then return redis.call('del',KEYS[1]) else return 0 end";
ByteArrayResource resource = new ByteArrayResource(luaStr.getBytes());
getRedisScript = new DefaultRedisScript<>();
getRedisScript.setResultType(List.class);
getRedisScript.setScriptSource(new ResourceScriptSource(resource));
}
/**
* 释放锁
*
* @param lockKey 加锁的key
* @param uuid 线程的标志
*/
public void release(String lockKey, String uuid) {
try {
List<Integer> execute = stringRedisTemplate.execute(getRedisScript, Collections.singletonList(lockKey), uuid);
log.debug("解锁结果: {}", execute.get(0) == 0);
} catch (Exception e) {
log.error("解锁异常, key: {}, uuid: {}", lockKey, uuid);
log.error("", e);
}
}
}
使用
@Autowired
private RedisLock redisLock;
public void useLock() {
// 定义锁的key
String lockKey = "camera_update_key";
String uuid = UUIDUtils.get();
// 定义超时时间
long timeout = 5;
TimeUnit timeUnit = TimeUnit.SECONDS;
// 加锁
boolean lock = redisLock.lock(lockKey, uuid, timeout, timeUnit);
try {
if (lock) {
log.info("执行...");
} else {
throw new IllegalStateException("未获取到锁,放弃执行");
}
} finally {
// 在finally里面进行解锁
redisLock.release(lockKey, uuid);
}
}
EmLock,分布式锁注解
package com.emdata.lowvis.common.redislock;
import java.lang.annotation.*;
import java.util.concurrent.TimeUnit;
/**
* 分布式锁注解
*
* @version 1.0
* @date 2020/12/8 17:59
*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface EmLock {
/**
* 锁的范围,默认应用级别
* @return 锁的范围
*/
LockRangeEnum lockRange() default LockRangeEnum.APPLICATION;
/**
* 锁对应的key
* @return key
*/
String key();
/**
* 锁超时时间
* @return 时间
*/
int timeout() default 5;
/**
* 锁超时时间粒度
* @return 粒度
*/
TimeUnit timeUnit() default TimeUnit.SECONDS;
/**
* 是否自动释放锁
* @return true: 方法完成后,自动释放
*/
boolean autoRelease() default true;
}
使用
@Component
@Slf4j
public class ScheduleTask {
/**
* 用在定时任务方法上,锁的key为test_lock,指定了超时时间为2秒钟
* 锁的级别为默认的应用级别(LockRangeEnum.APPLICATION),在这个如果应用启动了多个容器运行,在只会有一个容器获取到锁,
* 自动释放锁为false,即方法执行完成后,也不会自动释放锁,只有到超时时间了,锁才会释放
*/
@Scheduled(cron = "0 0/1 * * * ? ")
@EmLock(key = "test_lock", timeout = 2, timeUnit = TimeUnit.SECONDS, autoRelease = false)
public void recordUpdateTask() {
log.info("执行任务.......");
}
/**
* 用在普通的方法上,锁的key为method_Lock,指定了超时时间为1分钟,
* 锁的级别为默认的线程级别,在该应用内多个线程执行该方法,则只会有一个线程获取到锁
* 如果启动了多个应用容器,同样多个容器内的所有线程,也只会有一个线程获取到锁
*/
@EmLock(key = "method_Lock", timeout = 1, timeUnit = TimeUnit.MINUTES, lockRange = LockRangeEnum.THREAD)
public void recordUpdate() {
log.info("执行任务2.......");
}
}
LockRangeEnum, 分布式锁的范围枚举
package com.emdata.lowvis.common.redislock;
/**
* 分布式锁的范围枚举
*
* @author pupengfei
* @version 1.0
* @date 2020/12/10 13:46
*/
public enum LockRangeEnum {
/**
* 应用级别,锁的级别在整个应用容器内
*/
APPLICATION,
/**
* 线程级别,锁的级别在每个线程
*/
THREAD
}
EmLockAspect,分布式锁切面
package com.emdata.lowvis.common.redislock;
import com.emdata.lowvis.common.utils.UUIDUtils;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
import java.util.concurrent.TimeUnit;
/**
* 分布式锁切面
*
* @version 1.0
* @date 2020/12/8 17:59
*/
@Slf4j
@Component
@Aspect
@Configuration
public class EmLockAspect {
@Autowired
private RedisLock redisLock;
/**
* 应用级别的容器的id
*/
private final String appUUID = UUIDUtils.get();
/**
* 线程级别的线程的id
*/
private final ThreadLocal<String> threadUUID = ThreadLocal.withInitial(UUIDUtils::get);
/**
* 定义切点
*/
@Pointcut("@annotation(com.emdata.lowvis.common.redislock.EmLock)")
public void lockAop() {
}
@Around("lockAop()")
public Object doAround(ProceedingJoinPoint point) throws Throwable {
// 获取方法
MethodSignature signature = (MethodSignature) point.getSignature();
Method method = signature.getMethod();
// 看有没有日志注解
EmLock emLock = method.getAnnotation(EmLock.class);
if (emLock == null) {
return point.proceed();
}
// 获取锁的级别
LockRangeEnum lockRangeEnum = emLock.lockRange();
String uuid = lockRangeEnum == LockRangeEnum.APPLICATION ? appUUID : threadUUID.get();
// 获取锁的key和超时时间
String key = emLock.key();
int timeout = emLock.timeout();
TimeUnit timeUnit = emLock.timeUnit();
// 加锁
boolean lock = redisLock.lock(key, uuid, timeout, timeUnit);
Object proceed = null;
try {
if (lock) {
log.info("获取到锁,继续执行...");
// 继续执行
proceed = point.proceed();
}
} finally {
// 自动释放,则释放锁
if (emLock.autoRelease()) {
redisLock.release(key, uuid);
}
}
return proceed;
}
}
注意
使用Redis作为分布式锁的实现,依赖于Redis服务,如果Redis服务无法正常访问,则会导致整个方法无法执行。 如果EmLock注解用在定时任务上时,如果应用运行在不同的服务器上,或者不同的docker容器里面时,必须保证运行环境的时间一致。 如果设置了定时任务上面的锁,不是自动释放的,则运行环境的时间,相差不大于锁超时时间的时候,也可以保证定时任务,唯一执行。因为在超时时间范围内,某个应用容器持有该锁,其他应用来获取锁时,同样获取不到,方法不会执行。
- 感谢你赐予我前进的力量
赞赏者名单
因为你们的支持让我意识到写文章的价值🙏
本文是原创文章,采用 CC BY-NC-ND 4.0 协议,完整转载请注明来自 平凡先生/文奚
评论
匿名评论
隐私政策
你无需删除空行,直接评论以获取最佳展示效果