分布式锁那点事

为什么要使用分布式锁

为了保证一个方法在高并发情况下的同一时间只能被同一个线程执行,在传统单体应用单机部署的情况下,可以使用Java并发处理相关的API(如ReentrantLcok或synchronized)进行互斥控制。但是,随着业务发展的需要,原单体单机部署的系统被演化成分布式系统后,由于分布式系统多线程、多进程并且分布在不同机器上,这将使原单机部署情况下的并发控制锁策略失效,为了解决这个问题就需要一种跨JVM的互斥机制来控制共享资源的访问,这就是分布式锁要解决的问题。

分布式锁的三种实现方式

在分析分布式锁的三种实现方式之前,先了解一下分布式锁应该具备哪些条件。

在分布式系统环境下,一个方法在同一时间只能被一个机器的一个线程执行;
高可用的获取锁与释放锁;
高性能的获取锁与释放锁;
具备可重入特性;
具备锁失效机制,防止死锁;
具备阻塞锁特性,即没有获取到锁将继续等待获取锁;
具备非阻塞锁特性,即没有获取到锁将直接返回获取锁失败。
基于数据库的实现方式

在数据库中创建一个表,表中包含方法名等字段,并在方法名字段上创建唯一索引,想要执行某个方法,就使用这个方法名向表中插入数据,成功插入则获取锁,执行完成后删除对应的行数据释放锁。

这种实现方式很简单,但是对于分布式锁应该具备的条件来说,它有一些问题需要解决及优化。

因为是基于数据库实现的,数据库的可用性和性能将直接影响分布式锁的可用性及性能,所以,数据库需要双机部署、数据同步、主备切换;
不具备可重入的特性,因为同一个线程在释放锁之前,行数据一直存在,无法再次成功插入数据,所以,需要在表中新增一列,用于记录当前获取到锁的机器和线程信息,在再次获取锁的时候,先查询表中机器和线程信息是否和当前机器和线程相同,若相同则直接获取锁;
没有锁失效机制,因为有可能出现成功插入数据后,服务器宕机了,对应的数据没有被删除,当服务恢复后一直获取不到锁,所以,需要在表中新增一列,用于记录失效时间,并且需要有定时任务清除这些失效的数据;
不具备阻塞锁特性,获取不到锁直接返回失败,所以需要优化获取逻辑,循环多次去获取。
优点:借助数据库,方案简单。

缺点:在实际实施的过程中会遇到各种不同的问题,为了解决这些问题,实现方式将会越来越复杂;依赖数据库需要一定的资源开销,性能问题需要考虑。

基于Redis的实现方式

在Redis2.6.12版本之前,使用setnx命令设置key-value、使用expire命令设置key的过期时间获取分布式锁,使用del命令释放分布式锁,但是这种实现有如下一些问题:

setnx命令设置完key-value后,还没来得及使用expire命令设置过期时间,当前线程挂掉了,会导致当前线程设置的key一直有效,后续线程无法正常通过setnx获取锁,造成死锁;
在分布式环境下,线程A通过这种实现方式获取到了锁,但是在获取到锁之后,执行被阻塞了,导致该锁失效,此时线程B获取到该锁,之后线程A恢复执行,执行完成后释放该锁,直接使用del命令,将会把线程B的锁也释放掉,而此时线程B还没执行完,将会导致不可预知的问题;
为了实现高可用,将会选择主从复制机制,但是主从复制机制是异步的,会出现数据不同步的问题,可能导致多个机器的多个线程获取到同一个锁。
针对上面这些问题,有如下一些解决方案:

第一个问题是因为两个命令是分开执行并且不具备原子特性,如果能将这两个命令合二为一就可以解决问题了。在Redis2.6.12版本中实现了这个功能,Redis为set命令增加了一系列选项,可以通过SET resource_name my_random_value NX PX max-lock-time来获取分布式锁,这个命令仅在不存在key(resource_name)的时候才能被执行成功(NX选项),并且这个key有一个max-lock-time秒的自动失效时间(PX属性)。这个key的值是“my_random_value”,它是一个随机值,这个值在所有的机器中必须是唯一的,用于安全释放锁。
为了解决第二个问题,用到了“my_random_value”,释放锁的时候,只有key存在并且存储的“my_random_value”值和指定的值一样才执行del命令,此过程可以通过以下Lua脚本实现:
if redis.call(“get”,KEYS[1]) == ARGV[1] then return redis.call(“del”,KEYS[1]) else return 0 end
第三个问题是因为采用了主从复制导致的,解决方案是不采用主从复制,使用RedLock算法,这里引用网上一段关于RedLock算法的描述。
在Redis的分布式环境中,假设有5个Redis master,这些节点完全互相独立,不存在主从复制或者其他集群协调机制。为了取到锁,客户端应该执行以下操作:

