深入探讨 Redis 分布式锁:避免锁被其他协程误删的策略

大厂面试官问我:Redis的分布式锁怎么解决死锁问题?【后端八股文十:Redis分布式锁八股文合集(2)】 Redisson在Redis的基础上,实现了可重入锁、公平锁、联锁等多种类型的分布式锁。可重入锁通过维护一个持有锁的线程ID和锁的持有次数来实现。公平锁通过维护一个等待队列,按照请求顺序依次获取锁。联锁允许同时获取多个锁,只有全部获取成功才算获取锁成功。 阅读详情

在分布式锁的设计中,"互斥性" 保证了同一时间只有一个节点能操作资源,而 "安全性" 则确保了锁不会被无关节点误删。本文将聚焦于 Redis 分布式锁的安全性问题,深入解析如何通过巧妙设计 value 值和原子操作,防止锁被其他协程或节点误删除。

一、为什么会出现锁被误删的情况?

你的笔记中提到 "过期时间可能导致锁被误删",这是分布式锁实现中一个典型的安全隐患。让我们通过一个具体场景理解这个问题:

  1. 客户端 A 获取锁,设置 value 为 "lock-value-A",过期时间 8 秒
  2. 客户端 A 的业务逻辑执行缓慢,8 秒后锁自动过期释放
  3. 客户端 B 此时成功获取锁,设置 value 为 "lock-value-B"
  4. 客户端 A 的业务逻辑终于执行完成,执行DEL命令释放锁
  5. 结果:客户端 A 删除了客户端 B 持有的锁,导致新的客户端 C 也能获取到锁,引发并发问题

问题的核心在于:释放锁时没有验证锁的归属权。如果只是简单执行DEL命令,任何知道锁 key 的客户端都能删除锁,这显然不符合分布式锁的安全要求。

二、解决方案:用唯一 value 标识锁的归属

解决锁误删问题的关键思路是:给每个锁设置唯一的 value 值,释放锁时先验证 value 是否匹配,只有匹配时才删除锁。

1. 唯一 value 的生成策略

value 需要满足 "全局唯一性",确保不同客户端、不同时间获取的锁具有不同标识。常见的生成方式有:

  • UUID/GUID:通用唯一识别码,如uuid.New().String()
  • 客户端 ID + 时间戳:如"client-123:" + time.Now().UnixNano()
  • 随机数 + 进程 ID:如"pid-456:" + rand.Intn(1000000)

在 Go 语言中生成 UUID 的示例:

import (
    "github.com/google/uuid"
)

// 生成唯一锁值
func generateLockValue() string {
    return uuid.New().String()
}

这个 value 会在获取锁时作为SET命令的参数存入 Redis,成为锁的 "身份证"。

2. 带 value 验证的锁释放流程

释放锁时需要执行两个操作:

  1. 获取锁的当前 value
  2. 比较 value 是否与自己持有的一致,一致则删除

但这两个操作如果分开执行,在高并发场景下仍然可能出现问题(如刚获取到 value,锁就过期被其他客户端获取)。因此必须保证这两个操作的原子性。

Redis 的 Lua 脚本功能正好满足这一需求 ——Redis 会将整个 Lua 脚本作为一个原子操作执行,中间不会被其他命令打断。

释放锁的 Lua 脚本如下:

-- 释放锁的Lua脚本
-- KEYS[1]:锁的key
-- ARGV[1]:当前客户端持有的value
if redis.call('GET', KEYS[1]) == ARGV[1] then
    -- value匹配,删除锁
    return redis.call('DEL', KEYS[1])
else
    -- value不匹配,不做操作
    return 0
end

这个脚本的逻辑很清晰:只有当 Redis 中锁的当前值与客户端持有的值完全一致时,才会执行删除操作,否则直接返回 0(不做任何处理)。

三、完整实现:从获取到释放的全流程

结合前面的知识,我们可以实现一个安全的分布式锁,包含完整的 value 验证机制。

1. Go 语言实现示例

package main

import (
	"context"
	"time"

	"github.com/go-redis/redis/v8"
	"github.com/google/uuid"
)

