如何更酷地实现 Go 程序热开关功能
共 3109字,需浏览 7分钟
·
2022-07-06 12:44
回复“Go语言”即可获赠从入门到进阶共10本电子书
黄叶仍风雨,青楼自管弦。
开发中,我们经常会有热开关的需求,即特定功能在程序运行中的适当时候对它进行打开或关闭。例如性能分析中使用的 pprof 采样,就是一种典型的热开关。本文将讨论如何将这种热开关做得更酷。
在介绍新方案之前,我们回顾一下,在 Go 程序中,pprof 是如何做的。
接口调用
对程序进行性能采样,可能会影响到它的服务能力。因此,线上采样一般是在指定的小块时间范围内进行,需要有效的开关控制。
为了做到这点,我们一般采用在代码中引入 net/http/pprof 包(其 init 函数绑定了路由功能函数)的方式。外部访问指定端口的 HTTP 服务时开启采样功能,采样时间结束后,即关闭采集。
实现代码如下所示
package main
import (
"net/http"
_ "net/http/pprof"
)
func main() {
go func() {
_ = http.ListenAndServe(":8080", nil)
}()
...
}
这种做法,当然没什么问题。我们可以学习它,将其他的开关功能也做成 HTTP 服务。但,还有其他更酷的方式吗?
信号通知
在信号处理与 Go 程序的优雅退出一文中,我们谈论过信号机制,它用以向应用程序发送某种事件通知。
我们可以将基于接口触发的方式改为信号通知。
首先,构造采样功能函数(对应于 net/http/pprof 包下 init 函数中绑定的路由功能函数)。
func RegisterSignalForProfiling(sig os.Signal) {
ch := make(chan os.Signal)
started := false
signal.Notify(ch, sig)
go func() {
var memoryProfile, cpuProfile, traceProfile *os.File
for range ch {
if started {
pprof.StopCPUProfile()
trace.Stop()
pprof.WriteHeapProfile(memoryProfile)
memoryProfile.Close()
cpuProfile.Close()
traceProfile.Close()
started = false
} else {
cpuProfile, _ = os.Create("cpu.pprof")
memoryProfile, _ = os.Create("memory.pprof")
traceProfile, _ = os.Create("runtime.trace")
pprof.StartCPUProfile(cpuProfile)
trace.Start(traceProfile)
started = true
}
}
}()
}
在上述函数中,我们定义了接收信号通道ch
,通过signal.Notify(ch, sig)
将指定的通知信号sig
与ch
进行绑定。for range ch
将阻塞等待外部信号sig
,随着sig
信号的到来,交替进入开启或关闭采样的逻辑。
在main
函数中,就可以这样替代http.ListenAndServe(":8080", nil)
了。
package main
import (
"syscall"
...
)
func main() {
RegisterSignalForProfiling(syscall.Signal(31))
...
}
在 linux 系统,可以通过kill -signal_number pid
命令向程序发送指定信号。
如上代码所示,我们硬编码指定的采样开关信号值是 31。因此,当程序运行起来后,我们在控制台输入kill -31 pid
命令,即可开启采样,再次输入kill -31 pid
命令,就关闭了采样。
依葫芦画瓢,我们再来一个打印 goroutine 堆栈信息的热开关函数,是不是很酷?
func RegisterSignalForPrintStack(sig os.Signal) {
ch := make(chan os.Signal)
signal.Notify(ch, sig)
go func() {
for range ch {
buffer := make([]byte, 1024*1024*4)
runtime.Stack(buffer, true)
fmt.Println(string(buffer))
}
}()
}
总结
热开关是一个很简单常用的功能,无非是选择何种触发与等待方式。基于接口的调用更适合于远程控制,基于信号则便于本地控制。
如果你有更好的实现方式,欢迎留言分享~
------------------- End -------------------
往期精彩文章推荐:
![](https://filescdn.proginn.com/6f02739c83b586c541eafb39727a8f42/939d4d77abffdf83da460df3a0aee32b.webp)
欢迎大家点赞,留言,转发,转载,感谢大家的相伴与支持
想加入Go学习群请在后台回复【入群】
万水千山总是情,点个【在看】行不行