看完这篇分布式锁,再也不迷惑了

分布式锁

一.分布式锁概述

1.分布式锁是什么

分布式锁,即分布式系统中的锁。在单体应用中我们通过锁解决的是控制共享资源访问的问题,而分布式锁,就是解决了分布式系统中控制共享资源访问的问题。与单体应用不同的是,分布式系统中竞争共享资源的最小粒度从线程升级成了进程。

分布式锁其实就是,控制分布式系统不同进程共同访问共享资源的一种锁的实现。如果不同的系统或同一个系统的不同主机之间共享了某个临界资源,往往需要互斥来防止彼此干扰,以保证一致性。

2.为什么需要分布式锁

传统单体应用下我们使用JDK自带的jvm锁(synchronized或Lock)就可以解决并发问题,但是在分布式环境下,每个服务器都有一块自己的jvm,这台服务器的jvm无法访问到其他机器jvm中的资源,所以需要一个能够跨jvm的第三方锁来解决分布式下的并发问题,这个第三方锁就是分布式锁。

4.分布式锁的主要作用

  1. 保证数据的正确性:
    比如:秒杀的时候防止商品超卖,表单重复提交,接口幂等性。
  2. 避免重复处理数据:
    比如:定时任务在多台机器重复执行,缓存过期所有请求都去加载数据库。

5.分布式锁应该具备哪些条件

  • 在分布式系统环境下,一个方法在同一时间只能被一个服务器的一个线程执行
  • 高可用的获取锁与释放锁
  • 高性能的获取锁与释放锁
  • 具备可重入特性(可理解为重新进入,由多于一个任务并发使用,而不必担心数据错误)
  • 具备锁失效机制,即自动解锁,防止死锁
  • 具备非阻塞锁特性,即没有获取到锁将直接返回获取锁失败

6.分布式锁的实现方式

分布式锁主要有三种方式,数据库锁,Redis锁,Zookper锁。

二.数据库分布式锁(以Mysql为例)

Mysql分布式锁有三种,锁表、乐观锁、悲观锁,锁表对整个所有的方法都生效,也是最mysql分布式锁中常用的,而乐观锁和悲观锁一般是自定义使用,用的比较少,了解为主。

1.锁表(基于数据库表增删)

基于数据库表增删是最简单的方式,首先创建一张锁的表主要包含下列字段:类的全路径名+方法名,时间戳等字段。

具体的使用方式:当需要锁住某个方法时,往该表中插入一条相关的记录。类的全路径名+方法名是有唯一性约束的,如果有多个请求同时提交到数据库的话,数据库会保证只有一个操作可以成功,那么我们就认为操作成功的那个线程获得了该方法的锁,可以执行方法体内容。执行完毕之后,需要delete该记录。

2.悲观锁

插入或更新操作前使用 select … 主键 for update 来实现分布式锁,操作前先查询并加上行锁或者表锁,其他表如果操作主键相同的记录则会被拒绝。

3.乐观锁

给数据库表加一个version字段,插入或更新前先查询一下version字段,最终执行插入或更新时在查询一下version字段,如果相同说明只有本机操作了这条记录,否则就放弃操作,比悲观锁效率高,但是重试可能会多次失败。

4.Mysql优缺点分析

优点:使用简单,容易理解

缺点:

1.Mysql的操作基于磁盘IO,大量的插入和删除情况下并发性能较低。

2.没有过期时间,一旦删除失败可能会造成死锁,解决方案是使用定时任务定时清理过期未删除的锁,但仍会有延迟。

3.使用数据库的行级锁并不一定靠谱,尤其是当我们的锁表并不大的时候。

三.Redis分布式锁

1.使用setnx和expire命令

// 1. 获取锁
redis.setnx('resource_name1', 'owner1')
// 2. 增加锁过期时间为6s
redis.exprire('resource_name1', 6, TimeUnit.SECONDS)

缺点:setnxexprire两条命令不是原子的,可能setnx获取锁之后还没来得及设置过期时间机器就宕机了。

2.使用Redis 2.6.12之后提供的一条复合命令

redis.set('resource_name1', 'owner1',"NX" "EX", 6)

缺点:这样虽然解决了上面说的问题,但是又会引入新的问题。

假如服务A加锁成功,锁会在10s后自动释放,但由于业务复杂,执行时间过长,10s内还没执行完,此时锁已经被redis自动释放掉了。此时服务B就重新获取到了该锁,服务B开始执行他的业务,服务A在执行到第12s的时候执行完了,那么服务A会去释放锁,则此时释放的却是服务B刚获取到的锁。

