最近小莱去大厂面试,最终挂在了分布式锁上,于是回来后认真整理了这篇文章,以期下次面试遇到同样的问题时一雪前耻......

什么是分布式锁

分布式锁是控制分布式系统之间同步访问共享资源的一种方式。举个通俗易懂的例子:网吧打游戏。

小莱去网吧打游戏,路上碰巧遇到了同学小王和小丁,三人同时来到网吧前台表示都想在包厢里上网。但是包厢只有一个,同一时间也只能容纳一人,前台MM很为难。突然,前台MM心生一计,将一枚硬币抛于空中,让他们三人同时争抢,谁能抢到谁去包厢。只见小莱眼疾手快最终将硬币据为己有,看着不甘的小王和小丁,哼着小曲进了包厢.....

在这个例子中,小莱、小王和小丁可以看成三个独立分布的客户端(三个独立系统),小莱在包厢上网的时间看作锁的时间,包厢可以看作同一资源。同一时刻三人都想去包厢(即都想访问同一资源),那么硬币就可以作为一把分布式锁限制同一时刻共享包厢的人员。

分布式锁的场景

当多个机器(多个进程)对同一数据进行修改时,并且要求这个修改是原子性的,那么就要用到分布式锁。例如:秒杀时解决库存超卖问题。

分布式锁的特点

1、互斥性

任意时刻,只有一个客户端能够持有锁。

2、不会发送死锁

即使有一个客户端在持有锁的期间崩溃而没有主动解锁,也能保证后续其他客户端能加锁。

3、容错性

只要大部分的redis节点正常运行,客户端就可以加锁和解锁。

4、解锁

加锁和解锁必须为同一个客户端,客户端不能解锁他人的锁。

分布式锁的实现

  • 基于redis实现

  • 基于mysql乐观锁实现

  • 基于zookeeper实现

在这篇文章中,我们重点来讲述如何通过redis来实现分布式锁。

加锁实现方式

常用redis命令

  • setnx:在指定的key不存在时,为key设置指定值

  • expire:设置key过期时间,单位以秒计

  • getset:设置指定key的值,并返回key的旧值

错误示例

1、通过setnx、expire实现

实现思路:在当前锁没有被占用的情况下,加锁成功后,给锁设置一个过期时间。

这乍看没有什么问题,但是仔细思考之后就会发现由于setnx/expire不具有原子性,某一时刻进程在执行expire前突然崩溃,就会导致该锁永久存在,后续进程在获取锁时发现锁已被占用,从而导致无法加锁。

2、将锁的值设为过期时间,通过锁的值对比实现

这段代码实现的缺点是:

  1. 需要分布式下每个客户端的时间保持一致;

  2. 锁快过期时,多个客户端同时执行getSet,虽然最终只有一个客户端可以加锁,但该客户端锁的过期时间可能被其他客户端覆盖;

  3. 不具备拥有者标识,任何客户端都可以解锁。

正确示例

参数说明:

  • nx:SET IF NOT EXIST,即当key不存在时,进行set操作,若key已经存在,则不做任何操作

  • px:给key加一个过期时间,单位ms

redis2.8版本后,set里提供了px参数,因此我们在实现分布式锁的时候就可以进行原子操作,同时加锁操作也变得简单。

通过上述示例,我们已经清楚地知道了加锁的实现方式,但是解铃还须系铃人,解锁如何实现呢?

解锁实现方式

常用redis命令

  • del:用于删除已存在的键

  • pttl:以毫秒为单位返回key的剩余过期时间

错误示例

1、最常见的一种错误解锁方式是直接通过删除del来进行的:

这种方式的错误在于不具有拥有者标识,任何客户端都可以随时进行解锁。

2、有人可能会说,加锁时给每个客户端分配一个唯一的value值,每次释放锁前把锁的值与客户端传过来的值做对比,相同再删除不就行了,即:

这种方式确实在一般情况下能够解决锁被其他客户端随意释放的问题,但是这样实现会有什么问题呢?答案是当客户端A在执行del之前,锁突然过期了,此时客户端B加锁成功,然后客户端A执行del操作则会将客户端B的锁解除。这还是因为删除不具有原子性。

:在这里还有一种解决临界条件下客户端A锁被其他客户端释放的方式,只是对性能可能有一些影响:在del前,我们可以先判断锁的过期时间,如果当前时间不小于10ms(根据自己的业务而定)的话可以操作del删除,否则自然释放,即:

正确示例

锁的释放包含了get、判断、del三个步骤。如果不能保证三个步骤的原子性,分布式锁就会有并发问题。

通过redis里eval命令操作lua代码,这样可以确保在解锁时保持原子性,而不会因为进程的崩溃导致解锁失败。

思考

到这里我们就完成了分布式锁的实现,请继续思考:

一、当在集群中,某个master节点宕机后,master数据未及时同步至slave节点时,上述示例是否还能满足当前场景?此时会发生什么样的情况?又该如何来解决?

上边讲述的示例适用于单实例或对业务要求性不高的情况,当在集群上实现分布式锁的时候,master节点宕机且数据未同步至slave节点时,此时就会出现多个客户端拥有一把锁的情况,失去了锁的互斥性原则。

基于此,redis官方提出了RedLock的实现方案,核心思想是同时使用多个Redis Master来冗余,且这些节点是完全独立的,也不需要对这些节点之间的数据进行同步。获取集群中多数master节点上的锁,同时全部获取,否则全部释放。

例如下图的集群中,同时在一半以上(2个master)的master上加锁,如果其中某一个master宕机,客户端仍然可以获取到锁。

Redis cluster集群图

二、业务未处理完面临锁时间到期如何处理?

还是开头那个例子,小莱在包厢里打游戏,任务做到一半,时间到了,这时怎么办呢?有经验的同学第一反应肯定是去续费。

