Skip to content
Go
Gin
服务器与部署

服务器与部署

本章覆盖 Gin 的服务器配置:自定义 HTTP 配置、Let’s Encrypt 自动 HTTPS、多服务运行、优雅关闭、健康检查,以及部署相关的环境变量。


自定义 HTTP 配置

router.Run() 使用的是默认的 HTTP 服务器。生产环境中需要自定义超时、请求头大小限制等:

func main() {
    router := gin.Default()

    s := &http.Server{
        Addr:           ":8080",
        Handler:        router,
        ReadTimeout:    10 * time.Second,  // 读取请求超时
        WriteTimeout:   10 * time.Second,  // 写入响应超时
        IdleTimeout:    120 * time.Second, // 空闲连接超时
        MaxHeaderBytes: 1 << 20,           // 1 MB 请求头限制
    }

    s.ListenAndServe()
}

常用超时推荐值

配置 推荐值 说明
ReadTimeout 5-10s 防止慢客户端占用连接
WriteTimeout 10-30s 根据业务响应时间调整
IdleTimeout 60-120s Keep-Alive 空闲连接超时
MaxHeaderBytes 1 MB 防止大请求头攻击

💡 合理设置超时可以防止 Slowloris 等慢速攻击,并在依赖服务故障时快速释放资源。


运行时自定义 JSON 编解码器

Gin 支持在不使用编译标签的情况下替换 JSON 库(如用 json-iterator 代替 encoding/json):

import (
    "github.com/gin-gonic/gin/codec/json"
    jsoniter "github.com/json-iterator/go"
)

type customJSON struct{}

func (customJSON) Marshal(v any) ([]byte, error)       { return jsoniter.Marshal(v) }
func (customJSON) Unmarshal(data []byte, v any) error  { return jsoniter.Unmarshal(data, v) }
func (customJSON) MarshalIndent(v any, prefix, indent string) ([]byte, error) {
    return jsoniter.MarshalIndent(v, prefix, indent)
}
func (customJSON) NewEncoder(w io.Writer) json.Encoder { return jsoniter.NewEncoder(w) }
func (customJSON) NewDecoder(r io.Reader) json.Decoder { return jsoniter.NewDecoder(r) }

func main() {
    json.API = customJSON{} // 替换默认 JSON 实现
    router := gin.Default()
    router.Run(":8080")
}

性能提示json-iterator 比标准库 encoding/json 快 2-3 倍,适合 JSON 序列化密集的场景。


支持 Let’s Encrypt

gin-gonic/autotls 提供自动 HTTPS,自动处理证书签发和续期:

基础用法

import "github.com/gin-gonic/autotls"

func main() {
    router := gin.Default()
    log.Fatal(autotls.Run(router, "example1.com", "example2.com"))
}

自定义证书管理器

import "golang.org/x/crypto/acme/autocert"

func main() {
    router := gin.Default()

    m := autocert.Manager{
        Prompt:     autocert.AcceptTOS,
        HostPolicy: autocert.HostWhitelist("example.com"),
        Cache:      autocert.DirCache("/var/www/.cache"), // 证书缓存目录
    }

    log.Fatal(autotls.RunWithManager(router, &m))
}

🚨 Let’s Encrypt 要求服务器可从公网通过 80 和 443 端口访问,这在本地开发环境不可用。


运行多个服务

使用 errgroup 在同一进程中运行多个 Gin 服务(如公网 API + 内部管理 API):

import "golang.org/x/sync/errgroup"

func router01() http.Handler {
    e := gin.New()
    e.GET("/", func(c *gin.Context) {
        c.JSON(200, gin.H{"message": "Public API"})
    })
    return e
}

func router02() http.Handler {
    e := gin.New()
    e.GET("/", func(c *gin.Context) {
        c.JSON(200, gin.H{"message": "Admin API"})
    })
    return e
}

func main() {
    var g errgroup.Group

    server01 := &http.Server{
        Addr:         ":8080",
        Handler:      router01(),
        ReadTimeout:  5 * time.Second,
        WriteTimeout: 10 * time.Second,
    }

    server02 := &http.Server{
        Addr:         ":8081",
        Handler:      router02(),
        ReadTimeout:  5 * time.Second,
        WriteTimeout: 10 * time.Second,
    }

    g.Go(func() error { return server01.ListenAndServe() })
    g.Go(func() error { return server02.ListenAndServe() })

    if err := g.Wait(); err != nil {
        log.Fatal(err)
    }
}

🚨 如果任一服务器启动失败(如端口被占用),g.Wait() 返回第一个错误,进程退出。


优雅关闭

当服务器收到终止信号时,立即关闭会丢弃所有进行中的请求。优雅关闭让正在处理的请求有时间完成。

