前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >架构师带你玩转分布式锁

架构师带你玩转分布式锁

作者头像
CSDN技术头条
发布2019-11-19 15:38:14
2850
发布2019-11-19 15:38:14
举报
文章被收录于专栏:CSDN技术头条CSDN技术头条

作者:奎哥 来源:不止思考公众号

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

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

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

在单机时代,虽然不需要分布式锁,但也面临过类似的问题,只不过在单机的情况下,如果有多个线程要同时访问某个共享资源的时候,我们可以采用线程间加锁的机制,即当某个线程获取到这个资源后,就立即对这个资源进行加锁,当使用完资源之后,再解锁,其它线程就可以接着使用了。例如,在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 来实现加锁的,例如:

代码语言:javascript
复制
//锁定的方法-伪代码
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 字段要加索引)

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

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

2. 基于 Redis 实现

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

代码语言:javascript
复制
SET user_key user_value NX PX 100

redis 从 2.6.12 版本开始,SET 命令才支持这些参数: NX:只在在键不存在时,才对键进行设置操作,SET key value NX 效果等同于 SETNX key value

PX millisecond:设置键的过期时间为 millisecond 毫秒,当超过这个时间后,设置的键会自动失效

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

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

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

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

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2019-01-17,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 GitChat精品课 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、我们为什么需要分布式锁?
  • 二、分布式锁的实现方式有哪些?
相关产品与服务
云数据库 Redis
腾讯云数据库 Redis(TencentDB for Redis)是腾讯云打造的兼容 Redis 协议的缓存和存储服务。丰富的数据结构能帮助您完成不同类型的业务场景开发。支持主从热备,提供自动容灾切换、数据备份、故障迁移、实例监控、在线扩容、数据回档等全套的数据库服务。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档