本篇文章将会剖析分布式锁,以及三种实现分布式锁的方法,包括基于数据库实现、基于缓存(Redis)实现、以及基于Zookeeper实现。

文章目录

  • 1.分布式锁
  • 2. 分布式锁的三种实现方式
    • 2.1 基于数据库实现分布式锁
    • 2.2 基于缓存实现分布式锁
    • 2.3 基于Zookeeper实现分布式锁
    • 2.4 三种实现分布式锁的方式对比
  • 3. 设计分布式锁的考虑角度

1.分布式锁

在多个线程访问同一个共享资源时,为了维护数据的一致性等,我们需要某种机制来保证只有满足某个条件的线程才能访问资源,这个机制就称为:锁。

锁是实现多线程同时访问同一共享资源,保证同一时刻只有一个线程可访问共享资源所作的一种标记。

与普通锁不同的是,分布式锁是指分布式环境下,系统部署在多个机器中,实现多进程分布式互斥的一种锁。为了保证多个进程都能看到锁,锁被存在公共存储(比如Redis)中,以实现多个进程并发访问同一共享资源,同一时刻只有一个进程可访问共享资源,确保资源的一致性。

下面介绍一个场景:某电商售卖一款手机,用户A想买一部,用户B想买2部,而库存只有2部手机。理想状态下,用户A网速好先买走一部,库存剩一部,此时应该提醒用户B购买失败。但实际情况是,用户A和B同时获取到商品库存还剩2部,然后用户A买走1部,在用户A更新库存之前,用户B又买走2部,次数用户B更新库存,商品还剩0部。就造成了“超卖”的情况。

在大规模分布式系统中,单个机器的线程锁无法管控多个机器对同一资源的访问,这时使用分布式锁,就可以把整个集群当作一个应用一样去处理,实用性和扩展性更好。

2. 分布式锁的三种实现方式

2.1 基于数据库实现分布式锁

这里指的是使用关系型数据库。
最简单方式就是创建一张表,然后通过操作该表中的数据来实现。
当我们要锁住某个资源时,就在该表中增加一条记录,想要释放锁的时候就删除这条记录。数据库对共享资源做唯一性约束,如果有多个请求被同时提交到数据库的话,数据库会保证只有一个操作可以成功,操作成功的那个线程就获得了访问共享资源的锁,从而进行操作。

但是这种方法只适用于并发量低,对性能要求低的场景。

基于数据库实现分布式锁比较简单,在于创建一张锁表,为申请者在锁表里建立一条记录,记录建立成功则获得锁,消除记录则释放锁。同样,这种方式存在两个极大的缺点:

  • 单点故障问题。 一旦数据库不可用,会导致整个系统崩溃。
  • 死锁问题。 数据库锁没有失效时间,未获得锁的进程只能一直等待已获得锁的进程主动释放锁。一旦已获得锁的进程挂掉或者解锁操作失败,会导致锁记录一直存在数据库中,其他进程无法获得锁。

2.2 基于缓存实现分布式锁

所谓基于缓存,也就是说把数据存放在计算机内存中,不需要写入磁盘,减少了IO读写。这里以Redis为例。

Redis 通常可以使用 setnx(key, value) 函数来实现分布式锁。 key 和 value 就是基于缓存的分布式锁的两个属性,其中 key 表示锁 id,value = currentTime + timeOut,表示当前时间 +超时时间。也就是说,某个进程获得 key 这把锁后,如果在 value 的时间内未释放锁,系统就会主动释放锁。

setnx 函数的返回值有 0 和 1:

  • 返回 1,说明该服务器获得锁,setnx 将 key 对应的 value 设置为当前时间 + 锁的有效时间。
  • 返回 0,说明其他服务器已经获得了锁,进程不能进入临界区。该服务器可以不断尝试 setnx操作,以获得锁。

Redis 通过队列来维持进程访问共享资源的先后顺序,Redis 锁主要基于 setnx 函数实现分布式锁。当进程通过 setnx<key,value> 函数返回 1 时,表示已经获得锁。排在后面的进程只能等待前面的进程主动释放锁,或者等到时间超时才能获得锁。

相对于基于数据库实现分布式锁的方案来说,基于缓存实现的分布式锁的优势表现在以下几个方面:

  • 性能更好。数据被存放在内存,而不是磁盘,避免了频繁的IO操作。
  • 很多缓存可以跨集群部署,避免了单点故障问题。
  • 很多缓存服务都提供了可以用来实现分布式锁的方法,比如 Redis 的 setnx 方法等。
  • 可以直接设置超时时间来控制锁的释放,因为这些缓存服务器一般支持自动删除过期数据。

这个方案的不足是,通过超时时间来控制锁的失效时间,并不是十分靠谱,因为一个进程执行时间可能比较长,或受系统进程做内存回收等影响,导致时间超时,从而不正确地释放了锁。

