Golang Web服务器突然停止运行的原因及解决方法

Golang Web服务器突然停止运行的原因及解决方法 我正在寻找一些建议,以缩小排查范围,找出我的Go服务器在运行一小段时间后停止的原因。

http://94.237.92.101:5050

我的Go代码很简单。不涉及SQL。只有HTML模板。

package main

import (
	"fmt"
	"html/template"
	"net/http"
	"strings"
)

func main() {
	http.HandleFunc("/", page)
	http.ListenAndServe(":5050", nil)
}

var tpl *template.Template

func init() {
	tpl = template.Must(template.ParseGlob("public/templates/*.html"))
	http.Handle("/img/", http.StripPrefix("/img/", http.FileServer(http.Dir("./public/img"))))
	http.Handle("/css/", http.StripPrefix("/css/", http.FileServer(http.Dir("./public/css"))))
	http.Handle("/icn/", http.StripPrefix("/icn/", http.FileServer(http.Dir("./public/icn"))))
	http.Handle("/js/", http.StripPrefix("/js/", http.FileServer(http.Dir("./public/js"))))
}

func page(w http.ResponseWriter, r *http.Request) {
	path := strings.Trim(r.URL.Path, "/")

	fmt.Println(path)
	switch path {
	case "favicon.ico":
		http.ServeFile(w, r, "/static/favicon.ico")
	case "norec":
		tpl.ExecuteTemplate(w, "norec.html", "")
	default:
		tpl.ExecuteTemplate(w, "home.html", "")
	}
}

https://play.golang.org/p/sFs4CqZ3Olt

有什么线索可以找到原因吗?HTML、CSS和Javascript验证都没有错误。


更多关于Golang Web服务器突然停止运行的原因及解决方法的实战教程也可以访问 https://www.itying.com/category-94-b0.html

21 回复

正如我所说,无论原帖作者使用什么进行日志记录

更多关于Golang Web服务器突然停止运行的原因及解决方法的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


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

它是直接退出,还是出现了恐慌或其他情况?

正如我所说,http.ListenAndServe 永远不会 返回 nil

NobbZ:

log.Fatal 总是会记录到进程的 stderr /fd:2

那么我该如何读取这个日志呢?var/log/stderr 是空的。

再次声明,我对 webmin 的功能一无所知。你需要自行查明它是否将进程的 stdoutstderr 持久化存储在某处。

skillian:

它只是退出了还是出现了恐慌或其他情况?

从服务器终端:

已终止 命令失败,退出状态为 36608

NobbZ:

你真的应该修改这个。

err := ListenAndServe(":5050", nil)
fmt.Println("%v", err)

我得到:

undefined: ListenAndServe

解决方案?

Sibert:

undefined: ListenAndServe

解决方案?

阅读错误信息和代码… 我忘记限定调用了,它当然是 http.ListenAndServe

我在多个地方读到,将 ListenAndServe 放在 log.Fatal 中是一个好主意,例如:

log.Fatal(http.ListenAndServe( “:8080” , nil))

正如这里所述。

NobbZ:

err := ListenAndServe(":5050", nil)
fmt.Println("%v", err)

终端没有收到来自此代码的任何消息。服务器只是静默地关闭了。

同一服务器上运行的其他 Go Web 服务器都没有任何问题。是否有任何 JavaScript 可能导致这种情况?

还有其他建议吗?

NobbZ:

我忘了限定调用,当然是 http.ListenAndServe

谢谢,但是不应该也有一个 if 语句吗?还是不需要?像这样:

err := http.ListenAndServe(":5050", nil)
if err != nil {
	fmt.Println("%v", err)
}

我不确定您是如何管理您的服务的。但我真的不相信它会静默退出。如果确实如此,您需要检查它的退出代码,也许它被系统通过信号杀死了?

您是否检查过系统日志,看看是否是 OOM 杀手导致的?

尝试一下这个高票答案:“Finding which process was killed by Linux OOM killer - Stack Overflow” https://stackoverflow.com/questions/624857/finding-which-process-was-killed-by-linux-oom-killer

我不了解 webmin,但 fmt.Println 会输出到标准输出。不确定 webmin 会如何处理标准输出的内容。

请确保使用你通常有效的日志记录机制。

同时确保你的所有端点都正确记录了日志。

详细的日志记录可能是帮助你调试此问题的唯一方法。

此外,你的服务管理器应该能够识别故障并重启你的服务,如果它不能,那么应该更换它。Systemd 在这方面做得很好,通常能正确处理。

