Go 服务自动收集线上问题现场

简介: Go 服务自动收集线上问题现场

前言

对于 pprof,相信熟悉 Go 语言的程序员基本都不陌生,一般线上的问题都是靠它可以快速定位。但是实际项目中,很多时候我们为了性能都不会开启它,但是出了问题又要靠它来分析。

好在 go-zero 已经帮我们很好的集成进来了,我们只需要像开关一样去开启、关闭它即可,这样我们就可以配合运维监控,当出现 cpu、内存等异常情况时候,自动开始开启收集(比如大半夜你睡的正香的时候),那么第二天可以通过分析当时的采样还原现场,那我们看看 go-zero 是如何做的。

源码分析

我们可以看 go-zero 源码位置 https://github.com/zeromicro/go-zero/blob/master/core/proc/signals.go

func init() {
  go func() {
    ...
    signals := make(chan os.Signal, 1)
    signal.Notify(signals, syscall.SIGUSR1, syscall.SIGUSR2, syscall.SIGTERM)
    for {
      v := <-signals
      switch v {
      ...
      case syscall.SIGUSR2:
        if profiler == nil {
          profiler = StartProfile()
        } else {
          profiler.Stop()
          profiler = nil
        }
      ...
    }
  }()
}

服务启动的时候,go-zero 在 init 初始化了监听信号操作(gracefulStop 也是通过这里通知的,这里不展开讲了),可以看到在接收到 syscall.SIGUSR2 信号时候,如果是第一次就开始收集,第二次就停止收集,看到这块可能瞬间就明白了,我们只需要在服务器执行 kill -usr2 <服务进程id> 就可以开始收集这个服务的 pprof 信息了,再执行一次就停止了收集,就可以将这些文件导出到本地,使用 go tool pprof 分析。

一次线上实战

我们线上有一个 mq 的服务监控告警,内存占用比较高,这时候我打开 grafana 看到服务内存累计占用的确异常,如下图:

这时候到线上找到这个服务的服务器,执行了 ps aux | grep xxx_mq,查询到了这个 mq 服务的进程 ID 是 21181,我们这时候就可以给这个 xxx_mq 服务发送信号收集 pprof 信息:kill -usr2 21181

第一次执行了这个命令后,在对应服务的 access.log 日志中可以看到 enablepprof,当我们再次执行 kill -usr2 21181access.log 日志中可以看到 disablepprof 信息,这时候代表收集完成了。值得注意的是收集的信息都在 /tmp 文件夹下,以这个服务名命名的如下:

- xxxx-mq-cpu-xxx.pprof
- xxxx-mq-memory-xxx.pprof
- xxxx-mq-mutex-xxx.pprof
- xxxx-mq-block-xxx.pprof
- .......

这时候就可以下载对应的 pprof 去本地分析,可以使用 go tool pprof xxxx-mq-memory-xxx.pprof,也可以配合 graphviz 使用 web ui 查看,由于我这边通过命令行就快速定位了问题,就没用使用 web ui

我使用 go tool pprof xxxx-**-mq-memory-xxx.pprof 然后进入命令行交互,使用 top 30 查看前面占用较高的资源。

前面基本是底层序列化等操作,发现主要问题集中在红色框中导致持续上涨的内存,因为业务同学在 mq 中消费完了消息又向下游其他的mq服务使用 publisher 发送一个 mq 消息,每次发送都调用一个 NewPublisher 然后在 defer close,恰恰这个 mq 服务又大量消息消费又特别频繁,导致内存不断上涨,最终解决方案将 NewPublishersvcCtx 中初始化一个 client 就可以了,没必要每次都要 NewPublisher,世界又恢复清净了。

写在最后

想一下 go-zero 给了我们 pprof 开关,让我们很方便的实现分析问题,但是不是所有问题都是一直存在的,比如半夜突发内存、cpu 过高,早上起来服务正常了,这怎么排查?所以我们希望如果异常了,能保留问题现场,那我们是不是可以配合运维监控实现自动保存问题现场呢?比如内存、cpu 连续超过 80% 指标3分钟的话,我们就调用这个开关收集,之后我们就可以根据这个文件来分析问题了,这样就达到自动化了。

项目地址

go-zero 微服务框架:https://github.com/zeromicro/go-zero

go-zero 微服务最佳实践项目:https://github.com/Mikaelemmmm/go-zero-looklook

相关实践学习
消息队列RocketMQ版:基础消息收发功能体验
本实验场景介绍消息队列RocketMQ版的基础消息收发功能,涵盖实例创建、Topic、Group资源创建以及消息收发体验等基础功能模块。
消息队列 MNS 入门课程
1、消息队列MNS简介 本节课介绍消息队列的MNS的基础概念 2、消息队列MNS特性 本节课介绍消息队列的MNS的主要特性 3、MNS的最佳实践及场景应用 本节课介绍消息队列的MNS的最佳实践及场景应用案例 4、手把手系列:消息队列MNS实操讲 本节课介绍消息队列的MNS的实际操作演示 5、动手实验:基于MNS,0基础轻松构建 Web Client 本节课带您一起基于MNS,0基础轻松构建 Web Client
相关文章
|
4月前
|
缓存 弹性计算 API
用 Go 快速开发一个 RESTful API 服务
用 Go 快速开发一个 RESTful API 服务
|
1月前
|
Go UED
Go Web服务中如何优雅平滑重启?
在生产环境中,服务升级时如何确保不中断当前请求并应用新代码是一个挑战。本文介绍了如何使用 Go 语言的 `endless` 包实现服务的优雅重启,确保在不停止服务的情况下完成无缝升级。通过示例代码和测试步骤,详细展示了 `endless` 包的工作原理和实际应用。
50 3
|
1月前
|
JSON Go UED
Go Web服务中如何优雅关机?
在构建 Web 服务时,优雅关机是一个关键的技术点,它确保服务关闭时所有正在处理的请求都能顺利完成。本文通过一个简单的 Go 语言示例,展示了如何使用 Gin 框架实现优雅关机。通过捕获系统信号和使用 `http.Server` 的 `Shutdown` 方法,我们可以在服务关闭前等待所有请求处理完毕,从而提升用户体验,避免数据丢失或不一致。
26 1
|
6月前
|
Go
go创建web服务
go创建web服务
|
3月前
|
Go API 开发者
深入探讨:使用Go语言构建高性能RESTful API服务
在本文中,我们将探索Go语言在构建高效、可靠的RESTful API服务中的独特优势。通过实际案例分析,我们将展示Go如何通过其并发模型、简洁的语法和内置的http包,成为现代后端服务开发的有力工具。
|
4月前
|
安全 Go Docker
Go服务Docker Pod不断重启排查和解决
该文章分享了Go服务在Docker Pod中不断重启的问题排查过程和解决方案,识别出并发写map导致fatal error的问题,并提供了使用sync.Map或concurrent-map库作为并发安全的替代方案。
55 4
|
4月前
|
监控 Go 微服务
使用 ServiceWeaver 构建 go 服务
使用 ServiceWeaver 构建 go 服务
|
4月前
|
安全 小程序 Go
如何更干净地退出Go服务
如何更干净地退出Go服务
|
4月前
|
运维 监控 容灾
[go 面试] 实现服务高可用的策略和实践
[go 面试] 实现服务高可用的策略和实践
|
5月前
|
XML JSON Go
Swoole与Go系列教程之WebSocket服务的应用
在 WebSocket 协议出现之前,Web 应用为了能过获取到实时的数据都是通过不断轮询服务端的接口。轮询的效率、延时很低,并且很耗费资源。
1061 2
Swoole与Go系列教程之WebSocket服务的应用