第26章:高性能与优雅关闭
本章目标
学完本章后,你将能够:
- 理解 Gin 高性能的原因
- 实现服务优雅关闭(Graceful Shutdown)
- 配置超时控制
- 在生产环境安全地停止和重启服务
前置知识
- 需要先学习:第03章(路由基础)、第08章(路由树)
26.1 Gin 为什么快
- Radix Tree 路由:O(路径长度) 匹配,不随路由数量增长
- 零内存分配:httprouter 对路径参数做了零分配优化
- 轻量设计:只做路由和中间件,没有 ORM、模板引擎等重组件
性能数据(官方 benchmark):
Gin: ~50,000 QPS/ms
Echo: ~48,000 QPS/ms
net/http: ~10,000 QPS/ms26.2 优雅关闭
直接 Ctrl+C 停止服务会中断正在处理的请求。优雅关闭让你:
- 停止接收新请求
- 处理完已有请求
- 关闭数据库连接等资源
go
package main
import (
"context"
"log"
"net/http"
"os"
"os/signal"
"syscall"
"time"
"github.com/gin-gonic/gin"
)
func main() {
r := gin.Default()
r.GET("/", func(c *gin.Context) {
time.Sleep(5 * time.Second) // 模拟慢请求
c.String(200, "done")
})
// 创建 http.Server
srv := &http.Server{
Addr: ":8080",
Handler: r,
}
// 在 goroutine 中启动服务
go func() {
if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
log.Fatalf("启动失败: %v", err)
}
}()
log.Println("服务已启动 :8080")
// 等待中断信号
quit := make(chan os.Signal, 1)
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
<-quit // 阻塞,直到收到 Ctrl+C
log.Println("正在关闭服务...")
// 优雅关闭:5 秒超时
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
if err := srv.Shutdown(ctx); err != nil {
log.Fatalf("关闭失败: %v", err)
}
log.Println("服务已关闭")
}26.3 超时控制
go
srv := &http.Server{
Addr: ":8080",
Handler: r,
ReadTimeout: 10 * time.Second, // 读取请求超时
WriteTimeout: 10 * time.Second, // 写响应超时
IdleTimeout: 60 * time.Second, // 空闲连接超时
}26.4 性能优化建议
| 建议 | 说明 |
|---|---|
用 gin.New() + 自定义中间件 | 按需加载,不用 Default |
| 数据库连接池配置 | MaxOpenConns/MaxIdleConns(见 GORM 教程) |
| 接口返回只查需要的字段 | 不要 SELECT * |
| 合理使用缓存 | Redis 缓存热点数据 |
| Gzip 压缩 | 传输量减少 70%+ |
本章小结
- Gin 的快来自 Radix Tree + 零内存分配 + 轻量设计
- 优雅关闭 = 等正在处理的请求完成再退出
signal.Notify监听 SIGINT/SIGTERMsrv.Shutdown(ctx)平滑关闭- 生产必须配置 ReadTimeout/WriteTimeout
练习题
- 将你的 Gin 服务改造为优雅关闭模式。
- 测试:启动服务,发一个慢请求,同时 Ctrl+C,观察慢请求是否能正常完成。
- 配置
ReadTimeout和WriteTimeout,测试超时行为。 - (思考题)优雅关闭的 5 秒超时时间到了但请求还没处理完,会发生什么?