一、背景知识
1、为什么要使用分布式锁
一句话:在分布式 或 集群环境中,控制对共享资源访问的,保证资源的一致性。
2、分布式锁使用场景
集群场景
进程内,多个线程对某一个共享变量进行访问的时候,可以使用Java各种线程同步机制,比如synchronized、Lock、闭锁、信号量、CopyOnWriteXXX家族集合类、Vector、ConcurrentXXX家族集合类、volatile(仅①内存可见性②防止重排序)等等。
注意这是单机应用,单机应用不需要分布式锁,使用传统的线程同步机制即可保证共享资源的一致性访问。
后来业务发展,需要做集群,一个应用需要部署到几台机器上然后做负载均衡,大致如下图:
上图可以看到,变量A存在JVM1、JVM2、JVM3三个JVM内存中(这个变量A主要体现是在一个类中的一个成员变量,是一个有状态的对象,例如:UserController控制器中的一个整形类型的成员变量),如果不加任何控制的话,变量A同时都会在JVM分配一块内存,三个请求发过来同时对这个变量操作,显然结果是不对的!即使不是同时发过来,三个请求分别操作三个不同JVM内存区域的数据,变量A之间不存在共享,也不具有可见性,处理的结果也是不对的!
分布式场景
为了保证一个方法或属性在高并发情况下的同一时间只能被同一个线程执行,在传统单体应用单机部署的情况下,可以使用Java并发处理相关的API(如ReentrantLock或Synchronized)进行互斥控制。在单机环境中,Java中提供了很多并发处理相关的API。但是,随着业务发展的需要,原单体单机部署的系统被演化成分布式集群系统后,由于分布式系统多线程、多进程并且分布在不同机器上,这将使原单机部署情况下的并发控制锁策略失效,单纯的Java API并不能提供分布式锁的能力。为了解决这个问题就需要一种跨JVM的互斥机制来控制共享资源的访问,这就是分布式锁要解决的问题!
二、分布式锁应该具备哪些条件
1、在分布式部署的应用集群中,同一个方法在同一时间只能被一台机器上的一个线程执行
2、高可用的获取锁与释放锁;
3、高性能的获取锁与释放锁;
4、具备可重入特性;(避免死锁)
5、具备锁失效机制,防止死锁;
6、具备可选择的请求锁时是否阻塞的特性(根据业务需求考虑要不要这条)
(1)具备非阻塞锁特性,即没有获取到锁将直接返回获取锁失败。
(2)具备 阻塞特特性,在锁已经被占用时,可以阻塞进行等待锁的释放,后占用上
7、这把锁最好是一把公平锁(根据业务需求考虑要不要这条)
非公平锁:所有等待锁的线程凭运气去争夺锁
三、分布式锁3种实现方式
1、实现思路
目前几乎很多大型网站及应用都是分布式部署的,分布式场景中的数据一致性问题一直是一个比较重要的话题。分布式的CAP理论告诉我们“任何一个分布式系统都无法同时满足:
①一致性(Consistency)
②可用性(Availability)
③分区容错性(Partition tolerance)
最多只能同时满足两项。”(而其中某一条又根据满足的不同程度可以进一步分:强/弱一致性,高可用性等等)所以,很多系统在设计之初就要对这三者做出取舍。
1)互联网领域应用:在互联网领域的绝大多数的场景中,都需要牺牲【强一致性】来换取系统的【高可用性】,系统往往只需要保证【最终一致性】,只要这个最终时间是在用户可以接受的范围内即可。在很多场景中,我们为了保证数据的最终一致性,需要很多的技术方案来支持,比如分布式事务、分布式锁等。
2、实现方式
1)基于数据库实现
方案1:基于数据库唯一性约束实现
方案2:基于数据库排他锁实现
2)基于缓存实现,比如redis
方案1:通过Redis原生API实现
方案2:通过Redisson实现(推荐)
3)基于zookeeper实现
3、zookeeper实现分布式锁与redis实现对比
技术层面:Redis 是nosql数据,而Zookeeper是分布式协调工具,主要用于分布式解决方案;
防死锁:Redis是通过对key设置有效期来解决死锁,而Zookeeper使用会话有效期方式解决死锁现象;
效率方面:Redis是NoSQL数据库,在效率上相对来说Redis就要比Zookeeper好很多;
可靠性:Redis有效期不是很好控制,可能会产生有效期延迟,同时redis分布式锁最大的问题是在redis master实例宕机的时候,发送的主备切换,可能导致多个客户端同时完成加锁,而Zookeeper的临时节点有先天性可控的有效期,所以相对来说Zookeeper比Redis可靠性高;
对比总结:Zookeeper分布式锁可靠性比redis强,但由于需要创建节点删除节点,所有效率相比Redis要低。那我在实际项目中我们如何选用呢?原则上如果并发量不是特别大,追求可靠性,那么首选zookeeper。而redis实现的分布式锁响应更快,对并发的支持性能更好,如果为了效率,首选redis实现。
详见我的其他博客:
zookeeper实现分布式锁:https://blog.csdn.net/mazhongjia/article/details/103923248
redis实现分布式锁(Redisson实现):https://blog.csdn.net/mazhongjia/article/details/108003368
数据库实现分布式锁:https://blog.csdn.net/mazhongjia/article/details/108003496