再次强调,当你的服务退出时,它的退出代码是什么?

Sibert:

Command failed with exit status 36608

这是一个垃圾值。在 Linux 上,退出码只有 8 位…

不过,如果我查看它的各个字节,我们得到的是 143 0

如果我们假设可以忽略 0 字节,那么退出码就是 143,这在 Linux 上通常意味着“被 SIGTERM 信号关闭”。

当你在终端使用 kill $pid 命令时,默认就会发生这种情况。现在你需要找出是什么发送了 SIGTERM 信号以及原因。

Sibert:

http.ListenAndServe(":5050", nil)

你真的应该修改这个。

err := ListenAndServe(":5050", nil)
fmt.Println("%v", err)

(或者使用你习惯的其他日志记录方式)

这样你的日志里就会留下一些信息。

http.ListenAndServe 的返回值保证永远不会是 nil,所以如果它返回了,就意味着发生了一个被你忽略的错误。

如果你确实没有在日志中看到这样的错误,那么问题就更深层次了,可能是进程被外部杀死了。

Sibert: 如何让这个日志打印到 var/log/stderr

我不太确定你具体指的是什么。但如果它是一个文件,那么打开它并将你的日志写入其中。具体如何操作取决于你使用的日志记录库。

log.Fatal 总是会将日志记录到进程的 stderr/fd:2,然后退出程序。

Systemd 和 Docker 通常会获取程序的 stdoutstderr,并将它们写入各自的日志中,之后你可以按服务/容器来浏览这些日志。

我不清楚你的 Webmin 是如何处理这类事情的。

NobbZ:

我不确定您是如何管理您的服务的。

通过Webmin管理的Debian 10。var/log/message 中除了正常消息外什么都没有:rsyslogd was HUPed,并且 var/log/stderr 是空的。

除了正在运行的 http.Server 之外没有其他进程,并且它由于某种原因反复终止。无论是 fmt.Println(err) 还是 log.Fatal 都没有报告任何问题。内存使用率没有超过 30%。

HTML、CSS 和 Javascript 都已通过验证。同一服务器上的其他 Go 服务器在其他端口上运行完美。

我已经激活了防火墙,但到目前为止没有区别。

有什么线索可以找到原因吗?

NobbZ: 另外,当你的服务退出时,它的退出代码是什么?

已终止 命令失败,退出状态码为 36608

NobbZ: 同时确保你的所有端点都正确记录日志。

Go Playground

Go Playground - The Go Programming Language

NobbZ: 详尽的日志记录可能是帮助你调试此问题的唯一方法。

我如何让这个日志打印到 var/log/stderr

log.Fatal(http.ListenAndServe(":8080", nil))

NobbZ: 你需要自己弄清楚,它是否将进程的 stdoutstderr 持久化到了某个地方。

这是一个 Debian 系统的问题。

  1. 首先我创建了一个 main.exe Go Playground - The Go Programming Language

  2. 然后在终端中将其重定向到 /var/log/messages

    ./main 2>> /var/log/messages
    
  3. 最后我执行了 main

    cd /logtest ./main
    

Aug 18 00:00:05 …rsyslogd was HUPed Message

问题是如何在 Go 代码内部实现 #2(重定向到 /var/log/messages)?

或者更好的做法是重定向到 stderr?

服务器突然停止通常由以下几个原因导致:

1. panic未恢复

最常见的原因是未处理的panic。添加recovery中间件:

func recoveryMiddleware(next http.HandlerFunc) http.HandlerFunc {
    return func(w http.ResponseWriter, r *http.Request) {
        defer func() {
            if err := recover(); err != nil {
                fmt.Printf("Panic recovered: %v\n", err)
                http.Error(w, "Internal Server Error", http.StatusInternalServerError)
            }
        }()
        next(w, r)
    }
}

func main() {
    http.HandleFunc("/", recoveryMiddleware(page))
    http.ListenAndServe(":5050", nil)
}

2. 模板执行错误

模板执行失败可能导致panic:

