Go Ticker资源泄露案例
前言
前面我们研究了Ticker的实现原理,已经知道Ticker如果不主动停止会有资源泄露的问题。
本节介绍一个真实的案例,重点分析产生资源泄露的现象以及排查思路。
应用背景
曾经做过一个产品,不经意间出现了CPU使用率缓慢升高,最后CPU使用率竟然达到了100%,严重影响了业务。经过排查,问题出在Ticker的使用方式上,创建了Ticker,在使用结束后没有释放导致的。
该产品需要监控其他服务器的健康状态,其中很常见的一种做法是心跳检测。简单的说,周期性的ping这些服务器,能在指定时间内收到ack说明与该服务器之间的网络没问题。
当时使用了一个小众的开源组件tatsushid/go-fastping
来做ping。 该组件介绍如下图所示:
问题现象
在做性能测试时,管理了1000台服务器,差不多4天后发现系统越来越慢,查看CPU使用情况,结果发现CPU使用率已经达到100%。
排查性能问题主要使用pprof,关于pprof的使用方法及原理介绍在请参照相关章节。
使用pprof查看CPU使用情况,主要是查看CPU都在忙什么:
从上图可以看出,CPU主要是被runtime包占用了,其中第二行runtime.siftdownTimer
正是timerproc中的一个动作。
再使用pprof查看函数调用栈,主要看是哪些函数在使用CPU:
从上图可以看出,CPU主要是被ping模块占用,其中ping.(*Pinger).Run
正是开源组件的一个接口。
经过pprof分析可以很清晰的指出问题出在go-fastping组件的Run()接口中,而且是与timer相关的。问题定位到这里,解决就很简单了。
此处,可以先总结一下Ticker资源泄露的现象:
- CPU使用率持续升高
- CPU使用率缓慢升高
源码分析
出问题的源码在ping.go的run()方法中。为叙述方便,对代码做了适当简化:
func (p *Pinger) run() {
timeout := time.NewTicker(p.Timeout) // 创建Ticker timeout
interval := time.NewTicker(p.Interval) // 创建Ticker
for {
select {
case <-p.done: // 正常退出,未关闭Ticker
wg.Wait()
return
case <-timeout.C: // 超时退出,未关闭Ticker
close(p.done)
wg.Wait()
return
case <-interval.C:
if p.Count > 0 && p.PacketsSent >= p.Count {
continue
}
err = p.sendICMP(conn)
if err != nil {
fmt.Println("FATAL: ", err.Error())
}
case r := <-recv:
err := p.processPacket(r)
if err != nil {
fmt.Println("FATAL: ", err.Error())
}
}
if p.Count > 0 && p.PacketsRecv >= p.Count { // 退出,未关闭Ticker
close(p.done)
wg.Wait()
return
}
}
}
该段代码可以看出,这个函数是有出口的,但在出口处没有关闭Ticker,导致资源泄露。
这个问题已经被修复了,可以看到修复后的局部代码如下:
timeout := time.NewTicker(p.Timeout)
defer timeout.Stop() // 使用defer保证Ticker最后被关闭
interval := time.NewTicker(p.Interval)
defer interval.Stop() // 使用defer保证Ticker最后被关闭
总结
有一种情况使用Ticker不主动关闭也不会造成资源泄露,比如,函数创建Ticker后就不会退出,直到进程结束。这种情况下不会持续的创建Ticker,也就不会造成资源泄露。
但是,不管哪种情况,创建一个Ticker后,紧跟着使用defer语句关闭Ticker总是好的习惯。因为,有可能别人无意间拷贝了你的部分代码,而忽略了关闭Ticker的动作。
赠人玫瑰手留余香,如果觉得不错请给个赞~
本篇文章已归档到GitHub项目,求星~ 点我即达

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
-
上一篇
深入剖析神秘的“零拷贝”
前言 "零拷贝"这三个字,想必大家多多少少都有听过吧,这个技术在各种开源组件中都使用了,比如kafka,rocketmq,netty,nginx等等开源框架都在其中引用了这项技术。所以今天想和大家分享一下有关于零拷贝的一些知识。 计算机中数据传输 在介绍零拷贝之前我想说下在计算机系统中数据传输的方式。数据传输系统的发展,为了写这一部分又祭出了我尘封多年的计算机组成原理: 早期阶段: 分散连接,串行工作,程序查询。 在这个阶段,CPU就像个保姆一样,需要手把手的把数据从I/O接口从读出然后再送给主存。 这个阶段具体流程是: CPU主动启动I/O设备 然后CPU一直问I/O设备老铁你准备好了吗,注意这里是一直询问。 如果I/O设备告诉了CPU说:我准备好了。CPU就从I/O接口中读数据。 然后CPU又继续把这个数据传给主存,就像快递员一样。 这种效率很低数据传输过程一直占据着CPU,CPU不能做其他更有意义的事。 接口模块和DMA阶段 这一部分介绍的也是我们后面具体 接口模块 在冯诺依曼结构中,每个部件之间均有单独连线,不仅先多,而且导致扩展I/O设备很不容易,我们上面的早期阶段就是这个体...
-
下一篇
ActiveMQ (二)
今天继续给大家分享的是ActiveMQ,如有不足,敬请指教。 上次我们说到,我们发现消费者每次只能消费一条消息。当队列中有多条消息的时候,我们需要多次运行消费者,才能消费完这些消息。很麻烦!!!!如何解决这个问题呢? 这就需要使用ActiveMQ监听器来监听队列,持续消费消息。 一、ActiveMQ监听器 1.1 配置步骤说明 创建一个监听器对象。 修改消费者代码,加载监听器 1.2 配置步骤 1.2.1 创建监听器MyListener类 package com.xkt.listener; import javax.jms.JMSException; import javax.jms.Message; import javax.jms.MessageListener; import javax.jms.TextMessage; /** * @author lzx * */ public class MyListener implements MessageListener { @Override public void onMessage(Message message) { i...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- Hadoop3单机部署,实现最简伪集群
- CentOS6,7,8上安装Nginx,支持https2.0的开启
- CentOS关闭SELinux安全模块
- SpringBoot2编写第一个Controller,响应你的http请求并返回结果
- SpringBoot2更换Tomcat为Jetty,小型站点的福音
- SpringBoot2整合Thymeleaf,官方推荐html解决方案
- SpringBoot2配置默认Tomcat设置,开启更多高级功能
- SpringBoot2整合Redis,开启缓存,提高访问速度
- SpringBoot2整合MyBatis,连接MySql数据库做增删改查操作
- Docker使用Oracle官方镜像安装(12C,18C,19C)