本文已参与「新人创作礼」活动,一起开启掘金创作之路。
分布式锁一般有三种实现方式:
- 基于数据库乐观锁
- 基于Redis的分布式锁
- 基于ZooKeeper的分布式锁
这里介绍基于Redis实现分布式锁的方法
首先讲一下为什么要使用分布式锁
- 为了保证一个方法在高并发情况下的同一时间只能被同一个线程执行,在单体应用单机部署,可以使用JVM锁的互斥性来保证同一时间同一个线程执行(例如:
ReentrantLcok、synchronized),但是分布式系统部署在多节点,不同的虚拟机,JVM的锁的互斥就失效了
分布式锁的原则
互斥性原则:任意时刻只能有一个客户端持有锁可重入原则:一个客户端获取到锁的同时这个客户端可以再次获取- 具有高
可用性、容错性:获取锁和释放锁的操作要保证高可用,并且速度要快 - 具有
专一性(安全性):谁加的的锁谁来解 锁失效机制:超时要自动释释放锁,不可出现死锁
先谈一下Redis分布式锁的实现原理
-
直接使用set(带有过期时间和IF NOT EXISTS )的命令,这里的给redis增加值和设置过期时间事原子操作
~ SET key value[EX seconds][PX milliseconds][NX|XX] ~ NX:(IF NOT EXISTS)表示key不存在的时候,才能set成功 ~ EX seconds: 设定key的过期时间,时间单位秒 ~ PX milliseconds: 设定key的过期时间,单位为毫秒 ~ XX: 仅当key存在时设置值
2.使用一个业务中唯一的id来确保是谁的锁作为set 的 value 值,当业务执行完,获取锁的value看是不是自己加的锁,若是才会删除释放锁
3.删除的时候判断是不是自己加的锁,释放锁两个操作不具有原子性,因为有可能释放锁的时候,这把锁已经不属于自己,会误删除别的线程的锁
-
为了确保获取值和删除事原子操作,必须使用LUA脚本
if redis.call('get',KEYS[1]) == ARGV[1] then return redis.call('del',KEYS[1]) else return 0 end;
4.防止业务执行还未完成时,到了过期时间锁被释放,可以增加守护线程,每隔多长时间检查一下客户端是否持有锁key,自动延长锁的key的过期时间
使用看门狗机制增加线程给锁续期,可以直接使用redision框架实现分布式锁
-
redission已经实现了可重入锁
-
加锁成功,就启动一个
watch dog看门狗后台线程,看门狗每隔10秒检查一次,如果当前的用户还持有锁,那么就会不断的延长锁key的生存时间 -
eg:假如加锁的时间是30秒,当加锁的业务没有执行完,到 30-10 = 20秒的时候进行一次续期,把锁重置成30秒
redisson使用RedLock
-
由于redis集群中,主从节点的切换会导致重复加锁,
-
例如:当某个线程给主节点加锁成功后,此时主节点宕机后,从节点升未主节点没有获取到主节点复制的信息,所以新的主节点没有这个锁,另外来的线程会对同一个锁进行加锁成功,导致两个线程持有相同的锁,违反了锁互斥性的原则
Redisson提供的 RedLock 方式解决了此问题,原理就是获取锁的时候需要同时使用多个独立的Redis实例分别进行加锁,当超过1/2的锁加锁成功,才能被定义为成功加锁
分布式锁的使用场景是什么
- 上一篇文章说的
防止缓存击穿 - 以及实现接口
幂等性