这会有锁过期和释放其他服务锁这种严重的问题。

3.使用Redission(看门狗)

采用了「自动续期」的方案来避免锁过期,这个守护线程我们一般也把它叫做「看门狗」线程,在redis锁快要过期的时候给其延长时间。

缺点:如果机器宕机了,看门狗也就没了。此时就不会延长key的过期时间,到了30s之后就会自动过期了,其他线程可以获取到锁)

4.解决释放其他服务锁问题

每个服务在设置value的时候,带上自己服务的唯一标识,如UUID,或者一些业务上的独特标识。这样在删除key的时候,只删除自己服务之前添加的key就可以了。

如果需要先查看锁是否是自己服务添加的,需要先get取出来判断,然后再进行del。这样的话就无法保证原子性了。

我们可以通过Lua脚本,将这两个操作合并成一个操作,就可以保证其原子性了。

Lua脚本的话,我也不会,用到的时候百度就完了。

如果是在单Redis实例的情况下,上面的已经完全实现了分布式锁的功能了。

缺点:如果Redis宕机了,以上都是白搭,就得搭建Redis集群,而这就会有新的问题出现,假设是主从集群,且主从数据并不是强一致性。当主节点宕机后,主节点的数据还未来得及同步到从节点,进行主从切换后,新的主节点并没有老的主节点的全部数据,这就会导致刚写入到老的主节点的锁在新的主节点并没有,其他服务来获取锁时还是会加锁成功。此时则会有2个服务都可以操作公共资源,此时的分布式锁则是不安全的。Redis的作者也想到这个问题,于是他发明RedLock。

5.使用RedLock

RedLock原理如下:采用Redis奇数集群(以3为例),获取的时候对每个Redis节点轮流获取,如果获取成功数量超过一半(2或者3)就认为获取锁成功了.

如果发生了获取锁成功后Redis节点宕机的情况,虽然剩下的是偶数集群,但是获取锁成功与否仍会按照最初奇数集群计算,其他线程最多只能获取那少于一半的Redis锁,即获取锁失败解决了上述问题。

6.Redis优缺点分析

优点:性能高,使用简单,在允许偶发锁失效的场景下推荐使用

缺点:

  1. 存在机器宕机锁信息丢失的情况,如果引入RedLock和Redis集群,效率又会大大降低,既然用了redis,就是为了提升效率,这又倒回去了。
  2. 通过轮询抢占锁的机制不是很可靠,当某线程占用锁时间较长时可能导致其他线程抢占锁失败。

四.Zookper分布式锁

1.Zookpeer分布式锁原理简介

Zookeeper 是基于临时顺序节点以及 Watcher 监听器机制实现分布式锁的。首先要先创建一个父节点,获取锁的时候,会在父节点下创建一个临时节点,这些节点会按照创建时间进行排序,只有排在最前面的能获取锁和释放锁,这叫顺序,如果机器宕机了,节点就会自动从父结点下断开,并由下一个顶上,这叫临时。

2.Zookpeer优缺点分析

优点:不依靠轮询抢占锁,依靠的是节点间的通信,不存在锁信息丢失的问题,当业务场景对一致性要求比较高是推荐使用。

缺点:Zookeeper需要部署集群,ZooKeeper实现的分布式锁,性能并不太高。虽然也是基于内存,但是每次在创建节点和销毁节点的过程中,都要动态创建、销毁瞬时节点来实现锁功能。而 ZK 中创建和删除节点只能通过 Leader 服务器来执行,然后 Leader 服务器还需要将数据同不到所有的 Follower 机器上,同步之后才返回,这样频繁的网络通信,性能的短板是非常突出的;而 Redis 则是异步复制。

五.总结及应用场景分析

Redis分布式锁不适合用于执行时间较长的业务,因为可能会丢失锁信息,而Zookper可以保证锁信息不会丢失。

一般的分布式业务场景,大多采用Redis单机分布式锁就可以解决并发问题。数据库分布式锁用的少是因为本来数据库就要承担大量请求的交互,为了减少对数据库压力才使用的Redis缓存。而且因为Redis是几乎一定会用到的,再引入Zookper分布式锁需要额外引入Zookper,增加复杂度和维护成本,除非需要保证分布式下的强一致性才会考虑使用Zookper。

  • 2
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值