搞懂分布式技术16:浅谈分布式锁的几种方案

前言

随着互联网技术的不断发展,数据量的不断增加,业务逻辑日趋复杂,在这种背景下,传统的集中式系统已经无法满足我们的业务需求,分布式系统被应用在更多的场景,而在分布式系统中访问共享资源就需要一种互斥机制,来防止彼此之间的互相干扰,以保证一致性,在这种情况下,我们就需要用到分布式锁。

分布式一致性问题

首先我们先来看一个小例子:

假设某商城有一个商品库存剩10个,用户A想要买6个,用户B想要买5个,在理想状态下,用户A先买走了6了,库存减少6个还剩4个,此时用户B应该无法购买5个,给出数量不足的提示;而在真实情况下,用户A和B同时获取到商品剩10个,A买走6个,在A更新库存之前,B又买走了5个,此时B更新库存,商品还剩5个,这就是典型的电商“秒杀”活动。

从上述例子不难看出,在高并发情况下,如果不做处理将会出现各种不可预知的后果。那么在这种高并发多线程的情况下,解决问题最有效最普遍的方法就是给共享资源或对共享资源的操作加一把锁,来保证对资源的访问互斥。在Java JDK已经为我们提供了这样的锁,利用ReentrantLcok或者synchronized,即可达到资源互斥访问的目的。但是在分布式系统中,由于分布式系统的分布性,即多线程和多进程并且分布在不同机器中,这两种锁将失去原有锁的效果,需要我们自己实现分布式锁——分布式锁。

分布式锁需要具备哪些条件

  1. 获取锁和释放锁的性能要好

  2. 判断是否获得锁必须是原子性的,否则可能导致多个请求都获取到锁

  3. 网络中断或宕机无法释放锁时,锁必须被清除,不然会发生死锁

  4. 可重入一个线程中可以多次获取同一把锁,比如一个线程在执行一个带锁的方法,该方法中又调用了另一个需要相同锁的方法,则该线程可以直接执行调用的方法,而无需重新获得锁;

  5. 阻塞锁和非阻塞锁,阻塞锁即没有获取到锁,则继续等待获取锁;非阻塞锁即没有获取到锁后,不继续等待,直接返回锁失败。

分布式锁实现方式

一、数据库锁

一般很少使用数据库锁,性能不好并且容易产生死锁。

  1. 基于MySQL锁表

该实现方式完全依靠数据库唯一索引来实现,当想要获得锁时,即向数据库中插入一条记录,释放锁时就删除这条记录。这种方式存在以下几个问题:

(1) 锁没有失效时间,解锁失败会导致死锁,其他线程无法再获取到锁,因为唯一索引insert都会返回失败。

(2) 只能是非阻塞锁,insert失败直接就报错了,无法进入队列进行重试

(3) 不可重入,同一线程在没有释放锁之前无法再获取到锁

  1. 采用乐观锁增加版本号

根据版本号判断更新之前有没有其他线程更新过,如果被更新过,则获取锁失败。

二、缓存锁

具体实例可以参考我讲述Redis的系列文章,里面有完整的Redis分布式锁实现方案

这里我们主要介绍几种基于redis实现的分布式锁:

  1. 基于setnx、expire两个命令来实现

基于setnx(set if not exist)的特点,当缓存里key不存在时,才会去set,否则直接返回false。如果返回true则获取到锁,否则获取锁失败,为了防止死锁,我们再用expire命令对这个key设置一个超时时间来避免。但是这里看似完美,实则有缺陷,当我们setnx成功后,线程发生异常中断,expire还没来的及设置,那么就会产生死锁。

解决上述问题有两种方案

第一种是采用redis2.6.12版本以后的set,它提供了一系列选项

EX seconds – 设置键key的过期时间,单位时秒

PX milliseconds – 设置键key的过期时间,单位时毫秒

NX – 只有键key不存在的时候才会设置key的值

