Golang服务意外停止的排查与解决方案
Golang服务意外停止的排查与解决方案 我正在开发一个集成了用Golang开发的REST API的应用程序,使用了Gin框架,并将API部署在AWS EC2实例上。
我运行API的步骤如下:
- 将全部文件上传到src目录
- 执行go get和go install brbapi(brbapi是包/文件夹名称)
- 启动tmux并运行"brbapi"(使用tmux保持API持续运行)
这样就能启动服务,我可以使用REST API,但有时(每天一次)服务会意外停止,没有任何错误或日志。
有没有什么方法可以保持服务持续运行,并添加一些日志记录方法来获取故障日志?
tmux 并不适合用于运行服务,应该使用 systemd 并设置正确的重启策略。
除此之外,我也有一些服务在生产环境中运行,它们全部 7×24 小时不间断运行,从未出现意外停止或退出的情况。
我的推测是,你在应用程序中触发了某些直接调用 os.Exit 的路径。
感谢Nobbz的回复,我检查了我的代码,os.Exists并没有在任何地方使用。我正按照你的建议尝试使用systemd,但不知道从哪里开始,因为我是Windows用户,对Ubuntu和命令不太熟悉。你能推荐一些教程吗?
如果你能解释一下你是如何让服务7x24小时运行的,那将会很有帮助。
我首先会找出它停止的原因,这个问题肯定可以修复,你的 Go 守护进程应该能够长时间运行而不会停止/崩溃。
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}"
}
]
}
}
}
}
这些方法将帮助你更好地监控服务状态、捕获错误并在服务异常时自动重启。