获取当前Unix时间,以毫秒为单位;
依次尝试从N个实例,使用相同的key和随机值获取锁。在步骤2,当向Redis设置锁时,客户端应该设置一个网络连接和响应超时时间,这个超时时间应该小于锁的失效时间。例如你的锁自动失效时间为10秒,则超时时间应该在5-50毫秒之间。这样可以避免服务器端Redis已经挂掉的情况下,客户端还在死死地等待响应结果。如果服务器端没有在规定时间内响应,客户端应该尽快尝试另外一个Redis实例;
客户端使用当前时间减去开始获取锁时间(步骤1记录的时间)就得到获取锁使用的时间。当且仅当从大多数(这里是3个节点)的Redis节点都取到锁,并且使用的时间小于锁失效时间时,锁才算获取成功。
如果取到了锁,key的真正有效时间等于有效时间减去获取锁所使用的时间(步骤3计算的结果);
如果因为某些原因,获取锁失败(没有在至少N/2+1个Redis实例取到锁或者取锁时间已经超过了有效时间),客户端应该在所有的Redis实例上进行解锁(即便某些Redis实例根本就没有加锁成功)。
通过上面的解决方案可以实现一个高效、高可用的分布式锁,这里推荐一个成熟、开源的分布式锁实现,即Redisson。

优点:高性能,借助Redis实现比较方便。

缺点:线程获取锁后,如果处理时间过长会导致锁超时失效,所以,通过锁超时机制不是十分可靠。

基于ZooKeeper的实现方式

ZooKeeper是一个为分布式应用提供一致性服务的开源组件,它内部是一个分层的文件系统目录树结构,规定同一个目录下只能有一个唯一文件名。基于ZooKeeper实现分布式锁的步骤如下:

创建一个目录mylock;
线程A想获取锁就在mylock目录下创建临时顺序节点;
获取mylock目录下所有的子节点,然后获取比自己小的兄弟节点,如果不存在,则说明当前线程顺序号最小,获得锁;
线程B获取所有节点,判断自己不是最小节点,设置监听比自己次小的节点;
线程A处理完,删除自己的节点,线程B监听到变更事件,判断自己是不是最小的节点,如果是则获得锁。
这里推荐一个apache的开源库Curator,它是一个ZooKeeper客户端,Curator提供的InterProcessMutex是分布式锁的实现,acquire方法用于获取锁,release方法用于释放锁。

优点:具备高可用、可重入、阻塞锁特性,可解决失效死锁问题。

缺点:因为需要频繁的创建和删除节点,性能上不如Redis方式。

总结

上面的三种实现方式,没有在所有场合都是完美的,所以,应根据不同的应用场景选择最适合的实现方式。
如果你想学习Java工程化、高性能及分布式、高性能、深入浅出。性能调优、Spring,MyBatis,Netty源码分析和大数据等知识点可以来找我。

而现在我就有一个平台可以提供给你们学习,让你在实践中积累经验掌握原理。主要方向是JAVA架构师。如果你想拿高薪,想突破瓶颈,想跟别人竞争能取得优势的,想进BAT但是有担心面试不过的,可以加我的Java架构进阶群:675047716

注:加群要求

1、具有2-5工作经验的,面对目前流行的技术不知从何下手,需要突破技术瓶颈的可以加。
2、在公司待久了,过得很安逸,但跳槽时面试碰壁。需要在短时间内进修、跳槽拿高薪的可以加。
3、如果没有工作经验,但基础非常扎实,对java工作机制,常用设计思想,常用java开发框架掌握熟练的,可以加。
4、觉得自己很牛B,一般需求都能搞定。但是所学的知识点没有系统化,很难在技术领域继续突破的可以加。
5.阿里Java高级大牛直播讲解知识点,分享知识,多年工作经验的梳理和总结,带着大家全面、科学地建立自己的技术体系和技术认知!
6.小号加群一律不给过,谢谢。
转发此文章请带上原文链接,否则将追究法律责任!