XX – 只有键key存在的时候才会设置key的值

第二种采用setnx(localkey,value),get(localkey),getset(localkey,value)实现,大体的实现过程如下:

(1) 线程Asetnx,值为超时的时间戳(t1),如果返回true,获得锁。

(2) 线程B用get 命令获取t1,与当前时间戳比较,判断是否超时,没超时false,如果已超时执行步骤3

(3) 计算新的超时时间t2,使用getset命令返回t3(这个值可能其他线程已经修改过),如果t1==t3,获得锁,如果t1!=t3说明锁被其他线程获取了

(4) 获取锁后,处理完业务逻辑,再去判断锁是否超时,如果没超时删除锁,如果已超时,不用处理(防止删除其他线程的锁)

一.redis命令讲解: setnx()命令: setnx的含义就是SET if Not Exists,其主要有两个参数 setnx(key, value)。

该方法是原子的,如果key不存在,则设置当前key成功,返回1;如果当前key已经存在,则设置当前key失败,返回0。

get()命令: get(key) 获取key的值,如果存在,则返回;如果不存在,则返回nil; getset()命令: 这个命令主要有两个参数 getset(key, newValue)。该方法是原子的,对key设置newValue这个值,并且返回key原来的旧值。 假设key原来是不存在的,那么多次执行这个命令,会出现下边的效果:

getset(key, "value1") 返回nil 此时key的值会被设置为value1

1.getset(key, "value2") 返回value1 此时key的值会被设置为value2

2.依次类推!

二.具体的使用步骤如下:

1.setnx(lockkey, 当前时间+过期超时时间) ,如果返回1,则获取锁成功;如果返回0则没有获取到锁,转向2。

  1. get(lockkey)获取值oldExpireTime ,并将这个value值与当前的系统时间进行比较,如果小于当前系统时间,则认为这个锁已经超时,可以允许别的请求重新获取,转向3。

    1. 计算newExpireTime=当前时间+过期超时时间,然后getset(lockkey, newExpireTime) 会返回当前lockkey的值currentExpireTime。

    2. 判断currentExpireTime与oldExpireTime 是否相等,如果相等,说明当前getset设置成功,获取到了锁。如果不相等,说明这个锁又被别的请求获取走了,那么当前请求可以直接返回失败,或者继续重试。

  2. 在获取到锁之后,当前线程可以开始自己的业务处理,当处理完毕后,比较自己的处理时间和对于锁设置的超时时间,如果小于锁设置的超时时间,则直接执行delete释放锁;如果大于锁设置的超时时间,则不需要再锁进行处理。

  1. RedLock算法

redlock算法是redis作者推荐的一种分布式锁实现方式,算法的内容如下:

(1) 获取当前时间;

(2) 使用setNx()尝试从5个相互独立redis客户端获取锁;

(3) 计算获取所有锁消耗的时间,当且仅当客户端从多数节点获取锁,并且获取锁的时间小于锁的有效时间,认为获得锁;

(4) 重新计算有效期时间,原有效时间减去获取锁消耗的时间;

(5) 删除所有实例的锁

redlock算法相对于单节点redis锁可靠性要更高,但是实现起来条件也较为苛刻。

(1) 必须部署5个节点才能让Redlock的可靠性更强

(2) 需要请求5个节点才能获取到锁,通过Future的方式,先并发向5个节点请求,再一起获得响应结果,能缩短响应时间,不过还是比单节点redis锁要耗费更多时间

然后由于必须获取到5个节点中的3个以上,所以可能出现获取锁冲突,即大家都获得了1-2把锁,结果谁也不能获取到锁,这个问题,redis作者借鉴了raft算法的精髓,通过冲突后从随机时间开始,可以大大降低冲突时间,但是这问题并不能很好的避免,特别是在第一次获取锁的时候,所以获取锁的时间成本增加了。

