go 内存泄露

事件回顾

  • 9.15号晚18点服务端发版
  • 9.16号晚21点监控显示自发版后服务器 TCP_alloc 指标一路飙升至40K(如图)

问题分析

看到 tcp_alloc 指标异常,初步怀疑有tcp连接创建后未关闭,应该是上次发版写了什么代码导致的。回顾此次发版清单,问题应该出现在了daemon服务心跳上报上。

daemon服务是一个业务监控服务,通过监听etcd中注册的服务状态变更,向企业微信用户发送消息提醒。在此之前由于消息提醒协程panic异常退出,导致Ad服务停止后消息提醒未正常发出,造成生产P3事故。本次发版通过增加每3秒心跳保活机制,双重保证在服务中断后,消息提醒能正常发出(代码如下)

复制代码
func KeepAliveWithMp() {
	var keepAliveWithMpUrl, _ = config.String("keepAliveWithMpUrl")
	if keepAliveWithMpUrl == "" {
		return
	}
	ticker := time.NewTicker(3 * time.Second)
	for range ticker.C {
		_, _ = http.Get(keepAliveWithMpUrl + "?runningKey=" + getRunningKey())
	}
	ticker.Stop()
}

问题出现在了 http.Get 请求发出后未读取响应主体,也没有关闭此次tcp连接,导致一直在创建新的tcp连接,一个get请求会创建两个goroutine,导致内存泄露。

此处有两种修复方式,一是在http请求完了之后主动关闭此次连接释放资源,另一种则是读取响应主体后复用改链接

代码修复

复制代码
// 方式一
func KeepAliveWithMp() {
	var keepAliveWithMpUrl, _ = config.String("keepAliveWithMpUrl")
	if keepAliveWithMpUrl == "" {
		return
	}
	ticker := time.NewTicker(3 * time.Second)
	for range ticker.C {
		resp, _ := http.Get(keepAliveWithMpUrl + "?runningKey=" + getRunningKey())
		_ = resp.Body.Close()
	}
	ticker.Stop()
}


// 方式二
func KeepAliveWithMp() {
	var keepAliveWithMpUrl, _ = config.String("keepAliveWithMpUrl")
	if keepAliveWithMpUrl == "" {
		return
	}
	ticker := time.NewTicker(3 * time.Second)
	for range ticker.C {
		resp, _ := http.Get(keepAliveWithMpUrl + "?runningKey=" + getRunningKey())
		_,_ = ioutil.ReadAll(resp.Body)
	}
	ticker.Stop()
}

后期预警

通过每小时检测一次服务器 TCP_alloc 指标,当超过30K 后发送短信提醒,避免再次出现该异常。

复制代码
// TCP_alloc 指标查看方式
cat /proc/net/sockstat 

sockets: used 3621
TCP: inuse 2833 orphan 5 tw 1792 alloc 3354 mem 489
UDP: inuse 9 mem 4
UDPLITE: inuse 0
RAW: inuse 0
FRAG: inuse 0 memory 0
相关推荐
海梨花3 分钟前
【从零开始学习Redis】项目实战-黑马点评D2
java·数据库·redis·后端·缓存
bug菌7 分钟前
零基础也能做出AI应用?Trae是如何打破编程"高墙"的?
后端·ai编程·trae
Sammyyyyy10 分钟前
2025年,Javascript后端应该用 Bun、Node.js 还是 Deno?
开发语言·javascript·node.js
Java技术小馆12 分钟前
重构 Controller 的 7 个黄金法则
java·后端·面试
用户40993225021224 分钟前
容器化部署FastAPI应用:如何让你的任务系统代码在云端跳舞?
后端·ai编程·trae
Java水解24 分钟前
MySQL 亿级数据表平滑分表实践:基于时间分片的架构演进
后端·mysql
Neo25531 分钟前
Spring 5.3.x 源码:invokeBeanFactoryPostProcessors()详解
后端
金銀銅鐵33 分钟前
[Java] 以 IntStream 为例,浅析 Stream 的实现
java·后端
Neo25536 分钟前
Spring 5.3.x 源码:refresh()方法
后端