本文是Redis分布式锁实操,具体原理请参考:<未完善>
定义redisLock注解
在需要加锁的方法上使用@RedisLock注解,可以指定加锁的key和锁的过期时间.
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RedisLock {
//指定加锁的key
String key() default "";
//锁的过期时间 单位:秒
int timeout() default 60;
}
定义RedisPool
redis分布式锁实现的核心:使用lua命令保证setnx 和 过期时间这两个操作的原子性.
@Component
public class RedisPool {
private static JedisPool jedisPool;
@PostConstruct
private void init(){
//初始化jedis
JedisPoolConfig config = new JedisPoolConfig();
config.setMaxTotal(200);
config.setMaxIdle(8);
config.setMaxWaitMillis(100000L);
config.setTestOnBorrow(true);
this.jedisPool = new JedisPool(config,"host",port,"password");
}
/**
* 尝试获取分布式锁
* @param lockKey 锁
* @param value 请求标识
* @param expireTime 超期时间
* @return 是否获取成功
*/
public static boolean tryGetDistributedLock(String lockKey,String value, int expireTime) {
Jedis jedis = jedisPool.getResource();
//没有锁(key),加锁并设置有效期 有锁不做任何操作.
String script = "if redis.call('setNx',KEYS[1],ARGV[1]) == 1 then if redis.call('get',KEYS[1])==ARGV[1] then return redis.call('expire',KEYS[1],ARGV[2]) else return 0 end else return 0 end";
List<String> strings = new ArrayList<String>();
strings.add(value);
strings.add(expireTime+"");
Object result = jedis.eval(script, Collections.singletonList(lockKey), strings);
if (RELEASE_SUCCESS.equals(result)) {
return true;
}
return false;
}
private static final Long RELEASE_SUCCESS = 1L;
/**
* 释放分布式锁
* @param lockKey 锁
* @param requestId 请求标识
* @return 是否释放成功
*
* 问题一:不是我加的锁,但是我能获取到key就能释放 解决:添加业务id或者线程id
* 问题二:业务执行时间大于锁的过期时间 解决:redisson-watch dog ->待解决
* 核心原理:另起一个线程,查询对应key是否还存在,存在就延长过期时间(原来过期时间的一半)
*/
public static boolean releaseDistributedLock(String lockKey, String requestId) {
Jedis jedis = jedisPool.getResource();
String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
Object result = jedis.eval(script, Collections.singletonList(lockKey), Collections.singletonList(requestId));
if (RELEASE_SUCCESS.equals(result)) {
return true;
}
return false;
}
}
定义AOP切面
@Component
@Aspect
public class RedisLockAop {
@Pointcut("@annotation(redisLock)")
public void setRedisLock(RedisLock redisLock) {
}
@Before("setRedisLock(redisLock)")
public void doBefore(JoinPoint joinPoint, RedisLock redisLock) {
String value = "";
//获取方法名称
String methodName = joinPoint.getSignature().getName();
//获取类名称
String className = joinPoint.getSignature().getDeclaringTypeName();
//获取参数值
Object[] args = joinPoint.getArgs();
//获取加锁的key
String key = redisLock.key();
long threadId = Thread.currentThread().getId();
if (ObjectUtils.isEmpty(args)) {
//方法参数为空,不进行加锁操作
//key = methodName + UUID.randomUUID().toString();
return;
} else {
//对象属性
Map<String, Object> map = getKeyAndValue(args[0], key);
key = StringUtils.isEmpty(key) ? "id" : key;
Object o = map.get(key);
if (ObjectUtils.isEmpty(o)) {
System.out.println("被锁的值为空");
return;
} else {
key = o.toString();
}
value = key;
key = threadId + "--" + key;
System.out.println("将要被锁的键是:" + key + "====将要被锁的值是:" + value);
}
boolean result = RedisPool.tryGetDistributedLock(key, value, redisLock.timeout());
if (result)
System.out.println("key:" + key + ":加锁成功");
else
System.out.println("key:" + key + ":加锁失败");
}
@After("setRedisLock(redisLock)")
public void doAfter(JoinPoint joinPoint, RedisLock redisLock) {
String value = "";
//获取参数值
Object[] args = joinPoint.getArgs();
//获取加锁的key
String key = redisLock.key();
long threadId = Thread.currentThread().getId();
if (ObjectUtils.isEmpty(args)) {
//方法参数为空,不进行解锁操作
//key = methodName + UUID.randomUUID().toString();
return;
} else {
//对象属性
Map<String, Object> map = getKeyAndValue(args[0], key);
key = StringUtils.isEmpty(key) ? "id" : key;
Object o = map.get(key);
if (ObjectUtils.isEmpty(o)) {
System.out.println("将要解锁的值为空");
return;
} else {
key = o.toString();
}
value = key;
key = threadId + "--" + key;
System.out.println("将要解锁的键是:" + key + "======" + "将要解锁的值是:" + value);
}
boolean b = RedisPool.releaseDistributedLock( key, value);
if (b)
System.out.println("key:" + key + "解锁成功!");
else
System.out.println("key:" + key + "解锁失败!");
}
public static Map<String, Object> getKeyAndValue(Object obj, String key) {
Map<String, Object> map = new HashMap<String, Object>();
Class<?> aClass = obj.getClass();
//加锁入参是基本类型或者其包装类
if (aClass.isPrimitive() || isWrapClass(aClass)) {
map.put(StringUtils.isEmpty(key) ? "id" : key, obj);
} else {
//获取参数名
Field[] declaredFields = aClass.getDeclaredFields();
for (int i = 0; i < declaredFields.length; i++) {
Field f = declaredFields[i];
//反射的对象在使用时 取消java语言访问检查
f.setAccessible(true);
try {
Object val = f.get(obj);
map.put(f.getName(), val);
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
return map;
}
/**
* 判断是否是基础数据类型的包装类型
*
* @param clz
* @return
*/
public static boolean isWrapClass(Class clz) {
try {
return ((Class) clz.getField("TYPE").get(null)).isPrimitive();
} catch (Exception e) {
return false;
}
}
}
具体使用
在service的方法上加上@RedisLock注解,可以指定加锁的key和锁的过期时间.也可以使用默认的参数.
@Service
public class RedisLockService {
@RedisLock(key = "userId",timeout = 50)
public String testRedisLock(User user) {
return user.getName();
}
@RedisLock()
public String testRedisLock1(User user) {
return user.getName();
}
@RedisLock(timeout = 50)
public String testRedisLockAboutStr(Integer userId) {
return userId + "";
}
}
版权声明:本文为wangzhenpeng233原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。