type RedisLock struct {
	client     *redis.Client
	lockKey    string       // 锁的key
	lockValue  string       // 锁的唯一value
	expireTime time.Duration // 过期时间
}

// 创建新锁实例
func NewRedisLock(client *redis.Client, key string, expireTime time.Duration) *RedisLock {
	return &RedisLock{
		client:    client,
		lockKey:   key,
		expireTime: expireTime,
	}
}

// 获取锁
func (rl *RedisLock) Lock(ctx context.Context) (bool, error) {
	// 生成唯一value
	rl.lockValue = uuid.New().String()
	
	// 执行SET NX命令,同时设置过期时间
	result, err := rl.client.SetNX(
		ctx,
		rl.lockKey,
		rl.lockValue,
		rl.expireTime,
	).Result()
	
	return result, err
}

// 释放锁(带value验证)
func (rl *RedisLock) Unlock(ctx context.Context) (bool, error) {
	// 释放锁的Lua脚本
	script := `
		if redis.call('GET', KEYS[1]) == ARGV[1] then
			return redis.call('DEL', KEYS[1])
		else
			return 0
		end
	`
	
	// 执行Lua脚本
	result, err := rl.client.Eval(
		ctx,
		script,
		[]string{rl.lockKey}, // KEYS参数
		rl.lockValue          // ARGV参数
	).Int64()
	
	if err != nil {
		return false, err
	}
	
	// 返回是否成功释放锁
	return result == 1, nil
}

// 获取当前锁的value(用于调试)
func (rl *RedisLock) GetCurrentValue(ctx context.Context) (string, error) {
	return rl.client.Get(ctx, rl.lockKey).Result()
}

2. 关键实现解析

  • Lock 方法:获取锁时生成唯一 value,并通过SetNX命令原子性地设置 key、value 和过期时间
  • Unlock 方法:使用 Lua 脚本实现 "验证 value + 删除锁" 的原子操作,确保只有锁的持有者才能释放锁
  • value 存储:将 value 保存在 RedisLock 实例中,用于释放锁时的验证

这种实现彻底解决了 "锁被误删" 的问题:即使锁过期后被其他客户端获取,原客户端在释放时会发现 value 不匹配,从而放弃删除操作。

四、进阶思考:value 设计的额外用途

除了防止误删,锁的 value 还可以承载更多信息,提升分布式锁的可观测性和功能性:

1. 携带持有者信息

将客户端 ID、进程 ID 等信息编码到 value 中,便于问题排查:

func generateLockValue(clientID string) string {
    // 格式:客户端ID:进程ID:UUID
    return fmt.Sprintf("%s:%d:%s", clientID, os.Getpid(), uuid.New().String())
}

当出现锁异常时,可以通过GET lockKey命令查看当前持有者信息,快速定位问题节点。

2. 支持可重入锁

可重入锁允许同一客户端多次获取同一把锁(类似 Java 的 ReentrantLock)。实现方式是在 value 中记录获取次数:

// 可重入锁的value格式:"clientID:count"
func (rl *ReentrantLock) Lock(ctx context.Context) (bool, error) {
    // 尝试获取锁
    currentValue, err := rl.client.Get(ctx, rl.lockKey).Result()
    
    if err == redis.Nil {
        // 锁不存在,第一次获取
        return rl.client.SetNX(ctx, rl.lockKey, rl.baseValue+":1", rl.expireTime).Result()
    }
    
    // 检查是否是当前客户端持有锁
    if strings.HasPrefix(currentValue, rl.baseValue) {
        // 解析当前计数并加1
        count, _ := strconv.Atoi(strings.Split(currentValue, ":")[1])
        newCount := count + 1
        // 更新计数(需要保证原子性,可使用Lua脚本)
        return true, rl.client.Set(ctx, rl.lockKey, fmt.Sprintf("%s:%d", rl.baseValue, newCount), rl.expireTime).Err()
    }
    
    // 其他客户端持有锁,获取失败
    return false, nil
}

释放时则递减计数,当计数为 0 时才真正删除锁。

五、总结:分布式锁安全的核心原则

