文章目录
优雅关闭示例 - Close 方法
此示例演示了如何使用 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")
}
代码说明
- 服务器初始化时包含一个简单路由,该路由模拟了 5 秒的延迟。
- 创建了一个通道来监听中断信号。
- 当接收到中断信号时,使用
server.Close()
方法优雅地关闭服务器。
如果去掉代码中的数字1,会发生什么
报警告
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)
- 说明:
使用带缓冲的 channel 可防止在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
)。服务器将在关闭前完成所有正在处理的请求。
代码 notify-without-context-server.go
//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()
方法优雅地关闭服务器
代码 notify-with-context-server.go
//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
秒的延迟 - 创建了一个监听中断信号的上下文
- 当接收到中断信号时,使用上下文优雅地关闭服务器