服务器与部署
本章覆盖 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以禁用调试输出,减少日志噪音。