防止锁被其他协程误删,本质上是解决 "锁的归属权验证" 问题。总结几个关键原则:

  1. 唯一标识原则:每个锁必须有唯一的 value,作为持有者的身份凭证
  2. 原子操作原则:释放锁时的 "验证 + 删除" 必须是原子操作,Lua 脚本是最佳选择
  3. 最小权限原则:只有锁的持有者才能释放锁,任何情况下都不允许越权操作

在实际开发中,建议直接使用经过验证的开源库(如 Redisson、go-redsync),它们已经妥善处理了这些安全细节。理解这些原理的价值在于:当遇到锁相关的异常时,能够快速定位问题根源,而不是停留在 "API 调用" 的层面。

分布式锁的安全性是一个细节决定成败的领域,一个小小的疏漏就可能导致整个分布式系统的并发控制失效。通过本文的学习,希望你能深刻理解 value 验证机制的重要性,在设计和使用分布式锁时始终保持对 "安全性" 的敬畏。

作者:Code季风
链接:https://juejin.cn/post/7536319934542381119
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

Redis分布式锁的实现、优化与Redlock算法探讨 要实现分布式锁,首先需要Redis具备“互斥”能力,这可以通过SETNX命令实现。利用这一点,不同客户端就能实现互斥,从而实现一个分布式锁。但是,由于SETNX和EXPIRE是两条命令,无法保证其原子性,因此有可能只执行了SETNX,未执行EXPIRE。为此,可以引入“看门狗”机制,即在加锁时开启一个守护线程,定期检测锁的失效时间,并在锁快要过期时自动续期。通过上述讲解和代码示例,我们可以看到实现一个高效、可靠的分布式锁系统的关键点和步骤,并且通过使用成熟的库如Redisson,能够大大简化这一过程。 阅读详情

相关推荐

## 13|Redis 分布式锁正确打开方式:Python 一致性与容错设计

摘要 本文针对Python+Redis场景下的分布式锁常见问题,系统讲解其实现要点与风险治理方案。内容涵盖锁设计四大约束(唯一令牌、原子释放、超时续约、失败兜底),提供Python实现示例及续约机制,分析失败场景的降级策略。通过实际案例复盘,展示如何解决锁过期导致的超卖问题,并附上锁使用FAQ和上线检查清单,帮助开发者构建高可靠的分布式锁方案。

zhaoyang4298的博客 565

Redis 分布式锁深度解析:如何防止锁被其他协程误删

防止锁被其他协程误删,本质上是解决 "锁的归属权验证" 问题。唯一标识原则:每个锁必须有唯一的 value,作为持有者的身份凭证原子操作原则:释放锁时的 "验证 + 删除" 必须是原子操作,Lua 脚本是最佳选择最小权限原则:只有锁的持有者才能释放锁,任何情况下都不允许越权操作在实际开发中,建议直接使用经过验证的开源库(如 Redisson、go-redsync),它们已经妥善处理了这些安全细节。

Landcc的博客 953

Java 源码剖析(20)--Redis 怎样实现分布式锁

Redis 怎样实现的分布式锁1) 分布式锁1.1) 分布式锁的常见实现方式:2)单机锁3)使用 Redis 实现分布式锁4)锁超时 如果在分布式环境下使用就会出现锁不生效的问题,需要使用分布式锁来解决。 1) 分布式锁 分布式锁是控制分布式系统之间同步访问共享资源的一种方式,是为了解决分布式系统中,不同的系统或是同一个系统的不同主机共享同一个资源的问题,它通常会采用互斥来保证程序的一致性,这就是分布式锁的用途以及执行原理。分布式锁示意图如下图所示: 1.1) 分布式锁的常见实现方式: 基于 MySQL

姜皓的博客 233

解决Redis分布式锁误删问题

解决Redis分布式锁误删问题

m0_61787196的博客 785

Matlab路径文件pathdef.m可能为只读文件修改方法

找到MATLAB目录下的bin这个文件夹,直接改这个文件夹的权限(之前试过改pathdef.m的权限,不好用)如果到这里重新导入还是不行,从官网重新下载需要的安装包,可能是你的安装包出了问题。(Win11系统,matlab版本为2022b)编辑——添加——在输入对象名称来选择中输入everyone。重启matlab,以管理员身份运行。并修改其他已有的权限,都给它。

