分布式锁

分布式锁主流的实现方案:

  1. 基于数据库实现分布式锁
  2. 基于缓存( Redis等)
  3. 基于Zookeeper

每一种分布式锁解决方案都有各自的优缺点:

  1. 性能:Redis最高
  2. 可靠性:zookeeper最高

因为Redis具备高性能、高可用、高并发的特性,这里,我们就基于Redis实现分布式锁。

分布式锁的关键是多进程共享的内存标记(锁),因此只要我们在Redis中放置一个这样的标记(数据)就可以了。不过在实现过程中,不要忘了我们需要实现下列目标:

  • 多进程可见:多进程可见,否则就无法实现分布式效果
  • 避免死锁:死锁的情况有很多,我们要思考各种异常导致死锁的情况,保证锁可以被释放
  • 排它:同一时刻,只能有一个进程获得锁
  • 高可用:避免锁服务宕机或处理好宕机的补救措施(redis集群架构:1.主从复制 2.哨兵 3.cluster集群)

分布式锁使用的逻辑如下:

1
2
3
4
5
6
7
8
9
10
11
尝试获取锁
成功:执行业务代码
执行业务
try{
获取锁
业务代码-宕机
} catch(){
}finally{
释放锁
}
失败:等待,进行自旋;

分布式锁三个操作:

  1. 加锁
  2. 解锁
  3. 重试

为了确保分布式锁可用,我们至少要确保锁的实现同时满足以下几个条件:

  • 互斥性。在任意时刻,只有一个客户端能持有锁。
  • 不会发生死锁。即使有一个客户端在持有锁的期间崩溃而没有主动解锁,也能保证后续其他客户端能加锁。
  • 解铃还须系铃人。加锁和解锁必须是同一个客户端,客户端自己不能把别人加的锁给解了。
  • 加锁和解锁必须具有原子性。
  • 实现可重入锁(可选)
  • 为了防止锁失效,锁要具备自动续期
  • 防止集群情况下锁失效,可以使用Redlock算法

关键理解

使用redis做分布式锁,主要三个变量
lockName: 锁的名称,用于标记一个具体的功能,不同的功能用不同的锁
uuid:标记上锁的对应进程,当一个进程上了锁之后,就只允许当前进程再次上锁(可重入),其他进程等待
expire:过期时间

续期:有时候锁的过期时间之内不能完成业务代码.就需要在过期时间过期1/3时,主动去续期,不能阻塞业务线程,用新线程

  • 原子性:lua脚本

基于redsi的分布式锁实现(aop)

结构

annotatioon

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
* @author orange
* @since 2024/10/6
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RedisCaChe {
String prefix() default "";
}

aop

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
import com.spzx.common.redis.annotation.RedisCaChe;
import lombok.SneakyThrows;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;

import java.util.Arrays;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
* @author orange
* @since 2024/10/6
*/
@Aspect
public class RedisCaCheAop {

@Autowired
RedisTemplate redisTemplate;
String data=":data:";
String lock=":lock:";


@SneakyThrows
@Around("@annotation(redisCaChe)")
Object around(ProceedingJoinPoint point, RedisCaChe redisCaChe) {
String prefix = redisCaChe.prefix();
Object[] args = point.getArgs();
String suffix = null;
if (args != null && args.length > 0) {
suffix = Arrays.asList(args).stream().map(arg -> arg.toString()).collect(Collectors.joining(":"));
}

String dataKey=prefix + data + suffix;
// 去redis查找
Object o = redisTemplate.opsForValue().get(dataKey);
if(o!=null){
return o;
}
//redis不存在
String lockName=prefix+lock+suffix;
String uuid = UUID.randomUUID().toString();
Boolean flag = redisTemplate.opsForValue().setIfAbsent(lockName, uuid, 5, TimeUnit.SECONDS);
if(flag){
try {
o = redisTemplate.opsForValue().get(dataKey);
if(o!=null){
return o;
}
Object proceed = point.proceed();
redisTemplate.opsForValue().set(dataKey,proceed,10,TimeUnit.MINUTES);
return proceed;
} finally {
redisTemplate.delete(lockName);
}
}
return point.proceed();
}
}