Gin优雅关闭 graceful-shutdown

发布于:2025-05-09 ⋅ 阅读:(11) ⋅ 点赞:(0)

优雅关闭示例 - Close 方法

此示例演示了如何使用 server.Close() 方法在 Gin 服务器中实现优雅关闭。

项目结构

  • server.go:处理优雅关闭的主服务器实现文件。

使用方法

  1. 安装所需依赖:

    go get -u github.com/gin-gonic/gin
    
  2. 运行服务器:

    go run server.go
    
  3. 访问服务器地址:http://localhost:8080/

  4. 要触发优雅关闭,发送中断信号(例如在终端中按 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)

修复如下

  1. 简要解释报错原因:
    未缓冲的 os.Signal channel 用于 signal.Notify,可能导致信号丢失。

  2. 修复建议:
    quit channel 改为带缓冲的 channel,例如 make(chan os.Signal, 1),以确保信号能被正确接收。

  3. 修复后的代码(仅展示修改部分):

// 原代码:
// quit := make(chan os.Signal)
// 修改为:
quit := make(chan os.Signal, 1)

signal.Notify(quit, os.Interrupt)
  1. 说明:
    使用带缓冲的 channel 可防止在 signal.Notify 接收前信号到达而被丢弃,避免潜在的阻塞或行为异常问题。这是 Go 中处理系统信号的标准做法。

优雅关闭示例

本目录包含使用上下文(Context)和不使用上下文两种方式实现Gin服务器优雅关闭的示例。

项目结构

  • notify-with-context/: 使用上下文实现优雅关闭的示例
  • notify-without-context/: 不使用上下文实现优雅关闭的示例

使用方法

使用上下文通知

  1. 安装所需依赖:

    go get -u github.com/gin-gonic/gin
    
  2. 运行服务器:

    go run notify-with-context/server.go
    
  3. 访问服务器地址:http://localhost:8080/

  4. 要触发优雅关闭,发送中断信号(如在终端中按Ctrl+C)。服务器将在关闭前完成所有正在处理的请求。

不使用上下文通知

  1. 安装所需依赖:

    go get -u github.com/gin-gonic/gin
    
  2. 运行服务器:

    go run notify-without-context/server.go
    
  3. 访问服务器地址:http://localhost:8080/

  4. 要触发优雅关闭,发送中断信号(如在终端中按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秒的延迟
  • 创建了一个监听中断信号的上下文
  • 当接收到中断信号时,使用上下文优雅地关闭服务器

网站公告

今日签到

点亮在社区的每一天
去签到