Gin 本身不提供全局并发限制能力,因其仅是 HTTP 路由引擎,不感知服务治理概念,也不拦截连接或调度 goroutine;真正的并发限制需在 http.Server 的 ConnState 层(控制连接数)或 handler 内用 semaphore(控制业务 goroutine 数)。

为什么 Gin 本身不提供全局并发限制能力
Gin 是一个 HTTP 路由和中间件引擎,不是服务治理框架,它不感知“服务实例”“注册中心”或“调用链路”,更不会主动限制本进程内同时处理的请求数。你看到的 gin.Engine 启动后接受的所有连接,只要操作系统没拒绝、Go 运行时没耗尽资源,就会被调度进 goroutine 执行——Gin 不做拦截,也不设闸门。
常见误解是给 gin.Use() 加个中间件就能“限并发”,但那只是对单个请求加超时或计数,无法阻止第 10001 个 goroutine 被创建。真正的并发限制必须落在更底层:HTTP server 的连接接纳层,或业务逻辑入口的协程池调度层。
用 net/http.Server 的 ConnState 实现连接级限流
最贴近“并发连接数”控制的做法,是接管 Gin 底层的 http.Server,利用其 ConnState 回调实时跟踪活跃连接数。Gin 的 *gin.Engine 实现了 http.Handler 接口,可以安全传入自定义 http.Server。
- 定义一个原子计数器:
var activeConns int64 - 在
ConnState中判断状态:if state == http.StateNew || state == http.StateActive时atomic.AddInt64(&activeConns, 1);state == http.StateClosed时减 1 - 当
atomic.LoadInt64(&activeConns) >= 10000,在ConnState里直接conn.Close()拒绝新连接(注意:这发生在 TLS 握手后、HTTP 请求解析前,比中间件早得多) - 不要依赖中间件做这事——中间件触发时连接已建立、goroutine 已启动、内存已分配
用 go-worker 或 semaphore 控制业务 goroutine 并发度
如果你真正想限制的是“同时执行业务逻辑的 goroutine 数量”(比如防止下游数据库被打爆),那应该在 handler 内部做信号量控制,而不是拦连接。
推荐用 golang.org/x/sync/semaphore:
var sem = semaphore.NewWeighted(100) // 最多 100 个并发业务处理
r.GET("/api/process", func(c *gin.Context) {
ctx := c.Request.Context()
if err := sem.Acquire(ctx, 1); err != nil {
c.Status(503)
return
}
defer sem.Release(1)
// 这里放实际业务逻辑,如调用 DB、RPC、AI 模型
c.JSON(200, gin.H{"ok": true})
})
- 这个限制是 per-request 的,和连接数无关;即使有 10 万长连接,真正干活的也最多 100 个
- 务必用
c.Request.Context()传入,否则超时或取消时Acquire不会响应 - 别用
time.Sleep模拟业务——它不释放 OS 线程,会卡死整个 goroutine 调度器
混用 Kratos 或 go-zero 时的并发控制优先级
如果你已在用 Kratos 或 go-zero 做微服务框架,就别在 Gin 层重复实现并发限制。它们的 transport 层(如 http.Server)已内置连接数、读写超时、最大 Header 大小等配置,且与注册中心联动(例如:自动下线过载实例)。
- Kratos 的
http.Server支持http.WithReadTimeout、http.WithWriteTimeout、http.WithIdleTimeout,这些比 Gin 中间件更早生效 - go-zero 的
RestConf中有MaxConns和MaxBytes字段,直接作用于底层 listener - 强行在 Gin 中间件里再套一层限流,会导致指标错乱(Prometheus 抓到的 QPS 是“进入 Gin”的数量,不是“真正执行的”)
- 最易忽略的一点:Kubernetes 的
livenessProbe如果配得太激进,可能在并发压测时误杀正在处理请求的 Pod


















