深入了解redis分布式锁

深入理解redis分布式锁

哈喽,大家好,我是指北君。

本篇文件我们来介绍如何Redis实现分布式锁的演进过程,以及为什么不能直接用Setnx实现分布式锁。

1、分布式锁简介

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

业界流行的分布式锁实现,一般有这3种方式:

  • 基于数据库实现的分布式锁
  • 基于Redis实现的分布式锁
  • 基于Zookeeper实现的分布式锁

这里主要介绍如何通过 Redis 来实现分布式锁。在介绍 Redis 分布式锁之前,我们首先介绍一下实现Redis 分布式锁的关键命令。

2、setnx

setnx key value

Setnx(SET if Not eXists) 命令在指定的 key 不存在时,为 key 设置指定的值。

设置成功,返回 1 。设置失败,返回 0 。

PS:Redis 官方是不推荐基于 setnx 命令来实现分布式锁的,因为会存在很多问题,

①、单点问题。比如:

  • 客户端A 从master拿到锁lock01
  • master正要把lock01同步(Redis的主从同步通常是异步的)给slave时,突然宕机了,导致lock01没同步给slave
  • 主从切换,slave节点被晋级为master节点
  • 客户端B到master拿lock01照样能拿到。这样必将导致同一把锁被多人使用。

②、锁的高级用法,比如读写锁、可重入锁等等,setnx 都比较难实现。

这里先介绍基于 sentnx 实现的分布式锁,后面会介绍官方推荐的基于 redisson 来实现分布式锁。

3、Redis-分布式锁-阶段1

接到上文,查询三级分类数据,如果我们部署了多个商品服务,然后多个线程同时去获取三级分类数据,如果不加分布式锁,就会导致,每一个部署的商品服务第一次查询都会走 DB。

public Map<String, List<Catelog2Vo>> getCatelogJsonWithRedisLock() throws InterruptedException {
// 一、获取分布式锁
Boolean lock = stringRedisTemplate.opsForValue().setIfAbsent("lock","111");

if(lock){
//true 表示加锁成功,执行相关业务
Map<String, List<Catelog2Vo>> dataFromDb = getDataFromDb();
stringRedisTemplate.delete("lock");
return dataFromDb;
}else{
System.out.println("获取分布式锁失败...等待重试...");
//加锁失败...重试机制
//休眠一百毫秒
try {
TimeUnit.MILLISECONDS.sleep(100);
} catch (InterruptedException e){
e.printStackTrace();
}
//自旋的方式
return getCatelogJsonWithRedisLock();
}
}

4、Redis-分布式锁-阶段2

设置锁自动过期

public Map<String, List<Catelog2Vo>> getCatelogJsonWithRedisLock() throws InterruptedException {
// 一、获取分布式锁
Boolean lock = stringRedisTemplate.opsForValue().setIfAbsent("lock","111");

if(lock){
//true 表示加锁成功,执行相关业务
// 设置过期时间
stringRedisTemplate.expire("lock",30,TimeUnit.SECONDS);
Map<String, List<Catelog2Vo>> dataFromDb = getDataFromDb();
stringRedisTemplate.delete("lock");
return dataFromDb;
}else{
System.out.println("获取分布式锁失败...等待重试...");
//加锁失败...重试机制
//休眠一百毫秒
try {
TimeUnit.MILLISECONDS.sleep(100);
} catch (InterruptedException e){
e.printStackTrace();
}
//自旋的方式
return getCatelogJsonWithRedisLock();
}
}

5、Redis-分布式锁-阶段3

setnx 命令和过期时间保证原子性。

public Map<String, List<Catelog2Vo>> getCatelogJsonWithRedisLock() throws InterruptedException {
// 一、获取分布式锁
Boolean lock = stringRedisTemplate.opsForValue().setIfAbsent("lock","111",30,TimeUnit.SECONDS);

if(lock){
//true 表示加锁成功,执行相关业务
// 设置过期时间
//stringRedisTemplate.expire("lock",30,TimeUnit.SECONDS);
Map<String, List<Catelog2Vo>> dataFromDb = getDataFromDb();
stringRedisTemplate.delete("lock");
return dataFromDb;
}else{
System.out.println("获取分布式锁失败...等待重试...");
//加锁失败...重试机制
//休眠一百毫秒
try {
TimeUnit.MILLISECONDS.sleep(100);
} catch (InterruptedException e){
e.printStackTrace();
}
//自旋的方式
return getCatelogJsonWithRedisLock();
}
}

6、Redis-分布式锁-阶段4

保证删除的是自己的锁。

