欢迎转载,转载请注明网址:https://blog.csdn.net/qq_41910280
简介
本文基于spring data redis和redisson两种方式完成了redis分布式锁, 以及注解实现。
版本说明
2019-03-07 见https://blog.csdn.net/qq_41910280/article/details/88837576
2021-12-07 见简介
- 基于RedisTemplate和Redisson的redis分布式锁
- 1. 基于RedisTemplate的分布式锁
- 2. 基于Redisson的实现
- 3. 注解
- 参考文献
优点:加锁和解锁都基于lua脚本,保证操作的原子性,同时不依赖于特定redis客户端实现
缺点:1.不可重入;(修改也只需要修改脚本)
2.没有自动续期(可以使用ScheduledExecutorService,不要使用Timer,会有系统时钟依赖)
import com.alibaba.fastjson.support.spring.FastJsonRedisSerializer;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisscript;
import org.springframework.data.redis.core.script.Redisscript;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import org.springframework.stereotype.Component;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.Collections;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
@Component
public class RedisLockTool {
private static final Logger logger = LoggerFactory.getLogger(RedisLockTool.class);
private final RedisSerializer redisSerializerString = StringRedisSerializer.UTF_8;
private final RedisSerializer redisSerializerLong = new FastJsonRedisSerializer(Long.class);
private final Jackson2JsonRedisSerializer
2. 基于Redisson的实现
优点:支持重入,watchdog自动续期
缺点:1.一个线程加的锁只能同一线程解锁,对线程间协作支持不好
2.需要redisson支持
3.对于多redis实例的情况,当刚获取锁后master宕机,而数据尚未同步至slave,其他客户端可以从该slave点(晋级为master)获得锁。想解决这种问题,需要使用RedLock算法(详见参考文档章节),获得至少N/2+1个Redis实例的锁才算加锁成功,否则立即释放锁,并在一个随机延时之后重试(避免活锁)
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.concurrent.TimeUnit;
@Component
public class RedissonLockTool {
private static final Logger logger = LoggerFactory.getLogger(RedissonLockTool.class);
@Autowired
RedissonClient redisson;
private final TimeUnit unit = TimeUnit.MILLISECONDS;
public void lock(String lockName) {
RLock rLock = redisson.getLock(lockName);
rLock.lock();
}
public void lock(String lockName, long leaseTime) {
RLock rLock = redisson.getLock(lockName);
rLock.lock(leaseTime, unit);
}
public boolean tryLock(String lockName, long waitTime) {
RLock rLock = redisson.getLock(lockName);
boolean getLock = false;
try {
getLock = rLock.tryLock(waitTime, unit);
} catch (InterruptedException e) {
logger.error("tryLock fail,lockName=" + lockName, e);
}
return getLock;
}
public boolean tryLock(String lockName, long leaseTime, long waitTime) {
RLock rLock = redisson.getLock(lockName);
boolean getLock = false;
try {
getLock = rLock.tryLock(waitTime, leaseTime, unit);
} catch (InterruptedException e) {
logger.error("tryLock fail,lockName=" + lockName, e);
}
return getLock;
}
public boolean unlock(String lockName) {
try {
redisson.getLock(lockName).unlock();
} catch (Exception e) {
logger.error("unlock fail", e);
return false;
}
return true;
}
public boolean isLocked(String lockName) {
RLock rLock = redisson.getLock(lockName);
return rLock.isLocked();
}
public boolean holdsLock(String lockName) {
RLock rLock = redisson.getLock(lockName);
return rLock.isHeldByCurrentThread();
}
}
3. 注解
优点:开发简单
缺点:粒度较大,不够灵活,内部调用无效
1.注解类
import java.lang.annotation.*;
@documented
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface DistributedLock {
String value() default "redisson";
int leaseTime() default 10000;
}
2.处理注解的类
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.test.utils.RedissonLockTool;
@Aspect
@Component
public class DistributedLockHandler {
private static final Logger logger = LoggerFactory.getLogger(DistributedLockHandler.class);
@Autowired
RedissonLockTool lockTool;
@Around("@annotation(distributedLock)")
public void around(ProceedingJoinPoint joinPoint, DistributedLock distributedLock) {
logger.info("[开始]@DistributedLock");
//获取锁名称
String lockName = distributedLock.value();
//获取超时时间,默认10秒
int leaseTime = distributedLock.leaseTime();
try {
lockTool.lock(lockName, leaseTime);
Thread thread = Thread.currentThread();
logger.info("lock:{} by thread{}:{}", lockName, thread.getId(), thread.getName());
joinPoint.proceed();
} catch (Throwable throwable) {
logger.error("lock fail", throwable);
// todo 发送报警信息
} finally {
// 解锁
if (lockTool.holdsLock(lockName)) {
lockTool.unlock(lockName);
}
}
logger.info("[结束]@DistributedLock");
}
}
测试代码:
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.test.annotation.DistributedLock;
import org.test.utils.RedisLockTool;
import org.test.utils.RedissonLockTool;
import static java.util.concurrent.TimeUnit.SECONDS;
@Api(value = "测试", tags = "测试")
@RestController
@RequestMapping("cloud/test/")
public class TestController {
public static final Logger logger = LoggerFactory.getLogger(TestController.class);
@Autowired
RedissonLockTool redissonLockTool;
@Autowired
RedisLockTool redisLockTool;
@ApiOperation("锁")
@GetMapping("lock")
public Object lock() {
redisLockTool.lock("name", "1234", 30000L);
System.out.println(Thread.currentThread().getName() + ": lock success");
return "ok";
}
@ApiOperation("解锁")
@GetMapping("unlock")
public Object unlock() {
boolean unlock = redisLockTool.unlock("name", "1234");
System.out.println(unlock);
System.out.println(Thread.currentThread().getName() + ": unlock success");
return "ok";
}
@ApiOperation("redissonLock")
@GetMapping("redissonLock")
public Object redissonLock() {
redissonLockTool.lock("name");
System.out.println(Thread.currentThread().getName() + ": lock success");
return "ok";
}
@ApiOperation("redissonUnlock")
@GetMapping("redissonUnlock")
public Object redissonUnlock() {
redissonLockTool.unlock("name");
System.out.println(Thread.currentThread().getName() + ": unlock success");
return "ok";
}
@ApiOperation("annotationLock")
@GetMapping("annotationLock")
@DistributedLock(value = "annotationLock", leaseTime = 20000)
public Object annotationLock() {
System.out.println("do something...");
try {
SECONDS.sleep(15);
} catch (InterruptedException e) {
}
return "ok";
}
}
参考文献
1.https://codeload.github.com/yudiandemingzi/spring-boot-distributed-redisson
2.redis官方文档
RedLock算法
我们考虑这样一种场景,假设我们的redis没用使用备份。一个客户端获取到了3个实例的锁。此时,其中一个已经被客户端取到锁的redis实例被重启,在这个时间点,就可能出现3个节点没有设置锁,此时如果有另外一个客户端来设置锁,锁就可能被再次获取到,这样锁的互相排斥的特性就被破坏掉了。
如果我们启用了AOF持久化,情况会好很多。我们可用使用SHUTDOWN命令关闭然后再次重启。因为Redis到期是语义上实现的,所以当服务器关闭时,实际上还是经过了时间,所有(保持锁)需要的条件都没有受到影响. 没有受到影响的前提是redis优雅的关闭。停电了怎么办?如果redis是每秒执行一次fsync,那么很有可能在redis重启之后,key已经丢弃。理论上,如果我们想在Redis重启地任何情况下都保证锁的安全,我们必须开启fsync=always的配置。这反过来将完全破坏与传统上用于以安全的方式实现分布式锁的同一级别的CP系统的性能.
然而情况总比一开始想象的好一些。当一个redis节点重启后,只要它不参与到任意当前活动的锁,没有被当做“当前存活”节点被客户端重新获取到,算法的安全性仍然是有保障的。
为了达到这种效果,我们只需要将新的redis实例,在一个TTL时间内,对客户端不可用即可,在这个时间内,所有客户端锁将被失效或者自动释放.
使用延迟重启可以在不采用持久化策略的情况下达到同样的安全,然而这样做有时会让系统转化为彻底不可用。比如大部分的redis实例都崩溃了,系统在TTL时间内任何锁都将无法加锁成功。
3.redisson官方文档
本人邮箱:zhouyouchn@126.com zhoooooouyou@gmail.com
zhouyou@whut.edu.cn 欢迎交流,共同进步。
欢迎转载,转载请注明本网址。



