Golang服务意外停止的排查与解决方案

Golang服务意外停止的排查与解决方案 我正在开发一个集成了用Golang开发的REST API的应用程序,使用了Gin框架,并将API部署在AWS EC2实例上。

我运行API的步骤如下:

  1. 将全部文件上传到src目录
  2. 执行go get和go install brbapi(brbapi是包/文件夹名称)
  3. 启动tmux并运行"brbapi"(使用tmux保持API持续运行)

这样就能启动服务,我可以使用REST API,但有时(每天一次)服务会意外停止,没有任何错误或日志。

有没有什么方法可以保持服务持续运行,并添加一些日志记录方法来获取故障日志?

7 回复

要了解关于 systemd 和全天候运行的 Go 应用程序,请观看 Francesc Campoy 的这个视频:https://www.youtube.com/watch?v=SQeAKSJH4vw

更多关于Golang服务意外停止的排查与解决方案的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


tmux 并不适合用于运行服务,应该使用 systemd 并设置正确的重启策略。

除此之外,我也有一些服务在生产环境中运行,它们全部 7×24 小时不间断运行,从未出现意外停止或退出的情况。

我的推测是,你在应用程序中触发了某些直接调用 os.Exit 的路径。

感谢Nobbz的回复,我检查了我的代码,os.Exists并没有在任何地方使用。我正按照你的建议尝试使用systemd,但不知道从哪里开始,因为我是Windows用户,对Ubuntu和命令不太熟悉。你能推荐一些教程吗?

如果你能解释一下你是如何让服务7x24小时运行的,那将会很有帮助。

我首先会找出它停止的原因,这个问题肯定可以修复,你的 Go 守护进程应该能够长时间运行而不会停止/崩溃。

GitHub

coreos/go-systemd

头像

coreos/go-systemd

Go 语言对 systemd 套接字激活、日志、D-Bus 和单元文件的绑定 - coreos/go-systemd

我完全同意Norbert的观点。你需要找一个精通Linux的人来帮你进行设置。

你的Go程序可能还有其他终止方式。也许你的代码中存在触发运行时错误的内容。我建议你仔细检查程序,确保对库调用返回的错误进行检查。如果检测到不可恢复的错误情况,可以打印日志信息,这样至少能够优雅地退出。微笑

如果你运气好的话,日志中至少会有一条有用的错误信息,帮助你发现问题。

如果你还没有这样做,请查看log包,并确保你理解error接口,以及正确使用它们的方法。

嗯,可能不是你的代码问题,而是其他原因。检查退出代码会有所帮助。在不了解你的程序、日志记录方式以及启动方式的情况下,我们无法判断该从何处着手排查。如果你没有使用 os.Exit,也许只是因为满足真实的退出条件而正常退出了主循环?

关于如何实现24/7持续运行?正如我之前所说,我使用 systemd 来管理服务。更准确地说,我负责编写软件,而运维团队负责提供必要的 systemd 单元文件,他们还负责所有监控集成之类的工作。

如果你不熟悉Linux系统,可能不应该选择它作为部署目标。至少应该找一位足够了解Linux的现场协助人员,这位人员需要被授权查看你的源代码和运行时环境,以便正确完成所有配置。

在对你软件一无所知的情况下,仅通过网络论坛来指导会涉及太多需要考虑的因素。

func main() {
    fmt.Println("hello world")
}

要排查和解决Golang服务意外停止的问题,可以从以下几个方面着手:

1. 添加完善的日志记录

首先在代码中添加详细的日志记录,特别是在启动、关闭和错误处理部分:

package main

import (
    "log"
    "net/http"
    "os"
    "time"
    
    "github.com/gin-gonic/gin"
)

