一文带你搞懂分布式锁!

为什么要使用分布式锁

为了保证一个方法在高并发情况下的同一时间只能被同一个线程执行,在传统单体应用单机部署的情况下,可以使用Java并发处理相关的API(如ReentrantLcok或synchronized)进行互斥控制。

但是,随着业务发展的需要,原单体单机部署的系统被演化成分布式系统后,由于分布式系统多线程、多进程并且分布在不同机器上,这将使原单机部署情况下的并发控制锁策略失效,

为了解决这个问题就需要一种跨JVM的互斥机制来控制共享资源的访问,这就是分布式锁要解决的问题。

分布式锁特征

一文带你搞懂分布式锁!_第1张图片

补充:具备非阻塞锁特性,即没有获取到锁将直接返回获取锁失败。

实现方式

  1. 基于数据库实现【不推荐】
  2. 基于 Redis 实现 【不推荐】
  3. 基于 REDLOCK 做分布式锁【不推荐】
  4. 基于 REDISSON 做分布式锁【推荐】
  5. 基于 Consul 做分布式锁【没用过 不了解 不评论】
  6. 基于Zookeeper实现分布式锁;【推荐】

------ 实际需求从REDISSON(性能好)、Zookeeper(不会丢失锁) 二选一

redis

单机加锁

单机操作是没有问题的,集群不适用。

一文带你搞懂分布式锁!_第2张图片

SETNX + EXPIRE

一文带你搞懂分布式锁!_第3张图片

加了try catch finally之后能够保证代码无论是否执行成功都能删除key,但不能避免服务器宕机或服务器重启。

于是我们加一个过期时间:

一文带你搞懂分布式锁!_第4张图片

但setnx和expire两个命令分开了,不是原子操作  正要执行expire设置过期时间时,进程crash或者要重启维护了,那么这个锁就“长生不老”了,别的线程永远获取不到锁啦

实际上,我们还可以使用Lua脚本来保证原子性(包含setnx和expire两条指令)。我们介绍set的扩展命令:

SET的扩展命令(SET EX PX NX)

redis提供了set的扩展命令能够一条命令执行setnx和expire:

一文带你搞懂分布式锁!_第5张图片

依然存在问题:比如一种情况,锁都过期了,业务代码还没有执行完,最后去删key,会删除别的线程锁。

那我们加一个UUID:

一文带你搞懂分布式锁!_第6张图片

这里依然存在原子性问题,get方法执行后,锁过期,在执行delete,依然会删除别的线程锁。

在这里我们一般也是用lua脚本代替这两条命令。至此,用redis实现分布式锁有许多问题的,想要解决都需要借助lua脚本。

其实,还有一种解决方法就是:锁续命(开启一个定时守护线程,每隔一段时间检查锁是否还存在,存在则对锁的过期时间延长,防止锁过期提前释放)。

redisson就是利用锁续命的机制实现的。

redisson

其实redisson底层也借助了lua脚本来执行。

一文带你搞懂分布式锁!_第7张图片

只要线程一加锁成功,就会启动一个watch dog看门狗,它是一个后台线程,会每隔10秒检查一下,如果线程1还持有锁,那么就会不断的延长锁key的生存时间。

一文带你搞懂分布式锁!_第8张图片

问题看似都解决了,其实不然,正式环境的redis一般都是集群环境,主从复制仍然会出现一些问题,导致主库挂掉,从库没有同步到key就会导致redisson的锁出现问题。

为了解决这个问题,Redis作者 antirez提出一种高级的分布式锁算法:Redlock。

搞多个Redis master部署,以保证它们不会同时宕掉。并且这些master节点是完全相互独立的,相互之间不存在数据同步。

同时,需要确保在这多个master实例上,是与在Redis单实例,使用相同方法来获取和释放锁。

redlock的加锁机制和zookeeper很像。

一文带你搞懂分布式锁!_第9张图片

我们是不推荐使用redlock的。

正式环境一般为了高可用,redis都是主从配置的,既然是主从,就会出现主库挂掉,从库没有同步到key的问题。

接下来我们推荐zookeeper:

zookeeper在数据的一致性上面做的更好,redisson在加锁时只要主库新增了key就会加锁,而zk会在主库新增key后同步到从库中(达到半数以上)才能加锁成功,

所以zk不会出现丢失锁的现象,但性能不如redisson。

一文带你搞懂分布式锁!_第10张图片

你可能感兴趣的:(分布式,后端)