深入理解 Redis 分布式锁:如何实现有效的过期时间和自动续期

Redis实现JWT(JSON Web TOKEN)自动延长TOKEN过期时间 JWT是JSON WEB TOKEN的简写,常用于生成及校验Token。常见的使用场景为:用户携带name和秘钥访问后端服务器,应用后端在校验通过后使用JWT生成并返回一串Token,后续用户只需要携带此Token就可以访问服务器,在此不多赘述。本文目的是基于redis实现token自动更新其过期时间,在校验用户姓名和密码后使用JWT工具类生成会过期的Token,当用户携带此Token访问服务器后会自动延长其过期时间。 阅读详情

在分布式系统中,Redis 分布式锁的可靠性很大程度上依赖于对锁生命周期的管理。上一篇文章我们探讨了分布式锁的基本原理,今天我们将聚焦于一个关键话题:如何通过合理设置过期时间和实现自动续期机制,来解决分布式锁中的死锁与锁提前释放问题。

一、为什么过期时间是分布式锁的生命线?

"服务挂掉时未删除锁可能导致死锁",这正是过期时间要解决的核心问题。让我们从一个故障场景说起:

假设客户端 A 获取锁后,服务器突然宕机,既没有完成业务逻辑,也没有执行释放锁的操作。如果没有过期时间,这个锁会永远存在于 Redis 中,其他客户端永远无法获取锁,造成永久性死锁。

过期时间就像一把 "安全闸",即使出现异常情况,也能保证锁在一定时间后自动释放。

1. 过期时间的设置方式

在 Redis 中,我们通常使用SET命令的扩展参数一次性完成 "设置锁 + 过期时间" 的原子操作:

// Go语言示例:设置带过期时间的锁
func acquireLock(redisClient *redis.Client, lockKey string, value string, expireSeconds int) (bool, error) {
    // SET key value NX EX seconds
    result, err := redisClient.SetNX(context.Background(), lockKey, value, time.Duration(expireSeconds)*time.Second).Result()
    return result, err
}

这里的关键参数:

  • NX:仅当 key 不存在时才设置(即 "抢锁" 逻辑)
  • EX:指定过期时间(单位秒),也可以用PX指定毫秒

这种方式的优势在于:将 "获取锁" 和 "设置过期时间" 合并为一个原子操作,避免了先设置锁再设置过期时间可能出现的中间状态(如设置完锁后服务宕机,导致过期时间未生效)。

2. 过期时间的合理取值

你的笔记提到源码中默认过期时间为 8 秒,这个值不是凭空设定的,需要考虑以下因素:

  • 业务执行时间:过期时间必须大于业务正常执行的最大耗时,否则会出现 "锁提前释放" 问题
  • Redis 性能:过期时间不宜过长,否则在异常情况下,锁释放延迟会影响系统可用性
  • 资源竞争激烈程度:高竞争场景下可适当缩短过期时间,减少等待时长

实际应用中,建议通过压测确定业务执行的 P99 耗时(99% 的请求能在该时间内完成),再将过期时间设置为其 1.5-2 倍,例如:

// 假设业务P99耗时为5秒,设置过期时间为8秒
const lockExpireSeconds = 8

二、锁提前释放的隐患与解决方案

即使设置了过期时间,仍然存在一个棘手问题:如果业务执行时间超过了过期时间,锁会被自动释放,导致其他客户端获取到锁,引发并发安全问题。

例如:

  1. 客户端 A 获取锁,过期时间 8 秒
  2. 客户端 A 的业务逻辑执行了 10 秒(超过过期时间)
  3. 8 秒后锁自动释放,客户端 B 成功获取锁
  4. 此时客户端 A 和 B 同时操作共享资源,导致数据不一致

1. 自动续期机制:Watch Dog 的工作原理

为了解决这个问题,业界普遍采用 "自动续期" 机制(如 Redisson 中的 Watch Dog),核心思路是:在锁即将过期但业务未完成时,自动延长锁的过期时间。