yqc1123的博客 1万+

避坑指南:RK3588视频编码与SRT推流实战中,我遇到的5个典型问题及解决方案

本文深度解析RK3588平台视频编码与SRT推流实战中的五个典型问题,包括MIPI摄像头内存对齐、多线程数据传递、FFmpeg时间戳设置、SRT连接参数调优及系统资源监控。提供具体解决方案和优化技巧,帮助开发者高效实现实时视频网络传输。

weixin_28718793的博客 325

分布式锁解决因为删锁而产生的两种问题

设置锁过期时间,访问数据库后删除锁。涉及的两种问题都是毫秒级别的问题,为了减轻服务器的压力,一般在大厂里要完善的代码。 而小公司里,则不需要这么麻烦,不设计删除锁,直接等待过期时间过去自动删除锁。这样就省去了因为删除锁产生的一系列问题。 分布式锁的两种问题: 第一种问题:锁过期,删除锁,把别人的删了 第1条线程拿锁进去访问数据库,突然该线程发生了意外,等待时间较长,直到过期时间到了(锁释放,第2条线程拿锁进入访问)!突然:第1条线程复活,访问数据库出来,删除锁(此时把第2条线程的锁给删除了) 请设计方案解决

Hiber12的博客 1415

告别深度学习!用5个麦克风搞定多说话人分离:ReTM算法保姆级教程

本文详细介绍了ReTM算法,这是一种无需深度学习、仅需5个麦克风即可实现多说话人语音分离的创新信号处理方法。它通过估计相对传递矩阵,利用空间物理信息在混响环境中分离语音,具有数据需求低、计算复杂度小、无需先验几何信息等优势,特别适合嵌入式设备和资源受限场景。

desk3的博客 160

redis中的锁,通过UUID防止误删

redis中的锁,通过UUID防止误删

荣山的博客 596

PL/SQL 中的数据导入和导出:CSV 文件格式详解

在 PL/SQL 开发中,数据的导入和导出是常见的操作。本文将深入探讨如何使用 PL/SQL 导入和导出 CSV 文件格式的数据,帮助你轻松地实现数据迁移、备份和数据交换等需求。CSV(Comma-Separated Values)是一种常见的文本文件格式,它以逗号作为字段的分隔符,每行表示一个记录。使用 CSV 格式可以方便地在不同的系统之间共享和传输数据。下面我们将分步介绍如何在 PL/SQL 中导入和导出 CSV 文件格式的数据。

pleaseprintf的博客 8838

海思Hi3516平台集成IMX214 Sensor实战:从I2C调试到VI通路构建

本文详细记录了在海思Hi3516平台上集成索尼IMX214图像传感器的完整实战过程。从硬件I2C通讯调试、驱动层适配,到上层VI通路构建与ISP配置,提供了系统性的排查步骤与代码修改要点,旨在帮助开发者快速打通从Sensor到屏幕的完整视频采集链路,解决集成过程中的常见问题。

tcp8optimizer的博客 597

Redis分布式锁最简单的实现SET lock 1 EX 10 NX。如何避免死锁?锁被别人释放怎么办?

想要实现分布式锁,必须要求 Redis 有「互斥」的能力,我们可以使用 SETNX 命令,这个命令表示SET if Not Exists,即如果 key 不存在,才会设置它的值,否则什么也不做。上面的命令执行时,每个客户端在释放锁时,都是「无脑」操作,并没有检查这把锁是否还「归自己持有」,所以就会发生释放别人锁的风险,这样的解锁流程,很不「严谨」!如何解决这个问题呢?此时,加锁成功的客户端,就可以去操作「共享资源」,例如,修改 MySQL 的某一行数据,或者调用一个 API 请求。怎么解决这个问题呢?

学亮编程手记 789

KEPServerEX实战:Modbus TCP与RTU双协议配置详解

本文详细解析了如何使用KEPServerEX软件同时配置与管理Modbus TCP和RTU混合网络。通过实战步骤,指导用户建立双协议通道、配置设备与标签,并分享核心的地址映射规则与通讯故障排查技巧,帮助工业自动化工程师高效集成多协议设备,简化数据采集架构。

weixin_29186249的博客 399

