Golang基于Redis实现可重入分布式锁详解

温以宁
温以宁
发布时间:2026-10-15| 6人浏览过
原创

本文介绍如何在Golang项目中利用Redis实现具备自动续期能力的可重入分布式锁。通过引入“看门狗”机制,解决因网络波动或进程崩溃导致的锁资源死锁问题,并提供完整的结构体设计与核心代码。

什么是可重入锁

在分布式系统中,为了确保同一时刻只有一个线程能执行特定任务,通常采用“加锁 → 执行 → 解锁”的逻辑。但由于网络不稳定、进程被杀或服务宕机等不可控因素,Unlock()操作可能无法执行,导致锁永久持有。

常见的兜底方案是在Lock时设置过期时间,但在DoJob()执行时长不确定的情况下,固定超时可能导致任务未完成前锁即释放,引发并发冲突;或者设置过长超时导致其他等待者长时间阻塞。

续期解决方案

为了平衡任务执行不确定性与资源竞争效率,业界常采用两种策略:

  1. 限制任务时长:对DoJob设置超时(如Golang中的TimeoutContext),并据此设定锁过期时间。
  2. 动态续期(可重入锁):设置一个较短的基础锁有效期,同时启动一个后台协程(称为WatchDog/看门狗)不断检测任务状态并进行锁续期。这种不断重新获取有效期的机制即为“可重入锁”的核心特征。

Golang具体实现

Golang原生支持协程(goroutine),非常适合实现非阻塞的后台续期逻辑。下面展示基于Redis的标准实现,主要依赖SET NX EX命令加锁,利用context控制看门狗的退出。

完整代码逻辑

该实现包含以下核心要素:

  • 数据结构:存储锁Key、过期时间、当前状态及用于取消后台任务的CancelFunc。
  • TryLock:执行原子性SET操作申请锁,成功后启动看门狗并标记上锁成功。
  • startWatchDog:新建协程循环执行,每隔一段时间调用Redis EXPIRE命令延长锁生命周期,直至主流程调用Unlock触发取消信号。
  • Unlock:首先触发cancelFun停止看门狗,随后在Redis中删除Key释放锁。
// DistributeLockRedis 基于redis的分布式可重入锁,自动续租
type DistributeLockRedis struct {
	key       string             // 锁的key
	expire    int64              // 锁超时时间
	status    bool               // 上锁成功标识
	cancelFun context.CancelFunc // 用于取消自动续租携程
	redis     redis.Client       // redis句柄
}

// NewDistributeLockRedis 初始化锁实例
func NewDistributeLockRedis(key string, expire int64) *DistributeLockRedis {
	return &DistributeLockRedis{
		 key : key,
		 expire : expire,
	}
}

// TryLock 尝试获取锁并启动看门狗
func (dl *DistributeLockRedis) TryLock() (err error) {
	if err = dl.lock(); err != nil {
		return err
	}
	// 创建带取消功能的上下文
	ctx, cancelFun := context.WithCancel(context.Background())
	dl.cancelFun = cancelFun
	// 启动后台协程进行自动续期
	dl.startWatchDog(ctx)
	dl.status = true
	return nil
}

// lock 底层竞争锁逻辑
func (dl *DistributeLockRedis) lock() error {
	// 使用 SET key value NX EX seconds 保证原子性
	res, err := redis.String(dl.redis.Do(context.Background(), "SET", dl.key, 1, "NX", "EX", dl.expire))
	if err != nil {
		return err
	}
	if res == "OK" {
		return nil
	}
	return fmt.Errorf("lock failed")
}

// startWatchDog 守护协程,自动续期
func (dl *DistributeLockRedis) startWatchDog(ctx context.Context) {
	// 此处省略 safeGo 包装,实际应使用安全的协程调度函数
	go func() {
		for {
			select {
			case <-ctx.Done():
				return // 收到取消信号,退出续期
			default:
				// 只有处于锁定状态才续期
				if dl.status {
					// 调用 Redis EXPIRE 命令续期
					_, err := redis.Int(dl.redis.Do(context.Background(), "EXPIRE", dl.key, dl.expire))
					if err != nil {
						return
					}
				}
				// 续期间隔为总过期时间的一半
				time.Sleep(time.Duration(dl.expire/2) * time.Second)
			}
		}
	}()
}

// Unlock 释放锁,同时关闭看门狗
func (dl *DistributeLockRedis) Unlock() (err error) {
	// 第一步:必须优先取消后台续期协程
	if dl.cancelFun != nil {
		dl.cancelFun()
	}
	var res int
	if dl.status {
		// 第二步:执行 DEL 命令释放Redis锁
		res, err = redis.Int(dl.redis.Do(context.Background(), "Del", dl.key))
		if err != nil || res != 1 {
			return fmt.Errorf("释放锁失败")
		}
		dl.status = false
	}
	return nil
}

使用方法

在实际业务逻辑中,可通过defer语句确保锁资源的最终释放,即使在发生panic时看门狗也会被正确终止。

func Foo() error {
  key := "my_task_key"
  
  // 1. 创建锁实例,设置过期时间为10秒
  dl := NewDistributeLockRedis(key, 10)
  
  // 2. 争抢锁
  err := dl.TryLock()
  if err != nil {
      return err // 没抢到锁直接返回
  }
  
  // 3. 确保业务结束(或异常)时一定释放锁
  defer dl.Unlock()
  
  // 4. 执行业务逻辑
  DoJob()
  
  return nil
}

上述代码展示了Golang中实现可重入分布式锁的标准范式,重点在于利用协程的生命周期管理与主业务逻辑绑定,确保服务异常时资源能安全回归。

以上就是Golang实现可重入锁的详细内容,更多关于Golang可重入锁的资料请关注本站其它相关文章!

相关标签:

免责声明:本文内容仅供参考,不代表本站观点。转载或引用请注明出处。

Python 相关产品和应用

全部下载
Selenium

Selenium

4.51.0 · Windows

用于浏览器自动化和 Web 测试的 Python 工具。

scikit-learn

scikit-learn

1.9.1 · Windows

常用 Python 机器学习算法与模型评估工具库。

Black

Black

26.10.1 · Windows

强调一致性的 Python 自动代码格式化工具。

Ruff

Ruff

0.17.1 · macOS

由 Rust 编写的高速 Python 代码检查和格式化工具。

pytest

pytest

9.1.1 · Windows

简洁而强大的 Python 自动化测试框架。

FastAPI

FastAPI

0.146.0 · Windows

高性能 Python API 框架,支持类型提示和自动接口文档。

Flask

Flask

3.1.3 · Windows

轻量灵活的 Python Web 应用框架。

Django

Django

6.1.2 · Windows

成熟的 Python Web 框架,适合构建内容站点和业务系统。

Spyder

Spyder

6.1.7 · Windows

面向科学计算与数据分析的 Python 集成开发环境。

SciPy

SciPy

1.18.1 · Windows

面向科学、工程和技术计算的 Python 算法库。