Java分布式锁那点事相关推荐

  1. java分布式锁解决方案 redisson or ZooKeeper

    redis 分布式锁 Redisson 是 redis 官方推荐的Java分布式锁第三方框架. 高效分布式锁 当我们在设计分布式锁的时候,我们应该考虑分布式锁至少要满足的一些条件,同时考虑如何高效的设 ...

  2. java分布式锁终极解决方案之 redisson

    目前有很多项目还在使用redis的 setNx 充当分布式锁,然而这个锁是有问题的,redisson是java支持redis的redlock的唯一实现,.目前支持集群模式,云托管模式,单Redis节点 ...

  3. 搞懂Java分布式锁实现看这篇文章就对了

    2019独角兽企业重金招聘Python工程师标准>>> 前言: 随着微处理机技术的发展,人们只需花几百美元就能买到一个CPU芯片,这个芯片每秒钟执行的指令比80年代最大的大型机的处理 ...

  4. Java分布式锁看这篇就够了,java基础面试笔试题

    我总结出了很多互联网公司的面试题及答案,并整理成了文档,以及各种学习的进阶学习资料,免费分享给大家. 扫描二维码或搜索下图红色VX号,加VX好友,拉你进[程序员面试学习交流群]免费领取.也欢迎各位一起 ...

  5. Java分布式锁的概念以及使用优点

    普通的锁,即在单机多线程环境下,当多个线程需要访问同一个变量或代码片段时,被访问的变量或代码片段叫做临界区域,我们需要控制线程一个一个的顺序执行,否则会出现并发问题. 设置一个各个线程都能看的见的标志 ...

  6. 【Java分布式锁都有哪几种实现方式】

    在分布式系统中,为了保证共享资源的互斥性,需要使用分布式锁.在Java中,实现分布式锁的方式有以下几种: 基于数据库实现分布式锁(唯一索引.悲观锁) 可以使用数据库中的唯一索引或者悲观锁来实现分布式锁 ...

  7. Java分布式锁解决方案

    目录 1.多线程并发问题 2.普通锁 3.分布式锁 4.分布式锁解决方案 4.1.Redis分布式锁(常用) 版本一:setnx key value 版本二:set key value nx ex s ...

  8. java分布式锁工具类_java 通过redis实现分布式锁

    1. 开局 在多线程环境中,经常会碰到需要加锁的情况,由于现在的系统基本都是集群分布式部署,JVM的lock已经不能满足分布式要求,分布式锁就这样产生了... 百度一下,网上有很多分布式锁的方案或者例 ...

  9. java分布式锁的三种实现方式

    分布式锁的核心思想,就是使用外部的一块共享的区域,来完成锁的实现. 一.使用mysql数据库实现(基本不用) 1.使用数据库悲观锁 可以使用select ... for update 来实现分布式锁. ...

最新文章

  1. 通过Google挖掘细分市场的一个案例
  2. ansys选择一个面上所有节点_如何使用ANSYS经典界面的选择工具
  3. 2018年自主车企销量目标完成情况 这几家企业估计要凉
  4. My first App EncryptWheel is in WAITING FOR REVIEW status
  5. [二分查找] 一:子区间界限应当如何确定
  6. 我的编码规范(慢慢补充)
  7. [Everyday Mathematics]20150103
  8. 单片机控制两个步进电机画圆_51单片机控制两个步进电机
  9. 三星鸿蒙手机,被忽视的对手:三星的自研系统,已全球第一,成华为鸿蒙对手...
  10. opencv机器学习ml模块简介
  11. 循序渐进之Maven(4) - 第一个SpringMVC项目
  12. [验证码实现] Captcha 验证码类,一个很个性的验证码类 (转载)
  13. nc 二次开发_金蝶云星空(K3CLOUD)和用友NC对比
  14. PLC控制系统设计的基本内容
  15. 洛谷试炼场---普及练习场
  16. Qt之动态属性unpolish()和polish()
  17. 【转】Android地图开发之OpenStreetMap基础教程
  18. 【Pyton安装】Python入门最详细的环境安装(附步骤),按照步骤点,几分钟就可以完成哦~
  19. set接口-存储及遍历、HashSet、LinkedHashSet、TreeSet
  20. signalr php,继SignalR 持久链接 Web客户端

热门文章

  1. 中国语言地图集 c1-12,中国语言地图集介绍——网上收集整理
  2. 房产经纪龙头居安思危孵化「贝壳」,如何用数字化解找房之痛?
  3. wsl连接vscode
  4. 结巴分词,停用词生成词云图
  5. 阿里云Android直播demo流程
  6. 高德地图之周边搜索和路线规划
  7. 如何解决WORD安全模式错误问题
  8. Python爬虫——爬去必应壁纸(简化版)
  9. 量化交易6-backtrader编写策略的时数据获取
  10. 数据分析应关注AARRR模型的哪些指标