如果5个节点有2个宕机,此时锁的可用性会极大降低,首先必须等待这两个宕机节点的结果超时才能返回,另外只有3个节点,客户端必须获取到这全部3个节点的锁才能拥有锁,难度也加大了。

如果出现网络分区,那么可能出现客户端永远也无法获取锁的情况,介于这种情况,下面我们来看一种更可靠的分布式锁zookeeper锁。

三、zookeeper分布式锁

关于zookeeper的分布式锁实现在之前讲述zookeeper的时候已经介绍了。这里不再赘述、

首先我们来了解一下zookeeper的特性,看看它为什么适合做分布式锁,

zookeeper是一个为分布式应用提供一致性服务的软件它内部是一个分层的文件系统目录树结构,规定统一个目录下只能有一个唯一文件名。

数据模型:

永久节点:节点创建后,不会因为会话失效而消失

临时节点:与永久节点相反,如果客户端连接失效,则立即删除节点

顺序节点:(临时或者永久的顺序节点)与上述两个节点特性类似,如果指定创建这类节点时,zk会自动在节点名后加一个数字后缀,并且是有序的。

监视器(watcher):

当创建一个节点时,可以注册一个该节点的监视器,当节点状态发生改变时,watch被触发时,ZooKeeper将会向客户端发送且仅发送一条通知,因为watch只能被触发一次。

根据zookeeper的这些特性,我们来看看如何利用这些特性来实现分布式锁:

  1. 创建一个锁目录lock

  2. 希望获得锁的线程A就在lock目录下,创建临时顺序节点

  3. 获取锁目录下所有的子节点,然后获取比自己小的兄弟节点,如果不存在,则说明当前线程顺序号最小,获得锁

  4. 线程B获取所有节点,判断自己不是最小节点,设置监听(watcher)比自己次小的节点(只关注比自己次小的节点是为了防止发生“羊群效应”)

  5. 线程A处理完,删除自己的节点,线程B监听到变更事件,判断自己是最小的节点,获得锁。

小结

在分布式系统中,共享资源互斥访问问题非常普遍,而针对访问共享资源的互斥问题,常用的解决方案就是使用分布式锁,这里只介绍了几种常用的分布式锁,分布式锁的实现方式还有有很多种,根据业务选择合适的分布式锁,下面对上述几种锁进行一下比较:

数据库锁:

优点:直接使用数据库,使用简单

缺点:分布式系统大多数瓶颈都在数据库,使用数据库锁会增加数据库负担。

缓存锁:

优点:性能高,实现起来较为方便,在允许偶发的锁失效情况,不影响系统正常使用,建议采用缓存锁。

缺点:通过锁超时机制不是十分可靠,当线程获得锁后,处理时间过长导致锁超时,就失效了锁的作用。

zookeeper锁:

优点:不依靠超时时间释放锁;可靠性高;系统要求高可靠性时,建议采用zookeeper锁。

缺点:性能比不上缓存锁,因为要频繁的创建节点删除节点。

转载于:https://www.cnblogs.com/itxiaok/p/10356670.html

