此示例演示了如何使用 server.Close()
方法在 Gin 服务器中实现优雅关闭。
server.go
:处理优雅关闭的主服务器实现文件。安装所需依赖:
go get -u github.com/gin-gonic/gin
运行服务器:
go run server.go
访问服务器地址:http://localhost:8080/
。
要触发优雅关闭,发送中断信号(例如在终端中按 Ctrl+C
)。服务器将在关闭前完成所有正在处理的请求。
//go:build go1.8
// +build go1.8
package main
import (
"log"
"net/http"
"os"
"os/signal"
"time"
"github.com/gin-gonic/gin"
)
func main() {
router := gin.Default()
router.GET("/", func(c *gin.Context) {
time.Sleep(5 * time.Second)
c.String(http.StatusOK, "Welcome Gin Server")
})
server := &http.Server{
Addr: ":8080",
Handler: router,
}
quit := make(chan os.Signal, 1)
signal.Notify(quit, os.Interrupt)
go func() {
<-quit
log.Println("receive interrupt signal")
if err := server.Close(); err != nil {
log.Fatal("Server Close:", err)
}
}()
if err := server.ListenAndServe(); err != nil {
if err == http.ErrServerClosed {
log.Println("Server closed under request")
} else {
log.Fatal("Server closed unexpect")
}
}
log.Println("Server exiting")
}
server.Close()
方法优雅地关闭服务器。
报警告
misuse of unbuffered os.Signal channel as argument to signal.Notify sigchanyzer(default)
修复如下
简要解释报错原因:
未缓冲的 os.Signal
channel 用于 signal.Notify
,可能导致信号丢失。
修复建议:
将 quit
channel 改为带缓冲的 channel,例如 make(chan os.Signal, 1)
,以确保信号能被正确接收。
修复后的代码(仅展示修改部分):
// 原代码:
// quit := make(chan os.Signal)
// 修改为:
quit := make(chan os.Signal, 1)
signal.Notify(quit, os.Interrupt)
signal.Notify
接收前信号到达而被丢弃,避免潜在的阻塞或行为异常问题。这是 Go 中处理系统信号的标准做法。本目录包含使用上下文(Context)和不使用上下文两种方式实现Gin服务器优雅关闭的示例。
notify-with-context/
: 使用上下文实现优雅关闭的示例notify-without-context/
: 不使用上下文实现优雅关闭的示例安装所需依赖:
go get -u github.com/gin-gonic/gin
运行服务器:
go run notify-with-context/server.go
访问服务器地址:http://localhost:8080/
要触发优雅关闭,发送中断信号(如在终端中按Ctrl+C
)。服务器将在关闭前完成所有正在处理的请求。
安装所需依赖:
go get -u github.com/gin-gonic/gin
运行服务器:
go run notify-without-context/server.go
访问服务器地址:http://localhost:8080/
要触发优雅关闭,发送中断信号(如在终端中按Ctrl+C
)。服务器将在关闭前完成所有正在处理的请求。
//go:build go1.8
// +build go1.8
package main
import (
"context"
"log"
"net/http"
"os"
"os/signal"
"syscall"
"time"
"github.com/gin-gonic/gin"
)
func main() {
router := gin.Default()
router.GET("/", func(c *gin.Context) {
time.Sleep(5 * time.Second)
c.String(http.StatusOK, "Welcome Gin Server")
})
srv := &http.Server{
Addr: ":8080",
Handler: router,
}
// Initializing the server in a goroutine so that
// it won't block the graceful shutdown handling below
go func() {
if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
log.Fatalf("listen: %s\n", err)
}
}()
// Wait for interrupt signal to gracefully shutdown the server with
// a timeout of 5 seconds.
quit := make(chan os.Signal, 1)
// kill (no param) default send syscall.SIGTERM
// kill -2 is syscall.SIGINT
// kill -9 is syscall.SIGKILL but can't be catch, so don't need add it
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
// 从quit中接收值,忽略结果
<-quit
log.Println("Shutting down server...")
// The context is used to inform the server it has 5 seconds to finish
// the request it is currently handling
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
if err := srv.Shutdown(ctx); err != nil {
log.Fatal("Server forced to shutdown: ", err)
}
log.Println("Server exiting")
}
不使用上下文示例
5
秒的延迟server.Shutdown()
方法优雅地关闭服务器//go:build go1.16
package main
import (
"context"
"log"
"net/http"
"os/signal"
"syscall"
"time"
"github.com/gin-gonic/gin"
)
func main() {
// Create context that listens for the interrupt signal from the OS.
ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM)
defer stop()
router := gin.Default()
router.GET("/", func(c *gin.Context) {
time.Sleep(10 * time.Second)
c.String(http.StatusOK, "Welcome Gin Server")
})
srv := &http.Server{
Addr: ":8080",
Handler: router,
}
// Initializing the server in a goroutine so that
// it won't block the graceful shutdown handling below
go func() {
if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
log.Fatalf("listen: %s\n", err)
}
}()
// Listen for the interrupt signal.
<-ctx.Done()
// Restore default behavior on the interrupt signal and notify user of shutdown.
stop()
log.Println("shutting down gracefully, press Ctrl+C again to force")
// The context is used to inform the server it has 5 seconds to finish
// the request it is currently handling
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
if err := srv.Shutdown(ctx); err != nil {
log.Fatal("Server forced to shutdown: ", err)
}
log.Println("Server exiting")
}
使用上下文示例
10
秒的延迟