# lock-redis
**Repository Path**: vigovin/lock-redis
## Basic Information
- **Project Name**: lock-redis
- **Description**: Redis 分布式锁工具
- **Primary Language**: Java
- **License**: Apache-2.0
- **Default Branch**: master
- **Homepage**: None
- **GVP Project**: No
## Statistics
- **Stars**: 2
- **Forks**: 0
- **Created**: 2020-07-31
- **Last Updated**: 2024-06-10
## Categories & Tags
**Categories**: Uncategorized
**Tags**: None
## README
## Redis 实现简单的分布式锁
### Redis 实现分布式锁需要考虑的问题
互斥性:在任意时刻,只有一个客户端能持有锁
加锁和解锁必须是同一个客户端,在方法退出时应该释放锁
不会发生死锁:即使有一个客户端在持有锁的期间崩溃而没有主动解锁,也能保证后续其他客户端能加锁(通过锁超时机制,注意**上锁的同时设置超时时间,必须保证原子性**)
具有容错性:只要大部分的Redis节点正常运行,客户端就可以加锁和解锁
多把锁的区分方式:一般通过 Key 进行区分
加锁失败是否可以自旋重试
### 使用方式
下载 jar 包,引入 Maven 依赖,在 application.yml 中配置开启分布式锁
在需要保证在分布式环境中只有一个进程执行的方法上加上注解`@Lock`即可,使用示例如下:
```java
@Lock(lockKey = "lock_db", expireTimeout = 30, expireTimeUnit = TimeUnit.SECONDS,
tryLockTimeout = 10, tryLockTimeUnit = TimeUnit.SECONDS)
public void operateDB() {
logger.info("Insert into DB...");
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
logger.info("Success~");
}
```
注解参数说明:
`lockKey`:锁标识,默认为`全类名+方法名`
`expireTimeout`:持有锁超时时间,默认为`1s`
`expireTimeUnit`:持有锁超时时间单位,默认为`秒`
`tryLockTimeout`:尝试获取锁超时时间(自旋时间),默认为`0`
`tryLockTimeUnit`:尝试获取锁超时时间单位,默认为`秒`
### 实现流程
定义分布式锁注解类,指定相关属性和默认值
定义锁服务的相关接口,需要上锁`lock`和解锁`unlock`相关方法
实现锁服务,上锁关键代码如下
```java
Boolean locked = redisTemplate.opsForValue().setIfAbsent(lockKey, requestId, expireTimeout, expireTimeUnit);
```
`setIfAbsent`保证上锁和设置超时时间是一个原子操作,如果上锁后再设置超时时间,如果在上锁后服务挂了,则这个锁永远不会被释放
解锁前需要校验`value`,保证上锁和解锁是同一个客户端操作的,但是校验和解锁的操作又必须是原子的,因为如果在校验后还没解锁的时间内,锁突然过期了,并且锁被其他的进程获取了,那么此时再解锁就是错误的。解锁可以使用`Lua`脚本保证原子性
```java
String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
DefaultRedisScript redisScript = new DefaultRedisScript<>(script, Long.class);
Long result = redisTemplate.execute(redisScript, Collections.singletonList(lockKey), requestId);
```
定义切面类,实现在方法进入前上锁、方法退出时解锁的逻辑
```java
@Pointcut("@annotation(com.vigovin.component.Lock)")
public void lockPointcut() {}
@Around("lockPointcut()")
public Object doAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
// 尝试上锁...
// 上锁失败则直接返回
try {
return proceedingJoinPoint.proceed();
} catch (Exception e) {
e.printStackTrace();
} finally {
// 校验解锁
}
}
```
## Reference
https://www.w3cschool.cn/redis/redis-yj3f2p0c.html