郑文峰的博客 郑文峰的博客
首页
分类
标签
归档
关于
  • 导航 (opens new window)
  • 代码片段 (opens new window)
  • 收藏
  • 友链
  • 外部页面

    • 开往 (opens new window)
GitHub (opens new window)

zhengwenfeng

穷则变,变则通,通则久
首页
分类
标签
归档
关于
  • 导航 (opens new window)
  • 代码片段 (opens new window)
  • 收藏
  • 友链
  • 外部页面

    • 开往 (opens new window)
GitHub (opens new window)
  • 编程
  • go
zhengwenfeng
2025-05-13
目录

使用etcd分布式锁导致的协程泄露与死锁问题

# 简介

本文记录自己在工作中排查etcd应用分布式锁而导致的泄露与死锁问题,并通过分析源码找到根因,最终解决。

# 问题重现

# 现象描述

服务出现数据入库失败,且伴随内存持续增长。关键现象:

  1. 锁残留:通过etcdctl get --prefix /my-lock/可看到锁KEY长期存在
  2. 租约续期:etcdctl lease timetolive显示租约TTL不断重置
  3. 资源增长:Go程序协程数随请求量线性增长(可通过pprof观测)

# 最小复现代码

func main() {
	// ... etcd client初始化代码不变...

	// 关键问题点1:缺失session关闭
	session, _ := concurrency.NewSession(cli)
	// defer session.Close() // 故意注释导致协程泄漏

	// 关键问题点2:未释放锁
	mutex := concurrency.NewMutex(session, "/my-lock/")
	ctx, _ := context.WithTimeout(context.Background(), 5*time.Second)
	_ = mutex.Lock(ctx)
	
	// ...业务逻辑代码...
	
	// 关键问题点3:阻止程序退出(仅用于demo)
	select {} 
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

# 根因分析

# 架构示意图

+--------------+      定期续约      +------+
|  Go routine  |----------------->| etcd |
+--------------+  (KeepAlive)     +------+
       ▲
       │ 未调用Close()
       └──────+
              |
+---------------------------+
| session.Close() 核心作用:|
| 1. 停止续约协程           |
| 2. 释放租约              |
+---------------------------+
1
2
3
4
5
6
7
8
9
10
11
12

# 源码关键路径

协程泄漏路径: NewSession() → go keepAlive协程 → client.KeepAlive() → 后台协程续约(sendKeepAliveLoop)

资源释放路径: session.Close() → Orphan() → 关闭上下文 → 触发keepAlive协程退出

# 解决方案

在创建session后,确保调用Close()方法,及时释放资源。

func main() {
	// ...初始化代码不变...

	session, err := concurrency.NewSession(cli)
	if err != nil {
		log.Fatal(err)
	}
	defer session.Close() // 新增关键修复

	mutex := concurrency.NewMutex(session, "/my-lock/")
	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel() // 确保上下文取消

	if err := mutex.Lock(ctx); err != nil {
		log.Fatal(err)
	}
	defer mutex.Unlock(context.TODO()) // 双保险释放锁

	// ...业务逻辑...
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

# 最佳实践

  1. 资源释放三原则:
  • 对每个NewSession()必须配对defer Close()
  • 锁操作必须包裹在Lock()/Unlock()中
  • 使用带超时的上下文(建议不超过5s)

# 总结

  1. etcd特性:会话型锁的设计需要客户端主动维护生命周期
  2. 调试技巧:使用go tool pprof观察goroutine增长趋势
#go#分布#工作记录
上次更新: 2025/05/13, 09:44:29
最近更新
01
基于pre-commit的Python代码规范落地实践
05-12
02
初识 MCP Server
05-01
03
pulsar阻塞导致logstash无法接入日志
10-30
更多文章>
Theme by Vdoing | Copyright © 2022-2025 zhengwenfeng | MIT License | 赣ICP备2025055428号
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式