架构师带你玩转分布式锁

大多数互联网系统都是分布式部署的,分布式部署确实能带来性能和效率上的提升,但为此,我们就需要多解决一个分布式环境下,数据一致性的问题。

当某个资源在多系统之间,具有共享性的时候,为了保证大家访问这个资源数据是一致的,那么就必须要求在同一时刻只能被一个客户端处理,不能并发的执行,否者就会出现同一时刻有人写有人读,大家访问到的数据就不一致了。

一、我们为什么需要分布式锁?

在单机时代,虽然不需要分布式锁,但也面临过类似的问题,只不过在单机的情况下,如果有多个线程要同时访问某个共享资源的时候,我们可以采用线程间加锁的机制,即当某个线程获取到这个资源后,就立即对这个资源进行加锁,当使用完资源之后,再解锁,其它线程就可以接着使用了。例如,在JAVA中,甚至专门提供了一些处理锁机制的一些API(synchronize/Lock等)。

但是到了分布式系统的时代,这种线程之间的锁机制,就没作用了,系统可能会有多份并且部署在不同的机器上,这些资源已经不是在线程之间共享了,而是属于进程之间共享的资源。

因此,为了解决这个问题,我们就必须引入「分布式锁」。

分布式锁,是指在分布式的部署环境下,通过锁机制来让多客户端互斥的对共享资源进行访问。

分布式锁要满足哪些要求呢?

  • 排他性:在同一时间只会有一个客户端能获取到锁,其它客户端无法同时获取
  • 避免死锁:这把锁在一段有限的时间之后,一定会被释放(正常释放或异常释放)
  • 高可用:获取或释放锁的机制必须高可用且性能佳

讲完了背景和理论,那我们接下来再看一下分布式锁的具体分类和实际运用。

二、分布式锁的实现方式有哪些?

目前主流的有三种,从实现的复杂度上来看,从上往下难度依次增加:

  • 基于数据库实现
  • 基于Redis实现
  • 基于ZooKeeper实现

无论哪种方式,其实都不完美,依旧要根据咱们业务的实际场景来选择。

  1. 基于数据库实现: 
    基于数据库来做分布式锁的话,通常有两种做法:

    • 基于数据库的乐观锁
    • 基于数据库的悲观锁

我们先来看一下如何基于「乐观锁」来实现:

乐观锁机制其实就是在数据库表中引入一个版本号(version)字段来实现的。 
当我们要从数据库中读取数据的时候,同时把这个version字段也读出来,如果要对读出来的数据进行更新后写回数据库,则需要将version加1,同时将新的数据与新的version更新到数据表中,且必须在更新的时候同时检查目前数据库里version值是不是之前的那个version,如果是,则正常更新。如果不是,则更新失败,说明在这个过程中有其它的进程去更新过数据了。

下面找图举例,


(图片来源网络)

如图,假设同一个账户,用户A和用户B都要去进行取款操作,账户的原始余额是2000,用户A要去取1500,用户B要去取1000,如果没有锁机制的话,在并发的情况下,可能会出现余额同时被扣1500和1000,导致最终余额的不正确甚至是负数。但如果这里用到乐观锁机制,当两个用户去数据库中读取余额的时候,除了读取到2000余额以外,还读取了当前的版本号version=1,等用户A或用户B去修改数据库余额的时候,无论谁先操作,都会将版本号加1,即version=2,那么另外一个用户去更新的时候就发现版本号不对,已经变成2了,不是当初读出来时候的1,那么本次更新失败,就得重新去读取最新的数据库余额。

通过上面这个例子可以看出来,使用「乐观锁」机制,必须得满足: 
(1)锁服务要有递增的版本号version 
(2)每次更新数据的时候都必须先判断版本号对不对,然后再写入新的版本号

我们再来看一下如何基于「悲观锁」来实现:

悲观锁也叫作排它锁,在Mysql中是基于 for update 来实现加锁的,例如:

//锁定的方法-伪代码

public boolean lock(){

connection.setAutoCommit(false)

for(){

result = select * from user where id = 100 for update;

if(result){

//结果不为空,则说明获取到了锁

return true;

}

//没有获取到锁,继续获取

sleep(1000);

}

return false;

}

//释放锁-伪代码

connection.commit();

上面的示例中,user表中,id是主键,通过 for update 操作,数据库在查询的时候就会给这条记录加上排它锁。 
(需要注意的是,在InnoDB中只有字段加了索引的,才会是行级锁,否者是表级锁,所以这个id字段要加索引)

当这条记录加上排它锁之后,其它线程是无法操作这条记录的。