使用 Go 1.8+ 内置的 Shutdown()

func main() {
    router := gin.Default()
    router.GET("/", func(c *gin.Context) {
        time.Sleep(5 * time.Second) // 模拟慢请求
        c.String(200, "Welcome Gin Server")
    })

    srv := &http.Server{
        Addr:    ":8080",
        Handler: router,
    }

    // 在 goroutine 中启动服务器
    go func() {
        if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
            log.Fatalf("listen: %s\n", err)
        }
    }()

    // 等待中断信号
    quit := make(chan os.Signal, 1)
    signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
    <-quit
    log.Println("Shutting down server...")

    // 给 5 秒完成正在处理的请求
    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")
}

优雅关闭流程

1. 收到 SIGINT/SIGTERM 信号
2. 停止接受新连接
3. 等待进行中的请求完成(最多 5 秒超时)
4. 关闭所有空闲连接
5. 进程退出

第三方库

说明
endless 零停机重启(fork + exec)
grace Facebook 开源的优雅重启
manners 轻量优雅关闭

💡 Go 1.8+ 推荐使用内置的 srv.Shutdown(),无需第三方依赖。如果需要零停机重启(如热更新二进制),再考虑 endless 或 grace。


健康检查

健康检查端点允许负载均衡器和 Kubernetes 等编排系统验证应用状态。

基本健康检查

r.GET("/healthz", func(c *gin.Context) {
    c.JSON(200, gin.H{"status": "ok"})
})

存活性 vs 就绪性(Kubernetes)

var isReady atomic.Bool

// 存活性探针:进程是否存活?
r.GET("/healthz", func(c *gin.Context) {
    c.JSON(200, gin.H{"status": "alive"})
})

// 就绪性探针:是否可以接收请求?
r.GET("/readyz", func(c *gin.Context) {
    if !isReady.Load() {
        c.JSON(503, gin.H{"status": "not ready"})
        return
    }
    // 检查关键依赖
    if err := db.Ping(); err != nil {
        c.JSON(503, gin.H{"status": "not ready", "reason": "database unreachable"})
        return
    }
    c.JSON(200, gin.H{"status": "ready"})
})

// 初始化完成后标记就绪
isReady.Store(true)
探针类型 失败后果 检查内容
存活性 (Liveness) 重启容器 进程是否卡死/死锁
就绪性 (Readiness) 暂时停止流量 依赖是否就绪(DB、Redis)

Kubernetes 配置

livenessProbe:
  httpGet:
    path: /healthz
    port: 8080
  initialDelaySeconds: 5
  periodSeconds: 10
readinessProbe:
  httpGet:
    path: /readyz
    port: 8080
  initialDelaySeconds: 5
  periodSeconds: 5

多依赖健康检查

type HealthChecker struct {
    DB    *sql.DB
    Redis *redis.Client
}

func (h *HealthChecker) CheckHealth(c *gin.Context) {
    ctx, cancel := context.WithTimeout(c.Request.Context(), 2*time.Second)
    defer cancel()

    checks := gin.H{}
    healthy := true

    if err := h.DB.PingContext(ctx); err != nil {
        checks["database"] = gin.H{"status": "unhealthy", "error": err.Error()}
        healthy = false
    } else {
        checks["database"] = gin.H{"status": "healthy"}
    }

    if err := h.Redis.Ping(ctx).Err(); err != nil {
        checks["redis"] = gin.H{"status": "unhealthy", "error": err.Error()}
        healthy = false
    } else {
        checks["redis"] = gin.H{"status": "healthy"}
    }

    status := 200
    if !healthy {
        status = 503
    }
    c.JSON(status, gin.H{"checks": checks})
}

部署配置

环境变量

变量 说明
PORT 使用 router.Run()(无参数)时监听的端口
GIN_MODE 运行模式:debug / release / test

常用启动配置

// 监听所有接口,默认 8080;可通过 PORT 环境变量覆盖
router.Run()

// 指定地址和端口
router.Run("192.168.1.100:8080")

// 仅指定端口(所有接口)
router.Run(":8080")

// 设置可信代理
router.SetTrustedProxies([]string{"10.0.0.0/8"})

Docker 部署建议

FROM golang:1.25-alpine AS builder
WORKDIR /app
COPY . .
RUN CGO_ENABLED=0 go build -o server .

FROM alpine:latest
RUN apk --no-cache add ca-certificates
COPY --from=builder /app/server .
ENV GIN_MODE=release
EXPOSE 8080
CMD ["./server"]

💡 Docker 中设置 GIN_MODE=release 以禁用调试输出,减少日志噪音。