搞懂分布式技术16:浅谈分布式锁的几种方案相关推荐

  1. 浅谈分布式架构搭建-理论知识

    浅谈分布式架构搭建 基础 理念 技术选型 后端技术设计 总体架构设计 关键案例设计 架构师搭建架一般优先考虑的是安全性.稳定性.高吞吐量.哈哈,菜鸟的我让我装个B,回忆一下以前架构搭建 基础 理念 C ...

  2. 技术帖 浅谈屏幕比例 16比9和4比3的小秘密

    技术帖 浅谈屏幕比例 16比9和4比3的小秘密 随着平板电视的日益普及~  16:9 已经逐渐进入普通百姓的客厅 普及一下 关于屏幕比例的知识 解释一下 各种比例之间有何区别 结合本人的一点点 视频方 ...

  3. oracle hash join outer,CSS_浅谈Oracle中的三种Join方法,基本概念 Nested loop join: Outer - phpStudy...

    浅谈Oracle中的三种Join方法 基本概念 Nested loop join: Outer table中的每一行与inner table中的相应记录join,类似一个嵌套的循环. Sort mer ...

  4. [转]浅谈MS-SQL锁机制

    本文转自:http://study.99net.net/study/database/mssql/1085625420.html 浅谈MS-SQL锁机制 2004-05-27     锁的概述 一. ...

  5. [分享]浅谈分布式数据库

    文章集中整理总结mysql分库分表开源产品,分布式数据库的设计,以及实际应用案例等相关内容,部分附上本文作者实际应用过程中的理解. 本文感谢sjdbc,mycat,姜承尧,林涛等文章提供的精彩介绍. ...

  6. 浅谈分布式一致性算法raft

    前言:在分布式的系统中,存在很多的节点,节点之间如何进行协作运行.高效流转.主节点挂了怎么办.如何选主.各节点之间如何保持一致,这都是不可不面对的问题,此时raft算法应运而生,专门 用来解决上述问题 ...

  7. 浅谈分布式 ID 的实践与应用

    导读:在业务系统中很多场景下需要生成不重复的 ID,比如订单编号.支付流水单号.优惠券编号等都需要使用到. 文|古德 网易云信资深 JAVA 开发工程师 本文将介绍分布式 ID 的产生原因,以及目前业 ...

  8. 浅谈分布式一致性:Raft 与 SOFAJRaft

    简介: SOFAJRaft已开源 作者 | 家纯 来源 | 阿里技术公众号 一 分布式共识算法 (Consensus Algorithm) 1 如何理解分布式共识? 多个参与者针对某一件事达成完全一致 ...

  9. 浅谈分布式和微服务架构

    文章目录 一.微服务简介 1.微服务的诞生 2.微服务架构与SOA架构的区别 二.CAP理论 三.分布式事务 四.服务拆分 总结 一.微服务简介 1.微服务的诞生   在微服务概念出现之前还有一个概念 ...

最新文章

  1. git在不同操作系统下自动替换换行符
  2. 【怎样写代码】偷窥高手 -- 反射技术(七):通过反射实例化对象
  3. python绘制灰度图片直方图-python数字图像处理实现直方图与均衡化
  4. 在Delphi中根据SQL Server表名和表描述生成SQL语句
  5. 第六十六篇、OC_Sqlite数据库操作
  6. java(13)内部类
  7. hdu-Find the nondecreasing subsequences(树状数组)
  8. AI理论知识整理(6)-最大似然法
  9. Unity3D研究院之获取摄像机的视口区域
  10. 机器学习——支持向量机SVM之多分类问题
  11. Android之INSTALL_FAILED_INSUFFICIENT_STORAG解决办法
  12. 洛谷 1087——FBI树
  13. UNIX系统编程小结(三)----进程相关
  14. mysql2008怎么安装_SQL Server 2008如何安装及附加数据库?
  15. photoshop制作透明背景图片1
  16. 不知道何时,我逐渐丧失了表达能力
  17. java am pm_【am.pm正确时间书写格式】作文写作问答 - 归教作文网
  18. Python 灰帽子笔记之调试器
  19. Appium连接逍遥模拟器,解决Timing Out
  20. 分布式算法---gossip 协议

热门文章

  1. python各个解释器的用途-Python解释器有哪些?Python解释器种类
  2. 小学生 python教程-Python最佳学习路线图
  3. python timer使用-python中timer定时器常用的两种实现方法
  4. 关于python语言、下列说法不正确的是-关于 Python语言,哪个说法是不正确的?
  5. python 字符串前加r b u f 含义
  6. CVPR2019目标检测方法进展综述
  7. 错误:AttributeError: module 'enum' has no attribute 'IntFlag'
  8. LeetCode Interleaving String(动态规划)
  9. mysql 批量更新
  10. 结构型模式之Proxy模式