那么,这样的话,我们就可以认为获得了排它锁的这个线程是拥有了分布式锁,然后就可以执行我们想要做的业务逻辑,当逻辑完成之后,再调用上述释放锁的语句即可。

  1. 基于Redis实现

基于Redis实现的锁机制,主要是依赖redis自身的原子操作,例如:

SET user_key user_value NX PX 10000

redis从2.6.12版本开始,SET命令才支持这些参数: 
NX:只在在键不存在时,才对键进行设置操作,SET key value NX 效果等同于 SETNX key value 
PX millisecond:设置键的过期时间为millisecond毫秒,当超过这个时间后,设置的键会自动失效

上述代码示例是指, 
当redis中不存在user_key这个键的时候,才会去设置一个user_key键,并且给这个键的值设置为 user_value,且这个键的存活时间为10000ms

为什么这个命令可以帮我们实现锁机制呢? 
因为这个命令是只有在某个key不存在的时候,才会执行成功。那么当多个进程同时并发的去设置同一个key的时候,就永远只会有一个进程成功。 
当某个进程设置成功之后,就可以去执行业务逻辑了,等业务逻辑执行完毕之后,再去进行解锁。

解锁很简单,只需要删除这个key就可以了,不过删除之前需要判断,这个key对应的value是当初自己设置的那个。

另外,针对redis集群模式的分布式锁,可以采用redis的Redlock机制。

  1. 基于ZooKeeper实现

其实基于ZooKeeper,就是使用它的临时有序节点来实现的分布式锁。

原理就是:当某客户端要进行逻辑的加锁时,就在zookeeper上的某个指定节点的目录下,去生成一个唯一的临时有序节点, 然后判断自己是否是这些有序节点中序号最小的一个,如果是,则算是获取了锁。如果不是,则说明没有获取到锁,那么就需要在序列中找到比自己小的那个节点,并对其调用exist()方法,对其注册事件监听,当监听到这个节点被删除了,那就再去判断一次自己当初创建的节点是否变成了序列中最小的。如果是,则获取锁,如果不是,则重复上述步骤。

当释放锁的时候,只需将这个临时节点删除即可。


(图片来自网络)

如图,locker是一个持久节点,node_1/node_2/…/node_n 就是上面说的临时节点,由客户端client去创建的。 
client_1/client_2/…/clien_n 都是想去获取锁的客户端。以client_1为例,它想去获取分布式锁,则需要跑到locker下面去创建临时节点(假如是node_1)创建完毕后,看一下自己的节点序号是否是locker下面最小的,如果是,则获取了锁。如果不是,则去找到比自己小的那个节点(假如是node_2),找到后,就监听node_2,直到node_2被删除,那么就开始再次判断自己的node_1是不是序列中最小的,如果是,则获取锁,如果还不是,则继续找一下一个节点。

以上,就讲完了为什么我们需要分布式锁这个技术,以及分布式锁中常见的三种机制,欢迎大家一起交流。

本文原创发布于微信公众号「 不止思考 」,欢迎关注,交流互联网认知、项目管理、大数据、Web、区块链技术。

原文地址:https://www.cnblogs.com/jsjwk/p/9529003.html

时间: 2024-11-08 20:22:43

架构师带你玩转分布式锁的相关文章

蚂蚁金服架构师带你深入性能优化一MySql性能优化实战

概要: Mysql的优化,大体可以分为三部分:索引的优化,sql语句的优化,表的优化.本文主要帮助自己整理思路,也可作为一个学习MySQL优化的提纲. 索引的优化 只要列中含有NULL值,就最好不要在此例设置索引,复合索引如果有NULL值,此列在使用时也不会使用索引 尽量使用短索引,如果可以,应该制定一个前缀长度 对于经常在where子句使用的列,最好设置索引,这样会加快查找速度 对于有多个列where或者order by子句的,应该建立复合索引 对于like语句,以%或者'-'开头的不会使用索

阿里P8架构师告诉你什么是分布式架构

一.前言 我们都知道,当今无论在BAT这样的大公司,还是各种各样的小公司,甚至是传统行业刚转互联网的企业都开始使用分布式架构,那么什么叫分布式架构呢?分布式架构有什么好处呢?分布式架构经过了怎样的发展呢?是哪家企业开启了分布式架构的时代呢?读完本文,你就会得到这些答案,下面让我们一起来开启分布式概述的奇妙之旅吧! 二.分布式架构的发展历史 1946年2.14日,那是一个浪漫的情人节 , 世界上第一台电子数字计算机在美国宾夕法尼亚大学诞生了,她的名字叫ENIAC.这台计算机占地170平米.重达 3

