事件回顾
- 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