微服务-分布式锁

微服务一般都是分布式部署的,在分布式部署的系统中,如果不同环境下的系统都要访问一个临界资源。为了避免同时对这个资源进行操作,有序的的访问。这时候就需要一个++协调器++,这个协调器就是++分布式锁++


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

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



分布式锁的两种实现方式

1. Redis
加锁

最简单的方法是使用 setnx 命令,是「SETifNoteXists」的缩写。

key 是锁的唯一标识,按业务来决定命名。

比如想要给一种商品的秒杀活动加锁,可以给 key 命名为 “lock_sale_商品ID” 。

而 value 设置成什么呢?我们可以姑且设置成 1。

加锁的伪代码如下:

setnx(lock_sale_商品ID,1)
解锁

有加锁就得有解锁。当得到锁的线程执行完任务,需要释放锁,以便其他线程可以进入。

释放锁的最简单方式是执行 del 指令,伪代码如下:

del(lock_sale_商品ID)
锁超时

锁超时是什么意思呢?如果一个得到锁的线程在执行任务的过程中挂掉,来不及显式地释放锁,这块资源将会永远被锁住(死锁),别的线程再也别想进来。

所以,setnx 的 key 必须设置一个超时时间,以保证即使没有被显式释放,这把锁也要在一定时间后自动释放。setnx 不支持超时参数,所以需要额外的指令,伪代码如下:

expire(lock_sale_商品ID, 30)
存在的问题
  • setnx 和 expire 两个操作非原子性,如果在两个指令之间发生宕机会造成死锁
//set指令增加了可选参数,超时时间
set(lock_sale_商品ID,1,30,NX)
  • del 操作导致误删

A线程在超时之前未完成任务,释放了锁。马上B线程介入同一资源进行操作,操作过程中A线程操作完成任务结束执行del指令释放锁,因为是同一资源,所以A线程删除的B线程的锁


/**
怎么避免这种情况呢?
可以在加锁的时候把当前的线程 ID 当做 value,
并在删除之前验证 key 对应的 value 是不是自己线程的 ID。
*/
加锁
String threadId = Thread.currentThread().getId()
set(key,threadId ,30,NX

解锁
if(threadId .equals(redisClient.get(key))){
    del(key)
}
  • 出现并发的可能性

第二点所描述的场景,虽然我们避免了线程 A 误删掉 key 的情况,但是同一时间有 A,B 两个线程在访问代码块,仍然是不完美的。
怎么办呢?我们可以让获得锁的线程开启一个守护线程,用来给快要过期的锁“续航”。

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容