java架构师,高并发,分布式,集群,大型高并发电商项目实战视频教程

15套java架构师.集群.高可用.高可扩展.高性能.高并发.性能优化.Spring boot.Redis.ActiveMQ.Nginx.Mycat.Netty.Jvm大型分布式项目实战视频教程 视频课程内容包含: 高级Java架构师包含:Spring boot.Spring  cloud.Dubbo.Redis.ActiveMQ.Nginx.Mycat.Spring.MongoDB.ZeroMQ.Git.Nosql.Jvm.Mecached.Netty.Nio.Mina.性能调优.高并发.to

阿里P8架构专家带你透析分布式架构

什么是分布式系统 分布式系统是由一组通过网络进行通信.为了完成共同的任务而协调工作的计算机节点组成的系统.分布式系统的出现是为了用廉价的.普通的机器完成单个计算机无法完成的计算.存储任务.其目的是利用更多的机器,处理更多的数据. 首先需要明确的是,只有当单个节点的处理能力无法满足日益增长的计算.存储任务的时候,且硬件的提升(加内存.加磁盘.使用更好的CPU)高昂到得不偿失的时候,应用程序也不能进一步优化的时候,我们才需要考虑分布式系统.因为,分布式系统要解决的问题本身就是和单机系统一样的,而由于

万丈高楼平地起:阿里架构师带你吃透不一样的Redis核心原理实战

前言 随着互联网科技的不断发展,我们以前单纯直接操作数据库的方式已经不能满足现有的高性能和高并发的需求了,于是缓存技术应用而生. Redis是互联网技术领域使用最为广泛的存储中间件,它是「Remote DictionaryService」的首字母缩写,也就是「远程字典服务」.Redis 以其超高的性能.完美的文档.简洁易懂的源码和丰富的客户端库支持在开源中间件领域广受好评.国内外很多大型互联网公司都在使用 Redis,比如 Twitter.YouPorn.暴雪娱乐.Github.StackOve

P7架构师带你构建高可用ZooKeeper集群

前言: ZooKeeper 是 Apache 的一个顶级项目,为分布式应用提供高效.高可用的分布式协调服务,提供了诸如数据发布/订阅.负载均衡.命名服务.分布式协调/通知和分布式锁等分布式基础服务.由于 ZooKeeper 便捷的使用方式.卓越的性能和良好的稳定性,被广泛地应用于诸如 Hadoop.HBase.Kafka 和 Dubbo 等大型分布式系统中. 本文的目标读者是对 ZooKeeper 有一定了解的技术人员,将从 ZooKeeper 运行模式.集群组成.容灾和水平扩容四方面逐步深入,

携程系统架构师带你手写spring mvc,解读spring核心源码!

讲师简介: James老师 系统架构师.项目经理 十余年Java经验,曾就职于携程.人人网等一线互联网公司,专注于java领域,精通软件架构设计,对于高并发.高性能服务有深刻的见解, 在服务化基础架构和微服务技术有大量的建设和设计经验. 课程内容: 1.为什么读Spring源码? 如果你是一名JAVA开发人员,你一定用过Spring Framework. 作为一款非常经典的开源框架,从2004年发布的1.0版本到现在的5.0版本,经历了14年的洗礼, 持久不衰 与其说现在是JAVA的天下, 不如

阿里P8高级架构师带你领略阿里巴巴微服务架构——最后有惊喜哦

Dubbo微服务框架的核心功能 启动时检查 ?Dubbo 缺省会在启动时检查依赖的服务是否可用,不可用时会抛出异常,阻止 Spring 初始化完成,以便上线时,能及早发现问题,默认 check="true" 集群容错 failover 失败自动切换,当出现失败重试其它服务器.通常用于读操作,重试带来更长延迟. failfast快速失败,只发起一次调用,失败立即报错.通常用于非幂等性写操作,如新增记录. failsafe失败安全,出现异常时,直接忽略.通常用于写入审计日志等操作. fai

八年一线架构师,带你0基础入门大数据

在职八年老司机带你0基础入门大数据 ,教你如何从小白变成行业精英 ,让高薪变的简单! 孙老师太阁孙老师具备8年从业经验,4年大数据经验,4年培训讲师经验,精通java python 和大数据生态圈,曾担任清华大学JAVA技术研究与开发联合实验室研究员,设计过滴滴大数据架构,以及国家级项目,对于数据的处理和分析有独到的见解,对于教学能够如浅入深,有丰富的软件设计,软件研发,软件管理,流程控制经验点击进入课程 官方网址:www.tigerlab.net太阁博客:blog.tigerlab.net官方