【Redis】分布式锁,实现分布式锁,设置过期时间,UUID防误删,LUA脚本防误删

@GetMapping("testLock") public void testLock(){ //1获取锁,setne Boolean lock = redisTemplate.opsForValue().setIfAbsent("lock", "111",3,TimeUnit.SECONDS); //2获取锁成功、查询num的值 if(lock){ Object value = redisTemplate.opsForValue().get("...

Java—wang的博客 766

SCI一区论文投稿终极技巧:Expert Systems With Applications;Knowledge-Based Systems

投稿计算机1区论文(如KBS、ESWA)的核心技巧包括:1)关键词选择需平衡范围与相关性,避免过细或过泛;2)研究内容聚焦单一核心创新点,避免内容分散;3)必须设计消融实验量化创新点的贡献;4)公开实验代码和数据集,确保可验证性;5)合理使用AI工具仅辅助写作,核心内容需自主完成;6)实验数据必须真实可靠。特别强调1区期刊对创新性和实验严谨性的高要求,需避免常见拒稿原因如内容分散、缺乏验证等。这些策略可提高投稿成功率,但前提是研究本身具有实质性创新。

ZhangJiqun&191cm&85kg 1161

Isaac Lab & GR00T N1 入门教程(七)人形机器人推理与技能学习策略基准测试

本文介绍了IsaacLab设计的工业机器人操作评估系统,包含两项任务:螺母倾倒(成功率91%)和排气管分拣(成功率95%)。系统支持通过IsaacGR00TN1模型进行策略闭环评估,提供完整的开发环境配置指南(需Ubuntu22.04、CUDA12.8等环境),数据集下载方式(HuggingFace平台托管),以及模型微调流程。评估支持并行化测试(建议10-30个并行环境),并给出详细硬件配置建议(推荐RTX A6000 Ada显卡)。文档还包含常见问题解决方案,特别说明当前暂不支持Blackwell GP

2506_90492529的博客 1119

Redis分布式锁故障处理:当Redis不可用时的应对策略

方案对比表方案可用性一致性复杂度适用场景单节点Redis低强低开发测试环境中强中多数生产环境高弱高大规模分布式系统Redlock极高强极高金融级关键系统本地降级策略高弱中高并发容灾场景决策建议评估业务对一致性的要求等级测试不同方案的故障恢复时间(RTO)监控Redis集群健康状态(使用Prometheus+Grafana)定期进行故障演练(Chaos Engineering)最后提醒:分布式锁没有完美方案,需根据CAP理论进行取舍!

一位努力搬砖程序员。在这里,我会分享关于编程技巧、项目实战经验以及对新技术的见解。希望通过我的文章能够帮助你在技术路上更进一步。期待与你共同学习和成长! 1390

ET2012新版免狗免安装绿色软件

ET2012新版免狗免安祖昂绿色软件。,新界面,新视觉,

Redis分布式锁不止set nx那么简单

Lock4j是一个分布式锁组件,其提供了多种不同的支持以满足不同性能和环境的需求,立志打造一个简单但富有内涵的分布式锁组件。其特性就是简单易用,功能强大,扩展性强,支持redissionzookeeper。可混用,支持扩展。该开源项目来自苞米豆社区,你可能没听过。但是你一定听过,它的作者和Lock4j的作者是同一人。

static_coder的博客 1014

最新的sdio 4.0 spec 协议标准 带书签 高清完整PDF版

Part-E1-SDIO-Specification-Ver4.00-Final-120220.pdf:最新的sdio 4.0 spec 协议标准 带书签 高清完整PDF版,包括SDIO 接口的外设,SDIO 接口官方协议等。

汉书HE8,好用的汇编代码编写工具

汉书HE8,好用的汇编代码编写工具早期编写汇编代码所用功能强大

PHP如何实现可靠的Redis分布式锁?90%的人都忽略了这3个关键步骤

掌握PHP实现基于Redis的分布式锁的关键方法,解决高并发场景下的资源竞争问题。详解SETNX与EXPIRE配合、锁续期机制和异常释放处理三大核心步骤,确保锁的可靠性与系统性能。实用技巧值得收藏。

DevPath的博客 1000