那么对应到锁的应用上也是这样,当占有锁的时间快到了但是此时业务未处理完,可以延长锁的过期时间,即锁支持可重入。

总结

1、无论加锁还是解锁,都要确保原子性操作;

2、Redis分布式锁要考虑单实例和多实例的情况;

3、正确加锁方式:

如果当前业务可容忍多个客户端拥有一把锁或保证master不会发生故障,在集群中也可以使用这种方式。

4、正确解锁方式:

特别推荐一个分享架构+算法的优质内容,还没关注的小伙伴,可以长按关注一下:

长按订阅更多精彩▼如有收获,点个在看,诚挚感谢

面试官:谈谈分布式锁的实现相关推荐

  1. 面试官:分布式锁用Redis好?还是Zookeeper好?

    提到锁大家肯定有了解,像 Synchronized.ReentrantLock,在单进程情况下,多个线程访问同一资源,可以用它们来保证线程的安全性. 不过目前互联网项目越来越多的项目采用集群部署,也就 ...

  2. 面试官:Redis分布式锁解决方案是什么?

    今天博主在这片文章中主要给大家讲下Redis分布式锁的原理以及解决方案 学到三连呦 1.Redis分布式锁原理 1.1.简述 我们知道分布式锁的特性是排他.避免死锁.高可用.分布式锁的实现可以通过数据 ...

  3. 阿里面试官:分布式锁到底用Redis好?还是Zookeeper好?

    首先,分布式锁和我们平常讲到的锁原理基本一样,目的就是确保,在多个线程并发时,只有一个线程在同一刻操作这个业务或者说方法.变量. 在一个进程中,也就是一个jvm 或者说应用中,我们很容易去处理控制,在 ...

  4. 面试官:谈谈分库分表吧?

    面试官:"有并发的经验没?"  应聘者:"有一点."   面试官:"那你们为了处理并发,做了哪些优化?"   应聘者:"前后端分离 ...

  5. 联合索引会创建几个索引_面试官:谈谈你对mysql联合索引的认识?

    引言 这篇文章作为<面试官:谈谈你对mysql索引的认识>的续篇,我当时在写这篇的时候,考虑到篇幅问题所以略去了联合索引的内容,今天给大家补上. 本文预计分为两个部分:(1)联合索引部分的 ...

  6. 面试热点Redis分布式锁,再细说一次

    欢迎关注方志朋的博客,回复"666"获面试宝典 谈起redis锁,下面三个,算是出现最多的高频词汇: setnx redLock redisson | setnx 其实目前通常所说 ...

  7. redis订阅执行一段时间自动停止_面试系列 redis 分布式锁amp;数据一致性

    分布式锁 多个系统同时操作一个redis,因为jvm锁是线程级别的,所以没有办法锁住多个系统. Redis锁实现: setnx key value 只有在key不存在时设置key的值 此时key相当于 ...

  8. 面试连环炮之分布式锁

    面试连环炮系列专栏,暂不想换工作的同学可补充知识盲点查缺补漏,准备换工作的同学可针对性突击训练,不打无准备之战.面试战场所向披靡,成为offer收割机,找到心仪的工作.  楼主努力更新,争取每日多更. ...

  9. 10、面试官对于分布式搜索引擎的4个连环炮

    业内目前来说事实上的一个标准,就是分布式搜索引擎一般大家都用elasticsearch,es,solr,但是确实,这两年大家一般都用更加易用的es. lucene 如果你确实真的不连lucene都不知 ...

最新文章

  1. MySQL · B+树并发控制机制的前世今生
  2. 百度网盘的速度又又又又又又被黑了...侮辱性极强...
  3. OpenGL 字体颜色问题
  4. Linux centos 下apache(httpd)编译安装
  5. SQL2000: MMC 不能打开文件
  6. React-Amap-HOC组件封装
  7. 2014年夏末大Java新闻
  8. Mac 下隐藏显示隐藏文件
  9. Android心电数据分析,Android SurfaceView+Canvas画脉搏/心电数据图-Go语言中文社区
  10. mysql注解实体类_jpa实体类生成mysql表及字段注解
  11. LeetCode 97. 交错字符串(动态规划)
  12. layUI固定列重复
  13. android系统预制app/bin/.so文件及文件夹
  14. 如何清除vsphere主机提示“此主机当前没有管理网络冗余”
  15. hd获取硬盘序列号_获取硬盘序列号
  16. linux nginx环境下,网站不显示字体和图形.(图形变成了方框),解决nginx下加载eot|otf|ttf|woff|svg等404 错误问题
  17. st8s003 c语言编译器,ST系列STM8S003F3P6单片机芯片介绍
  18. excel打开密码忘记了_忘记EXCEL表格密码怎么办,这样操作可以清除
  19. 腾讯地图位置服务器,腾讯地图推出地形图服务
  20. React---关于useCallback和useMemo的详解

热门文章

  1. HDU4081(次小生成树)
  2. (2017)第八届蓝桥杯大赛个人赛省赛(软件类) C/C++ 大学A组 题解(第八题包子凑数)
  3. 【题解】P1419 寻找段落(二分+单调队列)难度⭐⭐⭐★
  4. 63.不同的路径II
  5. win七系统如何卸载MySQL_win7系统卸载SQL2008R2数据库的详细教程
  6. poi 合并单元格_POI数据获取脚本分享
  7. pytorch 模型可视化_高效使用Pytorch的6个技巧:为你的训练Pipeline提供强大动力
  8. win10pin不可用进不去系统_华为鸿蒙系统来了!若安卓不可用,鸿蒙随时顶上!
  9. 【CSS】【13】文字的排版
  10. 《Python数据科学指南》——1.17 使用映射函数