redis实现分布式锁简单又实用,这里是一个开箱即用redis分布式锁
pom文件添加除springBoot相关依赖以外的以下依赖包
<!-- https://mvnrepository.com/artifact/cn.hutool/hutool-all -->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>4.6.1</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.58</version>
</dependency>
<!-- https://mvnrepository.com/artifact/io.netty/netty-all -->
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.1.39.Final</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-data-redis -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
<version>2.1.6.RELEASE</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework.data/spring-data-redis -->
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-redis</artifactId>
<version>2.1.3.RELEASE</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-aop -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
<version>2.0.3.RELEASE</version>
</dependency>
/**
* redis 锁 key
*
* Create Time : 2019/7/24 9:08
*
* @author cmw
*/
@Target({ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface LockKey {
}
/**
* redis 锁
*
* Create Time : 2019/7/23 10:02
*
* @author cmw
*/
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RedisLock {
@AliasFor("lockKey") String value() default "";
@AliasFor("value") String lockKey() default "";
/**
* 超时时间 默认 30秒超时
*
* @return
*/
long expireTime() default 30;
/**
* 读取超时时间 默认 不复读
* @return
*/
long readTimeout() default -1;
}
/**
* redis 锁 切面
*
* Create Time : 2019/7/23 10:13
*
* @author cmw
*/
@Component
@Aspect
@Scope
@Order(1)
public class RedisLockAspect {
private final Logger log = LoggerFactory.getLogger(getClass());
@Autowired
private RedisLockTool redisLockTool;
// @Around("lockAspect(redisLock)")
@Around("@annotation(redisLock)")
public Object around(ProceedingJoinPoint joinPoint, RedisLock redisLock) throws Throwable {
System.out.println("进入切面~~~~~~~~~~~~~~~~~~~~~~~~~~");
redisLock = AnnotationUtils.getAnnotation(redisLock, RedisLock.class);
boolean res = false;
String lockKey = redisLock.lockKey();
String requestId = UUID.randomUUID().toString();
HashedWheelTimer hashedWheelTimer = new HashedWheelTimer(Executors.defaultThreadFactory());
Object obj = null;
try {
lockKey += getMethodParameterLockKey(joinPoint);
if (!StringUtils.hasText(lockKey)) {
lockKey = joinPoint.getSignature().getDeclaringTypeName();
}
if (redisLock.readTimeout() > 0) {
res = redisLockTool.tryGetDistributedLock(lockKey, requestId, Duration.ofSeconds(redisLock.expireTime()), Duration.ofSeconds(redisLock.readTimeout()));
} else {
res = redisLockTool.tryGetDistributedLock(lockKey, requestId, Duration.ofSeconds(redisLock.expireTime()));
}
if (res) {
// 每隔 redisLock.expireTime() / 3 秒延期一次,这里一般可以省略,让key自己过期就好了
long delay = redisLock.expireTime() / 3;
String finalLockKey = lockKey;
hashedWheelTimer.newTimeout(timeout -> {
redisLockTool.lockWatchdog(finalLockKey);
hashedWheelTimer.newTimeout(timeout.task(), delay, TimeUnit.SECONDS);
}, delay, TimeUnit.SECONDS);
obj = joinPoint.proceed();
} else {
log.error("redis lock -> get lock fail!");
//这里建议返回给客户的约定的信息
}
} catch (Throwable e) {
log.error("redis lock -> ", e);
throw e;
} finally {
if (res) {
//释放锁 停止 延期行为
hashedWheelTimer.stop();
redisLockTool.releaseDistributedLock(lockKey, requestId);
}
}
return obj;
}
private String getMethodParameterLockKey(ProceedingJoinPoint joinPoint) {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
Annotation[][] parameterAnnotations = method.getParameterAnnotations();
Object[] objects = joinPoint.getArgs();
StringBuffer lockKey = new StringBuffer();
// 带特定注解的参数下标集合
for (int i = 0; i < parameterAnnotations.length; i++) {
for (Annotation an : parameterAnnotations[i]) {
if (an instanceof LockKey) {
Assert.notNull(objects[i], "redis lock -> LockKey is null");
lockKey.append(objects[i].toString());
}
}
}
return lockKey.toString();
}
}
@Component
public class RedisLockTool {
private final Logger log = LoggerFactory.getLogger(getClass());
public static final String PRDFIX = "lock:";
private final StringRedisTemplate stringRedisTemplate;
public static final String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
public RedisLockTool(StringRedisTemplate stringRedisTemplate) {
this.stringRedisTemplate = stringRedisTemplate;
}
/**
* 尝试获取分布式锁
*
* @param lockKey 锁
* @param requestId 请求标识
* @param expireTime 超期时间
* @return 是否获取成功
*/
public boolean tryGetDistributedLock(String lockKey, String requestId, Duration expireTime) {
log.info("redis lock -> do tryGetDistributedLock lockKey:{},requestId:{}", lockKey, requestId);
//(PRDFIX + lockKey, requestId, expireTime);
Expiration expiration = Expiration.from(expireTime.toMillis(), TimeUnit.MILLISECONDS);
StringRedisSerializer serializer = new StringRedisSerializer();
return stringRedisTemplate.execute(connection -> connection.set(serializer.serialize(PRDFIX + lockKey),
serializer.serialize(requestId),
expiration,
RedisStringCommands.SetOption.ifAbsent()), true);
}
public boolean tryGetDistributedLock(String lockKey, String requestId, Duration expireTime, Duration readTimeout) {
log.info("redis lock -> do tryGetDistributedLock lockKey:{},requestId:{}", lockKey, requestId);
//(PRDFIX + lockKey, requestId, expireTime);
Expiration expiration = Expiration.from(expireTime.toMillis(), TimeUnit.MILLISECONDS);
StringRedisSerializer serializer = new StringRedisSerializer();
long start = System.currentTimeMillis();
long timeout = readTimeout.toMillis();
boolean result;
while (isTimeout(start, timeout)) {
result = stringRedisTemplate.execute(connection -> connection.set(serializer.serialize(PRDFIX + lockKey),
serializer.serialize(requestId),
expiration,
RedisStringCommands.SetOption.ifAbsent()), true);
if (result) {
return true;
}
}
return false;
}
private boolean isTimeout(long start, long timeout) {
return start + timeout > System.currentTimeMillis();
}
/**
* 释放分布式锁
*
* @param lockKey 锁
* @param requestId 请求标识
* @return 是否释放成功
*/
public boolean releaseDistributedLock(String lockKey, String requestId) {
log.info("redis lock -> do releaseDistributedLock lockKey:{},requestId:{}", lockKey, requestId);
DefaultRedisScript<Boolean> redisScript = new DefaultRedisScript(script, Boolean.class);
Boolean bool = stringRedisTemplate.execute(redisScript,
stringRedisTemplate.getKeySerializer(),
new FastJsonRedisSerializer<>(Boolean.class),
Collections.singletonList(PRDFIX + lockKey),
requestId);
if (bool == null) {
return false;
} else {
return bool;
}
}
/**
* 延长锁 存活时间
*
* @param lockKey
*/
public void lockWatchdog(String lockKey) {
log.info("redis lock -> do lockWatchdog lockKey:{}", lockKey);
stringRedisTemplate.expire(lockKey, 30, TimeUnit.SECONDS);
}
}
测试
@Test
public void test03() throws InterruptedException {
SynchronousQueue queue = new SynchronousQueue<Runnable>();
ThreadPoolExecutor executor = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60, TimeUnit.SECONDS, queue);
//ExecutorService executor = Executors.newFixedThreadPool(5);
CountDownLatch countDownLatch = new CountDownLatch(5);
for (int j = 0; j < 5; j++) {
executor.execute(new Runnable() {
@Override
public void run() {
weiXinService.t1();
countDownLatch.countDown();
}
});
}
countDownLatch.await();
executor.shutdown();
}
@RedisLock(expireTime = 2,readTimeout = 9)
@Override
public void t1() {
String name = java.lang.Thread.currentThread().getName();
System.out.println(name +"~~开始~~"+System.currentTimeMillis());
try {
java.lang.Thread.sleep(1000);
} catch (Exception e) {
System.out.println("wwtefysho;");
e.printStackTrace();
}
System.out.println(name +"~~结束~~"+System.currentTimeMillis());
}