2.3 基于Zookeeper实现分布式锁

ZooKeeper 基于树形数据存储结构实现分布式锁,来解决多个进程同时访问同一临界资源时,数据的一致性问题。ZooKeeper 的树形数据存储结构主要由 4 种节点构成。

  • 持久节点。这是默认的节点类型,一直存在于 ZooKeeper 中。
  • 持久顺序节点。也就是说,在创建节点时,ZooKeeper 根据节点创建的时间顺序对节点进行编号。
  • 临时节点。与持久节点不同,当客户端与 ZooKeeper 断开连接后,该进程创建的临时节点就会被删除。
  • 临时顺序节点,就是按时间顺序编号的临时节点。

根据它们的特征,ZooKeeper 基于临时顺序节点实现了分布锁。

还是以电商场景为例,Zookeeper会采用如下方法来实现分布式锁:
1)在与该方法对应的持久节点 shared_lock 的目录下,为每个进程创建一个临时顺房节点。如下图所示,手机就是一个拥有 shared_lock 的目录,当有人手机时,会为他创建一个临时顺序节点。
2)每个进程获取 shared_lock 目录下的所有临时节点列表,注册子节点变更的 Watcher,并监听节点。
3)每个节点确定自己的编号是否是 shared_lock 下所有子节点中最小的,若最小,则获得锁。例如,用户 A 的订单最先到服务器,因此创建了编号为 1的临时顺序节点 LockNode1。该节点的编号是持久节点目录下最小的,因此获取到分布式锁,可以访问临界资源,从而可以购买商品。
4)若本进程对应的临时节点编号不是最小的,则分为两种情况:
a.本进程为读请求,如果比自己序号小的节点中有写请求,则等待;
b.本进程为写请求,如果比自己序号小的节点中有读请求,则等待。

使用 ZooKeeper 可以完美解决设计分布式锁时遇到的各种问题,比如单点故障、不可重入、死锁等问题。虽然 ZooKeeper 实现的分布式锁,几乎能涵盖所有分布式锁的特性,且易于实现,但需要频繁地添加和删除节点,所以性能不如基于缓存实现的分布式锁。

知识扩展:如何解决分布式锁的羊群效应问题?

在分布式锁问题中,会经常遇到羊群效应。所谓羊群效应,就是在整个分布式锁的竞争过程中大量的“Watcher 通知”和“子节点列表的获取”操作重复运行,并且大多数节点的运行结果都是判断出自己当前并不是编号最小的节点,继续等待下一次通知,而不是执行业务逻辑。

这就会对 ZooKeeper 服务器造成巨大的性能影响和网络冲击。更甚的是,如果同一时间多个节点对应的客户端完成事务或事务中断引起节点消失,ZooKeeper 服务器就会在短时间内向其他客户端发送大量的事件通知。

那如何解决这个问题呢? 具体方法可以分为以下三步:

  • 1.在与该方法对应的持久节点的目录下,为每个进程创建一个临时顺序节点
  • 2.每个进程获取所有临时节点列表,对比自己的编号是否最小,若最小,则获得锁。
  • 3.若本进程对应的临时节点编号不是最小的,则继续判断:
    • 若本进程为读请求,则向比自己序号小的最后一个写请求节点注册 watch 监听,当监听到该节点释放锁后,则获取锁:
    • 若本进程为写请求,则向比自己序号小的最后一个读请求节点注册 watch 监听,当监听到该节点释放锁后,获取锁。

2.4 三种实现分布式锁的方式对比

对比角度 排序
理解的容易程度 数据库 > 缓存 > ZooKeeper
实现的复杂性 ZooKeeper > 缓存 > 数据库
性能 缓存 > ZooKeeper > 数据库
可靠性 ZooKeeper > 缓存 > 数据库

ZooKeeper 分布式锁的可靠性最高,有封装好的框架,很容易实现分布式锁的功总结来说,能,并且几乎解决了数据库锁和缓存式锁的不足,因此是实现分布式锁的首选方法。

3. 设计分布式锁的考虑角度

为了确保分布式锁的可用性,我们在设计时应考虑到以下几点:

  • 互斥性,即在分布式系统环境下,分布式锁应该能保证一个资源或一个方法在同一时间只能被个机器的一个线程或进程操作
  • 具备锁失效机制,防止死锁。即使有一个进程在持有锁的期间因为崩溃而没有主动解锁,也能保证后续其他进程可以获得锁。
  • 可重入性,即进程未释放锁时,可以多次访问临界资源.
  • 高可用的获取锁和释放锁的功能,且性能要好。