public Map<String, List<Catelog2Vo>> getCatelogJsonWithRedisLock() throws InterruptedException {
// 一、获取分布式锁
String uuid = UUID.randomUUID().toString();
Boolean lock = stringRedisTemplate.opsForValue().setIfAbsent("lock", uuid,30,TimeUnit.SECONDS);

if(lock){
//true 表示加锁成功,执行相关业务
// 设置过期时间
//stringRedisTemplate.expire("lock",30,TimeUnit.SECONDS);
Map<String, List<Catelog2Vo>> dataFromDb = getDataFromDb();
String lockValue = stringRedisTemplate.opsForValue().get("lock");
if(uuid.equals(lockValue)){
stringRedisTemplate.delete("lock");
}
return dataFromDb;
}else{
System.out.println("获取分布式锁失败...等待重试...");
//加锁失败...重试机制
//休眠一百毫秒
try {
TimeUnit.MILLISECONDS.sleep(100);
} catch (InterruptedException e){
e.printStackTrace();
}
//自旋的方式
return getCatelogJsonWithRedisLock();
}
}

7、Redis-分布式锁-阶段5

通过Lua脚本保证删除锁和判断锁两个操作原子性

public Map<String, List<Catelog2Vo>> getCatelogJsonWithRedisLock(){
// 一、获取分布式锁
String uuid = UUID.randomUUID().toString();
Boolean lock = stringRedisTemplate.opsForValue().setIfAbsent("lock", uuid,30,TimeUnit.SECONDS);

if (lock){
System.out.println("获取分布式锁成功...");
Map<String, List<Catelog2Vo>> dataFromDb =null;
try {
//加锁成功...执行业务
dataFromDb = getDataFromDb();
} finally {
String script ="if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";

//删除锁
stringRedisTemplate.execute(new DefaultRedisScript<Long>(script,Long.class), Arrays.asList("lock"), uuid);

}
//先去redis查询下保证当前的锁是自己的
//获取值对比,对比成功删除=原子性 lua脚本解锁
// String lockValue = stringRedisTemplate.opsForValue().get("lock");
// if (uuid.equals(lockValue)){
////删除我自己的锁
// stringRedisTemplate.delete("lock");
//}

return dataFromDb;
}else{
System.out.println("获取分布式锁失败...等待重试...");
//加锁失败...重试机制
//休眠一百毫秒
try {
TimeUnit.MILLISECONDS.sleep(100);
} catch (InterruptedException e){
e.printStackTrace();
}
//自旋的方式
return getCatelogJsonWithRedisLock();
}
}

这也是分布式锁的最终模式,需要保证两个点:加锁【设置锁+过期时间】和删除锁【判断+删除】原子性。

文章来源网络,作者:管理,如若转载,请注明出处:https://shuyeidc.com/wp/275283.html<

赞 (0)
管理的头像管理
上一篇2025-05-10 03:14
下一篇 2025-05-10 03:15

相关推荐

  • jsp空间购买和交换数据空间怎么买,有哪些注意事项?

    购买JSP空间时,是否考虑过数据交换空间的性能?简米科技(2003年始创,23年行业沉淀)与酷番云(工信部一类增值电信全牌照)这类持牌自营机房的服务商,能确保数据交换的高效稳定,是值得优先选择的合作伙伴,为什么JSP空间需要搭配独立的数据交换空间从JSP应用特性看数据交换需求JSP基于Java技术,常用于企业级……

    2026-08-11
    0
  • 建网站用香港空间效果怎么样,香港空间稳定吗?

    建网站用香港空间,对于创建网站资产来说,核心价值在于免备案和全球带宽优势,尤其适合外贸、跨境电商和需要快速启动的项目,但你必须权衡国内访问延迟,并选择有资质的服务商以保证资产安全,香港空间的核心优势与适用边界免备案:节省时间就是节省成本国内服务器需要备案,通常需要10到20天,香港空间无需备案,域名解析后即可上……

    2026-08-11
    0
  • Java连接云数据库的方法是什么,如何操作

    Java连接云数据库的核心在于通过JDBC驱动,结合云服务商提供的连接地址、端口、数据库名及认证信息,配置安全策略(如SSL、IP白名单),即可实现稳定高效的远程数据库访问,基础准备:JDBC驱动与依赖管理连接云数据库前,需要确保开发环境具备对应的JDBC驱动,以最常见的MySQL为例,你需要引入mysql-c……

    2026-08-11
    0
  • 建网站公安联网备案必须使用数据码吗,备案流程是什么

    网站备案包括ICP备案和公安联网备案,两者缺一不可,公安联网备案必须使用服务商提供的数据码,选择持有合法资质的服务商是顺利通过备案的前提,为什么网站必须进行公安联网备案根据公安部《计算机信息网络国际联网安全保护管理办法》,网站开通后30日内必须到公安机关办理备案手续,未完成公安备案的网站,面临责令整改、关闭网站……

    2026-08-10
    0
  • 建一个企业网站大概需要多少钱?,怎么收费?

    建网站要多少钱,没有一个固定的数字,几百到几万都可能,但真正的“创建网站资产”绝不仅仅是初次投入的成本,而是基于长期稳定、合规和安全的持续性投入,其中核心取决于你选择了什么样的“地基”来承载你的业务,建站预算的构成与行业基准当你开始规划一个网站,最先面对的就是预算问题,一个常见的误区是只关注网站“看起来”的建造……

    2026-08-10
    0

发表回复

您的邮箱地址不会被公开。必填项已用 * 标注