分布式锁
分布式锁主流的实现方案:
- 基于数据库实现分布式锁
- 基于缓存( Redis等)
- 基于Zookeeper
每一种分布式锁解决方案都有各自的优缺点:
- 性能:Redis最高
- 可靠性:zookeeper最高
因为Redis具备高性能、高可用、高并发的特性,这里,我们就基于Redis实现分布式锁。
分布式锁的关键是多进程共享的内存标记(锁),因此只要我们在Redis中放置一个这样的标记(数据)就可以了。不过在实现过程中,不要忘了我们需要实现下列目标:
- 多进程可见:多进程可见,否则就无法实现分布式效果
- 避免死锁:死锁的情况有很多,我们要思考各种异常导致死锁的情况,保证锁可以被释放
- 排它:同一时刻,只能有一个进程获得锁
- 高可用:避免锁服务宕机或处理好宕机的补救措施(redis集群架构:1.主从复制 2.哨兵 3.cluster集群)
分布式锁使用的逻辑如下:
1 2 3 4 5 6 7 8 9 10 11
| 尝试获取锁 成功:执行业务代码 执行业务 try{ 获取锁 业务代码-宕机 } catch(){ }finally{ 释放锁 } 失败:等待,进行自旋;
|
分布式锁三个操作:
- 加锁
- 解锁
- 重试
为了确保分布式锁可用,我们至少要确保锁的实现同时满足以下几个条件:
- 互斥性。在任意时刻,只有一个客户端能持有锁。
- 不会发生死锁。即使有一个客户端在持有锁的期间崩溃而没有主动解锁,也能保证后续其他客户端能加锁。
- 解铃还须系铃人。加锁和解锁必须是同一个客户端,客户端自己不能把别人加的锁给解了。
- 加锁和解锁必须具有原子性。
- 实现可重入锁(可选)
- 为了防止锁失效,锁要具备自动续期
- 防止集群情况下锁失效,可以使用Redlock算法
关键理解
使用redis做分布式锁,主要三个变量
lockName: 锁的名称,用于标记一个具体的功能,不同的功能用不同的锁
uuid:标记上锁的对应进程,当一个进程上了锁之后,就只允许当前进程再次上锁(可重入),其他进程等待
expire:过期时间
续期:有时候锁的过期时间之内不能完成业务代码.就需要在过期时间过期1/3时,主动去续期,不能阻塞业务线程,用新线程
基于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;
@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;
@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; Object o = redisTemplate.opsForValue().get(dataKey); if(o!=null){ return o; } 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(); } }
|