正文
-
项目地址:https://github.com/YunaiV/ruoyi-vue-pro
-
视频教程:https://doc.iocoder.cn/video/
基于上面本地锁的问题,我们需要一种支持
分布式集群环境
下的锁:查询 DB 时,只有一个线程能访问,其他线程都需要等待第一个线程释放锁资源后,才能继续执行。
生活中的案例
:可以把锁看成房门外的一把
锁
,所有并发线程比作
人
,他们都想进入房间,房间内只能有一个人进入。当有人进入后,将门反锁,其他人必须等待,直到进去的人出来。
我们来看下分布式锁的基本原理,如下图所示:
我们来分析下上图的分布式锁:
-
1.前端将 10W 的高并发请求转发给四个题目微服务。
-
-
3.每个处理请求的线程在执行业务之前,需要先抢占锁。可以理解为“占坑”。
-
4.获取到锁的线程在执行完业务后,释放锁。可以理解为“释放坑位”。
-
-
-
大白话解释:所有请求的线程都去同一个地方
“占坑”
,如果有坑位,就执行业务逻辑,没有坑位,就需要其他线程释放“坑位”。这个坑位是所有线程可见的,可以把这个坑位放到 Redis 缓存或者数据库,这篇讲的就是如何用 Redis 做
“分布式坑位”
。
基于 Spring Cloud Alibaba + Gateway + Nacos + RocketMQ + Vue & Element 实现的后台管理系统 + 用户小程序,支持 RBAC 动态权限、多租户、数据权限、工作流、三方登录、支付、短信、商城等功能
-
项目地址:https://github.com/YunaiV/yudao-cloud
-
视频教程:https://doc.iocoder.cn/video/
Redis 作为一个公共可访问的地方,正好可以作为“占坑”的地方。
用 Redis 实现分布式锁的几种方案,我们都是用 SETNX 命令(设置 key 等于某 value)。只是高阶方案传的参数个数不一样,以及考虑了异常情况。
我们来看下这个命令,
SETNX
是
set If not exist
的简写。意思就是当 key 不存在时,设置 key 的值,存在时,什么都不做。
在 Redis 命令行中是这样执行的:
set NX
我们可以进到 redis 容器中来试下
SETNX
命令。
先进入容器:
docker exec -it redid-cli
然后执行 SETNX 命令:将
wukong
这个 key 对应的 value 设置成
1111
。
set wukong 1111 NX
返回
OK
,表示设置成功。重复执行该命令,返回
nil
表示设置失败。
我们先用 Redis 的 SETNX 命令来实现最简单的分布式锁。
我们来看下流程图:
-
多个并发线程都去 Redis 中申请锁,也就是执行 setnx 命令,假设线程 A 执行成功,说明当前线程 A 获得了。
-
其他线程执行 setnx 命令都会是失败的,所以需要等待线程 A 释放锁。
-
-
其他线程继续抢占锁,也就是执行 setnx 命令。因为线程 A 已经删除了锁,所以又有其他线程可以抢占到锁了。
代码示例如下,Java 中 setnx 命令对应的代码为
setIfAbsent
。
setIfAbsent 方法的第一个参数代表 key,第二个参数代表值。
// 1.先抢占锁
Boolean lock = redisTemplate.opsForValue().setIfAbsent("lock", "123");
if(lock) {
// 2.抢占成功,执行业务
List typeEntityListFromDb = getDataFromDB();
// 3.解锁
redisTemplate.delete("lock");
return typeEntityListFromDb;
} else {
// 4.休眠一段时间
sleep(100);
// 5.抢占失败,等待锁释放
return getTypeEntityListByRedisDistributedLock();
}
一个小问题:那为什么需要休眠一段时间?
因为该程序存在递归调用,可能会导致栈空间溢出。
青铜之所以叫青铜,是因为它是最初级的,肯定会带来很多问题。