func main() {
    // 设置日志输出到文件
    logFile, err := os.OpenFile("api.log", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
    if err != nil {
        log.Fatal("无法打开日志文件:", err)
    }
    defer logFile.Close()
    
    log.SetOutput(logFile)
    log.SetFlags(log.LstdFlags | log.Lshortfile)
    
    log.Println("API服务启动...")
    
    router := gin.Default()
    
    // 添加恢复中间件来捕获panic
    router.Use(gin.Recovery())
    
    // 添加自定义恢复处理
    router.Use(func(c *gin.Context) {
        defer func() {
            if err := recover(); err != nil {
                log.Printf("发生panic: %v", err)
                c.JSON(http.StatusInternalServerError, gin.H{
                    "error": "内部服务器错误",
                })
            }
        }()
        c.Next()
    })
    
    // 示例路由
    router.GET("/health", func(c *gin.Context) {
        log.Println("健康检查请求")
        c.JSON(http.StatusOK, gin.H{
            "status":    "ok",
            "timestamp": time.Now().Unix(),
        })
    })
    
    // 你的其他路由...
    
    log.Println("开始监听端口...")
    if err := router.Run(":8080"); err != nil {
        log.Fatalf("服务器启动失败: %v", err)
    }
}

2. 使用进程管理工具

使用systemd或supervisord来管理服务,而不是tmux:

使用systemd(推荐)

创建服务文件 /etc/systemd/system/brbapi.service

[Unit]
Description=BRB API Service
After=network.target

[Service]
Type=simple
User=ubuntu
WorkingDirectory=/path/to/your/src
ExecStart=/home/ubuntu/go/bin/brbapi
Restart=always
RestartSec=10
StandardOutput=journal
StandardError=journal

[Install]
WantedBy=multi-user.target

然后启用并启动服务:

sudo systemctl daemon-reload
sudo systemctl enable brbapi
sudo systemctl start brbapi

使用supervisord

安装supervisord:

sudo apt-get install supervisor

创建配置文件 /etc/supervisor/conf.d/brbapi.conf

[program:brbapi]
command=/home/ubuntu/go/bin/brbapi
directory=/path/to/your/src
autostart=true
autorestart=true
startretries=3
stderr_logfile=/var/log/brbapi.err.log
stdout_logfile=/var/log/brbapi.out.log
user=ubuntu
environment=GIN_MODE=release

然后重新加载配置:

sudo supervisorctl reread
sudo supervisorctl update
sudo supervisorctl start brbapi

3. 添加信号处理和优雅关闭

package main

import (
    "context"
    "log"
    "net/http"
    "os"
    "os/signal"
    "syscall"
    "time"
    
    "github.com/gin-gonic/gin"
)

func main() {
    logFile, err := os.OpenFile("api.log", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
    if err != nil {
        log.Fatal("无法打开日志文件:", err)
    }
    defer logFile.Close()
    log.SetOutput(logFile)
    
    router := gin.Default()
    
    // 你的路由配置...
    router.GET("/api/data", func(c *gin.Context) {
        log.Println("处理数据请求")
        c.JSON(http.StatusOK, gin.H{"data": "example"})
    })
    
    srv := &http.Server{
        Addr:    ":8080",
        Handler: router,
    }
    
    // 在goroutine中启动服务器
    go func() {
        log.Println("启动服务器在 :8080")
        if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
            log.Fatalf("监听失败: %v", err)
        }
    }()
    
    // 等待中断信号以优雅地关闭服务器
    quit := make(chan os.Signal, 1)
    signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
    <-quit
    log.Println("正在关闭服务器...")
    
    ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
    defer cancel()
    
    if err := srv.Shutdown(ctx); err != nil {
        log.Fatal("服务器强制关闭:", err)
    }
    log.Println("服务器已退出")
}

4. 添加内存和性能监控

import (
    "runtime"
    "github.com/gin-gonic/gin"
)

// 添加内存状态端点
router.GET("/debug/memory", func(c *gin.Context) {
    var m runtime.MemStats
    runtime.ReadMemStats(&m)
    
    log.Printf("内存使用 - Alloc: %v MB, TotalAlloc: %v MB, Sys: %v MB, NumGC: %v",
        m.Alloc/1024/1024, m.TotalAlloc/1024/1024, m.Sys/1024/1024, m.NumGC)
        
    c.JSON(http.StatusOK, gin.H{
        "alloc":       m.Alloc / 1024 / 1024,
        "total_alloc": m.TotalAlloc / 1024 / 1024,
        "sys":         m.Sys / 1024 / 1024,
        "num_gc":      m.NumGC,
    })
})

5. 使用AWS CloudWatch记录日志

在EC2上安装CloudWatch代理来收集日志:

# 安装CloudWatch代理
sudo yum install -y amazon-cloudwatch-agent

# 配置代理(创建配置文件)
sudo nano /opt/aws/amazon-cloudwatch-agent/etc/amazon-cloudwatch-agent.json

配置文件示例:

{
    "logs": {
        "logs_collected": {
            "files": {
                "collect_list": [
                    {
                        "file_path": "/path/to/your/api.log",
                        "log_group_name": "brbapi",
                        "log_stream_name": "{instance_id}"
                    }
                ]
            }
        }
    }
}

这些方法将帮助你更好地监控服务状态、捕获错误并在服务异常时自动重启。

回到顶部