redis分布式锁原理

4 篇文章 0 订阅

什么是分布式锁?

单体系统中,在高并发的场景下多线程访问共享资源时,我们通过都会加锁方式来保证共享资源并发访问的安全性,确保同一时刻只能有一个线程对共享变量进行操作,通常我们都会使用java提供的synchronizedy以及reentrantlock等锁。
随着业务的不断发展,单体应用会集群部署多个实例或拆分为微服务,每个微服务部署到多个实例,高并发下请求就会在不同的实例中处理共享资源,原来单体应用中JVM级别的加锁方式在分布式场景下不能满足共享资源的并发访问要求。因此分布式锁就随之诞生。

分布式锁 是分布式环境下对共享资源并发控制的一种机制,控制某个共享资源同意时刻只能被一个进程应用所使用。
在这里插入图片描述

根据上图简单分析一下分布式锁的工程流程

  1. 假设高并发场景同一时刻有三个相同的请求分别转发到服务1服务2服务3,由线程1线程2线程3进行处理
  2. 在执行业务前,每个请求都会尝试获取独占锁
  3. 线程1成功获取到锁后,其它线程都会处于阻塞状态,等待锁释放
  4. 线程1释放锁后,其它线程会继续抢占锁
  5. 重复执行最后三步逻辑

分布式锁的核心是实现多进程之间互斥,而满足这一点的方式有很多,常见的有三种:

MySQLRedisZookeeper
互斥利用mysql本身的互斥锁机制setnx命令利用节点的唯一性和有序性实现互斥
高可用
高性能一般一般
安全性断开连接,自动释放锁利用锁超时时间,到期释放临时节点,断开连接自动释放

下面主要介绍的是使用redis实现分布式锁

redis分布式锁的演进过程

简单方案

使用 Redis 的 setnx 命令来实现简单的分布式锁。
命令格式:setnx key value
命令解释:当前key不存在时,key的值设为value,返回1。若key已经存在时,不做任何操作,返回0。
用法:可以将setnx用于加锁,如果 setnx 返回 1 ,说明客户端已经获得了锁,并且可以指定锁的有效时间。如果 setnx返回 0 ,说明key已经被其他客户端上锁了

  • 原理图

在这里插入图片描述

执行流程分析:

  • 高并发场景下多线程获取redis锁,也就是执行setnx命令,假设线程A执行命令成功,则会获取到锁并设置锁的过期时间(获取锁并设置锁过期时间必须为原子操作)。设置锁的超时时间目的是:当setnx占锁成功之后,业务代码或服务器宕机,没有执行删除锁的逻辑,则会造成死锁
    获取锁并设置锁过期时间实例代码:

在这里插入图片描述

  • 其他线程都会获取锁失败,并处于阻塞状态,等待A线程释放锁。
  • 线程 A 执行完自己的业务后,释放锁。
  • 其他线程则继续抢占redis锁。

方案缺陷:假设A线程获取到锁,由于某种原因导致 执行业务所需要的时间 大于 锁的过期时间,所以在锁自动释放后可能A线程业务逻辑还没执行完,此时线程B就已经获取到锁,等线程A执行完业务后释放了B线程的锁。

唯一锁标识方案

唯一锁标识方案:加锁时设置value为唯一标识(uuid),释放锁时需要判断value是否能够对应上uuid,如果相等则说明是当前线程加的锁可以释放。

示例代码:

在这里插入图片描述

方案缺陷:获取锁和释放锁不是原子操作,最终也会导致释放其他线程的锁
在这里插入图片描述

  • 线程1获取锁执行业务,获取锁标识并判断是否一致。
  • 由于某些原因(Full GC)导致程序执行阻塞,并且锁到了过期时间自动释放。
  • 线程2获取到锁。
  • 这时,线程1不再阻塞恢复执行状态,线程1拿到的还是自己的锁标识,并执行删除锁逻辑(删除了线程2的锁)。
最终方案

在唯一锁标识方案上,使用Lua脚本保证获取锁和释放锁这两个步骤为原子操作则可作为最终方案(Lua脚本功能:在一个脚本中编写多条Redis命令,确保多条命令执行时的原子性)。


使用setnx实现的分布式锁存在下面的问题
  • 不可重入:同一个线程无法多次获取同一把锁
  • 不可重试:获取锁只尝试一次就返回false,没有重试机制
  • 超时释放:锁超时释放虽然可以避免死锁,但如果是业务执行耗时较长,也会导致锁释放,存在安全隐患

Redssion

Redisson是一个在Redis的基础上实现的Java驻内存数据网格。它不仅提供了一系列的分布式的Java常用对象,还提供了许多分布式服务,其中就包含了各种完善的分布式锁实现。

redssion分布式锁使用redis的hash结构实现,key:分布式锁名称 field:线程id value:可重入次数

Redssion分布式锁实现原理
在这里插入图片描述
注:ttl为锁的最大等待时间(等待期间会重试),leaseTime为锁自动释放时间,-1是没有设置

Redisson解决了使用setnx实现的分布式锁的弊端

  • 可重入:利用redis的hash结构记录线程id和可重试次数(和Reentrantlock的可重入实现原理类似)。
  • 重试机制:获取锁失败并且等待锁时间没有过期会订阅释放锁的信号量,当锁被释放时通过PubSub发送信号量,线程会再次尝试获取锁(重试),如果超过等待锁时间会获取锁失败。
  • 超时自动续期:watchDog机制,当没有设置锁超时时间时,锁超时时间默认为30s,当线程获取到锁时会启动一个定时器,定时器的执行时间为(锁超时时间 / 3),相当于10s会重置一下超时时间(递归重置),这就是一个超时自动续期的过程。
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值