蓝牙模块HM-10手册

蓝牙模块HM-10手册,该手册对蓝牙模块HM-10进行了详细的介绍和使用方法以及一些AT指令。

深度解构分布式锁:从原子性原语到生产级容错方案的Redis实践之路

本文深入剖析了基于Redis实现分布式锁的技术演进路径,详细阐述了从简单的SETNX指令到生产级解决方案的迭代逻辑。文章重点分析了分布式锁面临的核心挑战,包括指令原子性、锁误删风险以及业务执行超时导致的死锁问题,并介绍了如何利用Lua脚本实现安全解锁。此外,文章结合Redisson框架,深入解读了“看门狗(Watchdog)”机制如何通过自动续期解决TTL设置难题,并提供了基于Java的库存扣减实战代码。最后,探讨了主从切换下的锁失效问题及防御性编程策略。

等风来 880

redis实现setnx,setex连用实现分布式锁

redis实现分布式锁 1.主要命令: setnx setex 2.主要问题: 使用redis实现分布式锁,利用上面两个命令的特性。但是最重要的是锁要有过期时间,不然万一服务器宕机或者redis宕机,redis锁将永远得不到释放,出现死锁,其他线程一直获取不到资源。为了避免这种情况发生就必须保证这两个命令setnx与setex(设置过期时间)执行的原子性。 还有一个问题就是如果设置了过期时间,...

weixin_42567141的博客 1万+

[Redis]浅析Redis 分布式锁解决方案

(主要是续约Redisson) 1.锁的设计? 注意互斥性与临界区。 超时释放,避免死锁。 可重入性。一个线程在持有锁的情况可以对其再次请求加锁,防止锁在线程执行完临界区操作之前释放。 2. Redis为什么可以做分布式锁? 利用 Redis 的 SETNX 命令,此命令同样是原子性操作,只有在 key 不存在的情况下,才能 set 成功。而基于 Redis 多机实现的分布式锁Redlock,是Redis 的作者 antirez 为了规范 Redis 分布式锁的实现,提出的一个更安全有效的实现机

新一的技术笔记 367

Redis 分布式锁实战

Redis 分布式锁的核心是基于原子操作保证互斥性,再通过过期时间、锁续约、集群部署等机制解决死锁、单点故障、锁提前释放等问题。手动实现分布式锁需兼顾诸多细节,容易出现漏洞,生产环境优先推荐使用 Redisson 框架,其封装了完整的特性,开箱即用,能大幅提升开发效率和系统稳定性。实际开发中,无需追求最复杂的方案,需结合业务场景选择合适的实现方式:简单场景可使用基础的 SET 命令 + Lua 脚本,高可用、高一致性场景可使用 Redisson + Redlock 算法。

qq_41559763的博客 707

redis分布式锁

首先说下有哪几种分布式锁,重点介绍redis分布式锁 1.Memcached分布式锁 利用Memcached的add命令。此命令是原子性操作,只有在key不存在的情况下,才能add成功,也就意味着线程得到了锁。 2.Redis分布式锁 和Memcached的方式类似,利用Redis的setnx命令。此命令同样是原子性操作,只有在key不存在的情况下,才能set成功。(setnx命令并不完善,后...

大魔王技术之家 219

Redis实现分布式锁

文章来源连接:https://blog.youkuaiyun.com/kongmin_123/article/details/82080962 在多线程并发的情况下,我们可以使用锁来保证一个代码块在同一时间内只能由一个线程访问。比如Java的synchronized关键字和Reentrantlock类等等。 这样子可以保证在同一个JVM进程内的多个

nbdebuyaobuyao的博客 302

分布式锁超时处理全攻略(含Redis/ZooKeeper对比实践)

掌握分布式锁的超时处理关键方法,避免死锁与资源争用。对比Redis与ZooKeeper实现方案,涵盖适用场景、自动续期与容错机制,提升系统可靠性,值得收藏。

simcode的专栏 1089
上一篇: Java 架构中的缓存秘籍:实现、问题与解决之道
下一篇: 深入理解 Redis 分布式锁:如何实现有效的过期时间和自动续期
程序员职业指南
博客等级 码龄4年 2139粉丝 960原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值