分布式锁的介绍与实现相关推荐

  1. Redis6笔记分享(从NoSQL基础到分布式锁的介绍)

    Redis6 1.NoSQL数据库简介 1.1技术发展 题外话:技术的分类 1.解决功能性的问题:Java.Jsp.RDBMS.Tomcat.HTML.Linux.JDBC.SVN 项目的本质无非是增 ...

  2. 关于几种分布式锁的简单介绍

    什么是分布式锁 要介绍分布式锁,首先要提到与分布式锁相对应的是线程锁.进程锁. 1.线程锁 主要用来给方法.代码块加锁.当某个方法或代码使用锁,在同一时刻仅有一个线程执行该方法或该代码段.线程锁只在同 ...

  3. 聊聊分布式锁——Redis和Redisson的方式

    聊聊分布式锁--Redis和Redisson的方式 一.什么是分布式锁 分布式~~锁,要这么念,首先得是『分布式』,然后才是『锁』 分布式:这里的分布式指的是分布式系统,涉及到好多技术和理论,包括CA ...

  4. Redis系列教程(八):分布式锁的由来、及Redis分布式锁的实现详解

    在很多场景中,我们为了保证数据的最终一致性,需要很多的技术方案来支持,比如分布式事务.分布式锁等.那具体什么是分布式锁,分布式锁应用在哪些业务场景.如何来实现分布式锁呢?今天来探讨分布式锁这个话题. ...

  5. 多机器使用setnx 设置同一个key_Redisson分布式锁的简单使用

    做一个积极的人 编码.改bug.提升自己 我有一个乐园,面向编程,春暖花开! 一:前言 我在实际环境中遇到了这样一种问题,分布式生成id的问题!因为业务逻辑的问题,我有个生成id的方法,是根据业务标识 ...

  6. redis锁和分布式锁的实现

    redis环境搭建 redis在java.spring.springboot中的实现 redis锁 1.添加依赖 <dependency><groupId>org.spring ...

  7. 分布式架构 --- 分布式锁

    分布式锁 1. 研究背景及其意义 2. 分布式锁的介绍 2.1 分布式锁 2.2 为什么需要分布式锁 2.3 分布式锁的基本要求 3. 分布式锁的实现 3.1 基于数据库的分布式锁 3.1.1选用数据 ...

  8. 第07课:基于 Redis 的分布式锁实现及其踩坑案例

    分布式锁的实现,目前常用的方案有以下三类: 数据库乐观锁: 基于分布式缓存实现的锁服务,典型代表有 Redis 和基于 Redis 的 RedLock: 基于分布式一致性算法实现的锁服务,典型代表有 ...

  9. Zookeeper学习:Zookeeper应用场景之分布式锁

    1. 分布式锁的介绍 分布式锁是控制分布式系统之间同步访问共享资源的一种方式.如果不同的系统或是同一个系统的不同主机之间共享了一个或一组资源,那么访问这些资源的时候,往往需要通过一些互斥手段来防止彼此 ...

最新文章

  1. git服务器查看用户信息,git 查看当前git用户_新Git用户使用方法
  2. 华尔街风暴的深层原因
  3. 8. Leetcode 26. 删除有序数组中的重复项 (数组-同向双指针-快慢指针)
  4. Java 设计模式——外观模式
  5. 构造函数、实例、原型对象、继承
  6. 从来不敷面膜的人_女人睡觉前,敷面膜洗还是不洗?很多人都做错了,难怪皮肤总不好...
  7. C++与Java异常处理的区别
  8. 灵玖软件:九眼智能文档核查云平台上线了
  9. TableviewController基础
  10. 最简单的视音频播放示例1:总述
  11. 下发布可执行文件_IOS APP 发布过程中涉及相关概念
  12. map的基本操作总结C++
  13. JS实现PDF合并功能
  14. 浏览器的“心脏”——内核
  15. kindle看pdf乱码_kindle阅读pdf中文乱码解决
  16. 天行健,君子以自强不息;地势坤,君子以厚德载物的解释
  17. arcengine shp数据导入gdb中
  18. 第十二章:email-mailbox:管理email归档-imaplib:IMAP4客户库-邮箱状态
  19. python编写程序公式计算s_PYTHON程序设计实验2
  20. 【Prometheus】Alertmanager告警全方位讲解

热门文章

  1. python统计字母在字符串中出现的次数
  2. ElasticSearch解决去重精度问题 scripted_metric 去重计数脚本计算高效率解决问题(二)
  3. PAT乙级 1009 说反话
  4. Spring Boot(五十六):基于Redis的搜索栏热搜功能
  5. AI+遥感智能解译,赋能智慧城市规划革新
  6. Linux 指令——文件和用户管理以及用户权限
  7. 基于uinput 实现远程键鼠
  8. 【报告分享】2021年中国品牌授权行业发展白皮书-中国玩协品牌授权专委会(附下载)
  9. mysql启动错误10061
  10. python opencv入门 BRIEF算法(36)