实现逻辑如下:

  • 获取锁成功后,启动一个后台协程(或线程)
  • 协程每隔一段时间(如过期时间的 1/3)检查锁是否仍被当前客户端持有
  • 如果持有,则延长锁的过期时间
  • 当业务执行完成并释放锁后,终止协程

2. 基于 Go 实现的自动续期方案

下面是一个简化的 Go 语言实现,包含自动续期逻辑:

package main

import (
	"context"
	"fmt"
	"time"

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

// 分布式锁结构体
type RedisLock struct {
	redisClient *redis.Client
	lockKey     string
	lockValue   string // 唯一标识,用于释放锁时验证
	expire      time.Duration
	cancelFunc  context.CancelFunc // 用于停止续期协程
	isLocked    bool
}

// 获取锁
func NewRedisLock(redisClient *redis.Client, lockKey string, expire time.Duration) *RedisLock {
	return &RedisLock{
		redisClient: redisClient,
		lockKey:     lockKey,
		lockValue:   generateUniqueValue(), // 生成唯一值,如UUID
		expire:      expire,
	}
}

// 生成唯一标识,用于区分不同客户端的锁
func generateUniqueValue() string {
	// 实际应用中可使用UUID或"主机名+进程ID+时间戳"等组合
	return fmt.Sprintf("%d-%s", time.Now().UnixNano(), "client-id")
}

// 尝试获取锁,并启动自动续期
func (rl *RedisLock) Lock(ctx context.Context) (bool, error) {
	// 尝试获取锁
	result, err := rl.redisClient.SetNX(ctx, rl.lockKey, rl.lockValue, rl.expire).Result()
	if err != nil || !result {
		return false, err
	}

	rl.isLocked = true

	// 启动自动续期协程
	rl.startRenewal()

	return true, nil
}

// 启动自动续期协程
func (rl *RedisLock) startRenewal() {
	// 创建可取消的上下文
	ctx, cancel := context.WithCancel(context.Background())
	rl.cancelFunc = cancel

	// 每隔expire/3时间续期一次
	renewalInterval := rl.expire / 3

	go func() {
		ticker := time.NewTicker(renewalInterval)
		defer ticker.Stop()

		for {
			select {
			case <-ticker.C:
				// 执行续期操作
				success, err := rl.renew(ctx)
				if !success || err != nil {
					// 续期失败,退出协程
					return
				}
			case <-ctx.Done():
				// 收到取消信号,退出协程
				return
			}
		}
	}()
}

// 续期逻辑,使用Lua脚本保证原子性
func (rl *RedisLock) renew(ctx context.Context) (bool, error) {
	// Lua脚本:只有当锁存在且值匹配时,才延长过期时间
	script := `
		if redis.call('GET', KEYS[1]) == ARGV[1] then
			return redis.call('EXPIRE', KEYS[1], ARGV[2])
		else
			return 0
		end
	`
	// 执行脚本
	result, err := rl.redisClient.Eval(ctx, script, []string{rl.lockKey}, 
		rl.lockValue, int(rl.expire.Seconds())).Int64()
	
	if err != nil {
		return false, err
	}
	
	return result == 1, nil
}

// 释放锁
func (rl *RedisLock) Unlock(ctx context.Context) (bool, error) {
	if !rl.isLocked {
		return false, nil
	}

	// 停止续期协程
	if rl.cancelFunc != nil {
		rl.cancelFunc()
	}

	// Lua脚本:只有当锁存在且值匹配时,才删除锁
	script := `
		if redis.call('GET', KEYS[1]) == ARGV[1] then
			return redis.call('DEL', KEYS[1])
		else
			return 0
		end
	`
	result, err := rl.redisClient.Eval(ctx, script, []string{rl.lockKey}, rl.lockValue).Int64()
	if err != nil {
		return false, err
	}

	rl.isLocked = false
	return result == 1, nil
}

三、自动续期的风险与应对策略

你的笔记提到 "如果服务焊住无法执行完,会一直申请锁延长",这确实是自动续期机制的潜在风险。我们需要从以下几个方面进行防范:

1. 业务超时控制

给业务逻辑设置一个最大执行时间,超过该时间则强制终止并释放锁:

// 带超时控制的业务执行
func runWithTimeout(ctx context.Context, businessFunc func(), timeout time.Duration) {
	// 创建带超时的上下文
	bizCtx, cancel := context.WithTimeout(ctx, timeout)
	defer cancel()

	// 启动业务协程
	done := make(chan struct{})
	go func() {
		businessFunc()
		close(done)
	}()

	// 等待业务完成或超时
	select {
	case <-done:
		// 业务正常完成
	case <-bizCtx.Done():
		// 业务超时,记录日志并处理
		fmt.Println("业务执行超时")
	}
}

2. 续期次数限制

设置最大续期次数,避免无限续期:

// 在RedisLock结构体中增加续期计数器
type RedisLock struct {
	// ... 其他字段
	maxRenewals int   // 最大续期次数
	renewCount  int   // 当前续期次数
}

// 续期时检查次数
func (rl *RedisLock) renew(ctx context.Context) (bool, error) {
	// 如果达到最大续期次数,不再续期
	if rl.renewCount >= rl.maxRenewals {
		return false, nil
	}
	
	// ... 原有续期逻辑
	
	// 续期成功,计数器加1
	rl.renewCount++
	return result == 1, nil
}

3. 监控与告警

对锁的持有时间进行监控,当发现某个锁的持有时间远超预期时,触发告警:

// 记录锁的获取时间
func (rl *RedisLock) Lock(ctx context.Context) (bool, error) {
	// ... 原有逻辑
	
	// 记录获取锁的时间
	rl.acquireTime = time.Now()
	
	// 启动监控协程
	go rl.monitorLock()
	
	return true, nil
}

// 监控锁的持有时间
func (rl *RedisLock) monitorLock() {
	// 每隔一段时间检查一次
	ticker := time.NewTicker(5 * time.Second)
	defer ticker.Stop()
	
	for {
		select {
		case <-ticker.C:
			// 计算锁的持有时间
			holdDuration := time.Since(rl.acquireTime)
			// 如果持有时间超过阈值,触发告警
			if holdDuration > 5*rl.expire {
				fmt.Printf("警告:锁%s已持有超过%d秒\n", rl.lockKey, int(holdDuration.Seconds()))
				// 实际应用中可发送告警到监控系统
			}
		case <-rl.cancelFunc:
			return
		}
	}
}

四、总结与最佳实践

Redis 分布式锁的过期时间和自动续期机制是保证其可靠性的核心,总结几点最佳实践:

过期时间设置原则:

  1. 必须大于业务正常执行时间的 P99 值
  2. 不宜过长(建议不超过 30 秒),避免异常情况下的锁阻塞
  3. 根据业务特性动态调整,而非固定值

自动续期实现要点:

  1. 采用 "1/3 过期时间" 作为续期间隔
  2. 续期操作必须使用 Lua 脚本保证原子性
  3. 续期协程必须能被正常终止,避免资源泄露

风险防控措施:

  1. 给业务逻辑设置超时控制
  2. 限制最大续期次数
  3. 对超长持有时间的锁进行监控告警

生产环境建议:

  1. 优先使用成熟框架(如 Redisson for Java,go-redsync for Go)
  2. 避免重复造轮子,除非有特殊业务需求
  3. 进行充分的故障注入测试,验证极端场景下的锁行为

通过合理设计过期时间和自动续期机制,我们可以大幅提升 Redis 分布式锁的可靠性,为分布式系统的并发控制提供坚实保障。

美团面试:Redis锁如何续期?Redis锁超时,任务没完怎么办? Redisson是一个在Redis的基础上实现的Java驻内存数据网格(In-Memory Data Grid)。它不仅提供了一系列的分布式的Java常用对象,还实现了可重入锁(Reentrant Lock)、公平锁(Fair Lock、联锁(MultiLock)、 红锁(RedLock)、 读写锁(ReadWriteLock)等,还提供了许多分布式服务。Redisson提供了使用Redis的最简单和最便捷的方法。 阅读详情

相关推荐

Redis分布式锁的原理以及如何续期

redis 分布式锁的原理

数据与算法架构提升之路专栏 3万+

Redis 分布式锁如何实现自动续期

如果锁当前还是被占用的,那么等待释放锁的消息,具体实现使用了信号量 Semaphore 来阻塞线程,当锁释放并发布释放锁的消息后,信号量的 release() 方法会被调用,此时被信号量阻塞的等待队列中的一个线程就可以继续尝试获取锁了。死循环去获取锁,而是利用了 Redis 的发布订阅机制,通过 await 方法阻塞等待锁的进程,有效的解决了无效的锁申请浪费资源的问题。如果这个锁的过期时间是30秒,但是业务运行超过了30秒,比如40秒,当业务运行到30秒的时候,锁过期了,其他客户端拿到了这个锁,怎么办。

z_ssyy的博客 1758

Redis分布式锁避坑指南:为什么你的Redisson锁突然失效了?

本文深入解析Redis分布式锁中Redisson看门狗机制的工作原理与常见问题,揭示锁突然失效的原因,并提供实用解决方案。通过案例分析和最佳实践,帮助开发者避免数据安全风险,优化分布式锁的使用效率。

weixin_30872499的博客 539

解决Redis分布式锁过期(续命)

Redis分布式锁(续命) 解决锁过期问题

weixin_47274578的博客 3577

卫星测试科普(三):卫星载荷的核心测试设备探秘

总的说来,卫星芯片与元器件,部组件与单机,整机与子系统,系统与整星的设计、定型、制造、验证、总装、发射、在轨等环节都离不开各种测试场景,对于这些卫星测试场景而言,“射频三大件”与高速数字示波器是构成强大测试系统的基础。卫星通信信号生成:覆盖Ka、Q等波段,具备扫频、脉冲、数字调制、噪声信号、雷达信号、数据链信号、OFDM 等多种信号库,用于测试和验证卫星电子设备的电气性能、频率响应、抗干扰能力等。卫星的通信需要高效、快速地传输大量数据,高速信号的质量至关重要,如果卫星信号不佳,卫星就会出现故障或者隐患。

spwbjss的博客 2320

【ORB-SLAM3】使用安卓手机的摄像头运行ORB3

使用安卓手机的摄像头运行ORB-SLAM3单目

caiqidong321的博客 4999

【Redis | 第六篇】基于Setnx | Redisson实现分布式锁(深入理解Redisson的加锁、可重入锁、看门狗机制)

Redis系列第六篇主要介绍了分布式锁、基于SETNX和Redisson实现分布式锁,其中SETNX命令中,要求确保加锁和删锁过程中,具备原子性,不然会出现(死锁、误删)等问题,最后再通过源码解读,深入理解Redisson的加锁流程、可重入锁分析、自动续期(看门狗机制)

踢桃的成长之路 3179

USRP B210 运行gnuradio时 FPGA固件版本冲突错误解决方法

事情起源于昨天拿到USRP后开始调试,结果整了一个下午都没解决这个问题。 昨天晚上自己思考了一下,觉得还是版本问题。 首先,uhd_find_devices和uhd_usrp_probe均可以正常运行,没有报错,固件正常加载,序列号也可看到。 teray@teray-X550JK:~$ uhd_find_devices [INFO] [UHD] linux; GNU C++ version 7.5...

斯哈研究所 4248

Redisson分布式锁的看门狗机制:为什么我的锁自动续期了?

本文深入解析了Redisson分布式锁的看门狗机制。当未指定租约时间时,该机制会自动启动,通过后台线程定期续期锁,默认每10秒将锁过期时间重置为30秒。文章详细阐述了其触发条件、核心配置参数lockWatchdogTimeout、工作原理及对长任务安全性的提升,并对比了其与固定超时锁的适用场景与潜在风险。

fern8的博客 1021

立创星火计划:基于STM32H743VIH6的Maverick_H743_MK1.0高性能开源飞控硬件全解析

本文详细解析了基于STM32H743VIH6核心的Maverick_H743_MK1.0高性能开源飞控硬件设计。重点阐述了采用双IMU冗余设计、高精度气压计、多路独立电源管理以及丰富接口(如CAN总线)的考量,旨在为穿越机、科研无人机等平台提供强大、可靠的飞行控制解决方案。

weixin_28787801的博客 200

交换机端口模式详解:Access、Hybrid、Trunk

交换机是网络中至关重要的组件之一,用于连接各种设备并启用数据的高效流动。为了更好地管理和控制网络流量,交换机提供了不同的端口模式,其中包括Access、Hybrid和Trunk。本文将深入探讨这三种端口模式,详细解释它们的配置、特性和应用场景,以帮助网络管理员更好地理解和优化网络架构。

网络技术联盟站 4016

Redis分布式锁原理与实现

在分布式系统中,数据的一致性和并发控制是至关重要的。随着微服务架构的普及,多个服务实例可能同时访问同一份数据,这就需要一种机制来确保数据的一致性和避免并发冲突。Redis分布式锁正是为了解决这一问题而诞生的。想象一个在线支付系统,当用户发起支付请求时,系统需要确保同一时间只有一个服务实例处理这笔交易,以防止重复扣款或数据不一致。这种情况下,分布式锁就扮演了至关重要的角色。Redis分布式锁是一种基于Redis的锁机制,它利用Redis的原子操作来保证锁的互斥性和可见性。

我是Java程序员廖志伟,感谢朋友们的支持! 2154

把 LangGraph 当“工作流引擎”用:与传统 BPM 的差异与融合策略

首先,我们来回顾一下有限状态机(FSM)的基本定义——一个 FSM 可以用一个五元组MQΣδq0FMQΣδq0​FQQQ有限状态集合,包含 FSM 所有可能的状态;Σ\SigmaΣ有限输入字母表,包含 FSM 所有可能的输入;δ\deltaδ状态转移函数,定义了 FSM 从当前状态q∈Qq \in Qq∈Q接收输入σ∈Σσ∈Σ后,转移到的下一个状态q′∈Qq' \in Qq′∈Q,即δQ×Σ→QδQ×Σ→Q。

AI 原生应用开发的博客 227

深入理解 Redisson 看门狗机制:保障分布式锁自动续期

在分布式系统的开发中,分布式锁是解决资源竞争、数据一致性问题的关键手段。Redisson 作为一个在 Java 领域广泛使用的 Redis 客户端框架,为我们提供了功能强大且易用的分布式锁实现。其中,看门狗(watchDog)机制更是 Redisson 分布式锁的一大特色,它能够自动为锁续期,确保线程在持有锁期间不会因超时而意外释放锁。本文将深入剖析 Redisson 看门狗机制,并通过代码示例帮助你快速掌握其使用方法。

kimdaydayup的博客 1634

CUDA编程 - Nsight system & Nsight compute 的安装和使用 - 学习记录

在 cuda 编程中,经常会用到 Nsight system 和 Nsight compute 进行性能分析等,下面做个学习总结。本篇包括安装和常用分析思路总结,具体实践操作总结会放在下一篇博客。

点云算法工程师 1万+

适航规章、标准,书籍资料下载汇总(增加DO-297 IMA)

从事IMA平台适航工作的朋友看过来,DO-297英文版PDF可以下载了!更新日志:2024-9-28:初版。2024-10-21:增加DO-330英文版PDF下载,后台回复关键词”330“获取下载链接。2024-10-30:增加DO-160G中文版(非机翻)和英文版PDF下载,后台回复关键词”160“获取下载链接。2024-11-10:增加DO-297英文版PDF下载,后台回复关键词”297“获取下载链接。以下是一些收集到的适航相关的资料,分享给大家。

qq_41854911的博客 1579

深入理解 Redis 分布式锁的实现原理与 Go 代码示例

首先,定义一个锁接口 Lock,包含 Lock 和 Unlock 两个方法,用于获取锁和释放锁操作。

m0_57836225的博客 1332

jdk-8u144-linux-x64.tar.gz.

该资源为搭建Linux系统Java环境用,资源的后缀为tar.gz格式的压缩包

Redis分布式锁详解:原理、实现与最佳实践

在分布式系统中,经常需要对共享资源进行同步访问,以避免并发问题。Redis分布式锁提供了一种简单而高效的方式来实现这一需求。本文将深入探讨Redis分布式锁的工作原理、实现方法以及在实际应用中的一些最佳实践。

2301_77695569的博客 1814

Camera2Demo

在Camera2 的基础上进行封装,使得Camera2一堆初始化逻辑与Activity分离,降低耦合度,完成预览拍照并保存的操作

VC中sqlite的使用

在VC中对sqlite的使用,实现数据库大批数据的快速插入,读取数据库中的数据在ListCtrl中显示

面试八股之Redis篇2.1——redis分布式锁——Redis分布式锁如何实现 ?

🔥本篇概览:详细讲解了Redis分布式锁如何实现 ?

做一个善良的人,做好自己的事,如果愿意,再发一份光。 1141

visio安装包

Microsoft Office Visio 是一款专业的制图软件。利用经过更新的形状、协作工具和数据链接图表,创建专业级图表以简化复杂的信息。

深入理解分布式锁机制

核心思路:通过 Redis 的原子命令和 Lua 脚本实现互斥锁,确保同一时刻只有一个客户端能持有锁。高级特性:重入机制允许同一线程多次获取同一锁;看门狗机制自动续期防止锁意外过期。扩展性:Redisson 提供多种锁模型(如公平锁、联锁等),满足不同分布式场景下的需求。这样,Redisson 的分布式锁既利用了 Redis 高性能、低延迟的特点,又通过一些机制(如重入、自动续期)提升了在复杂分布式系统中的可靠性和易用性。👉自动续期(看门狗机制)会不会让死锁无法释放?

weixin_47303191的博客 1189

基于Redis的分布式锁

本文详细介绍了Redis分布式锁的原理以及具体实现,Redisson实现锁的续期的原理以及实现,Redlock算法的原理以及实现方式。

爱新觉罗ZBC 795

深入理解分布式锁——以Redis为例

当一个节点申请锁时,中心节点会检查当前是否有其他节点持有锁,如果没有,则将锁分配给申请的节点;Etcd的分布式锁是通过创建一个带有TTL(Time To Live)的键值对来实现的,当一个节点需要获取锁时,它会尝试创建这个键值对,如果创建成功,那么这个节点就获取到了锁。在Zookeeper中,可以创建一个临时节点作为锁,当一个节点需要获取锁时,它会尝试创建这个临时节点,如果创建成功,那么这个节点就获取到了锁。当一个节点需要获取锁时,它会在这个表中插入一条记录,如果插入成功,那么这个节点就获取到了锁。

ahflex126r的博客 1072
上一篇: 深入探讨 Redis 分布式锁:避免锁被其他协程误删的策略
下一篇: Redis 分布式锁深度解析:setnx 命令的核心作用与实现
程序员职业指南
博客等级 码龄4年 2139粉丝 960原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值