func page(w http.ResponseWriter, r *http.Request) {
    path := strings.Trim(r.URL.Path, "/")
    fmt.Println(path)
    
    switch path {
    case "favicon.ico":
        http.ServeFile(w, r, "/static/favicon.ico")
    case "norec":
        if err := tpl.ExecuteTemplate(w, "norec.html", ""); err != nil {
            fmt.Printf("Template error: %v\n", err)
            http.Error(w, "Internal Server Error", http.StatusInternalServerError)
        }
    default:
        if err := tpl.ExecuteTemplate(w, "home.html", ""); err != nil {
            fmt.Printf("Template error: %v\n", err)
            http.Error(w, "Internal Server Error", http.StatusInternalServerError)
        }
    }
}

3. 文件路径问题

静态文件路径可能导致panic:

func init() {
    var err error
    tpl, err = template.ParseGlob("public/templates/*.html")
    if err != nil {
        panic(fmt.Sprintf("Template parsing error: %v", err))
    }
    
    // 检查目录是否存在
    dirs := []string{"./public/img", "./public/css", "./public/icn", "./public/js"}
    for _, dir := range dirs {
        if _, err := os.Stat(dir); os.IsNotExist(err) {
            fmt.Printf("Warning: directory %s does not exist\n", dir)
        }
    }
    
    http.Handle("/img/", http.StripPrefix("/img/", http.FileServer(http.Dir("./public/img"))))
    http.Handle("/css/", http.StripPrefix("/css/", http.FileServer(http.Dir("./public/css"))))
    http.Handle("/icn/", http.StripPrefix("/icn/", http.FileServer(http.Dir("./public/icn"))))
    http.Handle("/js/", http.StripPrefix("/js/", http.FileServer(http.Dir("./public/js"))))
}

4. 添加详细日志

启用详细日志记录:

func loggingMiddleware(next http.HandlerFunc) http.HandlerFunc {
    return func(w http.ResponseWriter, r *http.Request) {
        start := time.Now()
        fmt.Printf("[%s] %s %s\n", r.Method, r.URL.Path, r.RemoteAddr)
        
        rw := &responseWriter{ResponseWriter: w, statusCode: http.StatusOK}
        next(rw, r)
        
        fmt.Printf("[%d] %s %s completed in %v\n", 
            rw.statusCode, r.Method, r.URL.Path, time.Since(start))
    }
}

type responseWriter struct {
    http.ResponseWriter
    statusCode int
}

func (rw *responseWriter) WriteHeader(code int) {
    rw.statusCode = code
    rw.ResponseWriter.WriteHeader(code)
}

func main() {
    http.HandleFunc("/", loggingMiddleware(recoveryMiddleware(page)))
    
    fmt.Println("Server starting on :5050")
    if err := http.ListenAndServe(":5050", nil); err != nil {
        fmt.Printf("Server error: %v\n", err)
    }
}

5. 检查系统资源

添加资源监控:

func monitorResources() {
    go func() {
        for {
            var m runtime.MemStats
            runtime.ReadMemStats(&m)
            fmt.Printf("Alloc = %v MiB, TotalAlloc = %v MiB, Sys = %v MiB, NumGC = %v\n",
                m.Alloc/1024/1024, m.TotalAlloc/1024/1024, m.Sys/1024/1024, m.NumGC)
            time.Sleep(30 * time.Second)
        }
    }()
}

func main() {
    monitorResources()
    http.HandleFunc("/", loggingMiddleware(recoveryMiddleware(page)))
    
    fmt.Println("Server starting on :5050")
    if err := http.ListenAndServe(":5050", nil); err != nil {
        fmt.Printf("Server error: %v\n", err)
    }
}

6. favicon.ico路径问题

修正favicon处理:

func page(w http.ResponseWriter, r *http.Request) {
    path := strings.Trim(r.URL.Path, "/")
    fmt.Println("Request path:", path)
    
    switch path {
    case "favicon.ico":
        // 使用相对路径或绝对路径
        faviconPath := "./public/static/favicon.ico"
        if _, err := os.Stat(faviconPath); os.IsNotExist(err) {
            http.NotFound(w, r)
            return
        }
        http.ServeFile(w, r, faviconPath)
    case "norec":
        if err := tpl.ExecuteTemplate(w, "norec.html", ""); err != nil {
            fmt.Printf("Template error: %v\n", err)
            http.Error(w, "Internal Server Error", http.StatusInternalServerError)
        }
    default:
        if err := tpl.ExecuteTemplate(w, "home.html", ""); err != nil {
            fmt.Printf("Template error: %v\n", err)
            http.Error(w, "Internal Server Error", http.StatusInternalServerError)
        }
    }
}

这些修改会帮助捕获错误并记录服务器状态,从而确定停止的具体原因。

回到顶部