Gin框架Go服务偶发connection reset by peer问题排查与解决
偶发“connection reset by peer”错误排查与解决(基于Go Gin框架)
问题背景
- 基于Go Gin框架开发多REST API互调服务,每日偶发数次“connection reset by peer”错误
- 已排除资源不足、防火墙问题,移除Nginx反向代理改为直连后错误仍存在
- 当前仅能通过客户端日志感知错误,需实现服务端/Nginx侧的错误记录,同时统计非自有客户端的报错情况
一、服务端(Gin框架)错误记录与统计方案
1. 自定义中间件捕获连接错误
编写Gin中间件,在请求生命周期末尾捕获底层连接错误,同时通过客户端标识(IP、User-Agent)区分自有/非自有客户端:
package middleware import ( "log" "net" "net/http" "strings" "github.com/gin-gonic/gin" ) func ConnectionErrorLogger() gin.HandlerFunc { return func(c *gin.Context) { c.Next() // 检查是否触发连接重置错误 if connErr, ok := c.Request.Context().Err().(net.Error); ok { if strings.Contains(connErr.Error(), "connection reset by peer") { clientIP := c.ClientIP() userAgent := c.GetHeader("User-Agent") // 自定义规则判断非自有客户端(示例:通过User-Agent标识) isExternalClient := !strings.Contains(userAgent, "YourInternalServiceUA") log.Printf("[ConnReset] ClientIP: %s, UA: %s, IsExternal: %t, Path: %s", clientIP, userAgent, isExternalClient, c.Request.URL.Path) // 可扩展:将外部客户端报错统计写入监控系统(如Prometheus) if isExternalClient { externalConnResetCounter.Inc() } } } } }
注册全局中间件:
r := gin.Default() r.Use(middleware.ConnectionErrorLogger())
2. 前置处理:确保请求体被正确处理
未正确读取/关闭请求体是连接复用失败的潜在诱因,可在中间件中强制处理:
func EnsureRequestBodyProcessed() gin.HandlerFunc { return func(c *gin.Context) { defer func() { // 读取剩余请求体并关闭,避免连接占用 _, _ = io.Copy(io.Discard, c.Request.Body) _ = c.Request.Body.Close() }() c.Next() } }
二、Nginx侧错误记录与统计
若后续恢复Nginx代理,可通过以下配置实现错误记录与客户端区分:
- 自定义日志格式,增加连接状态与客户端标识:
log_format custom_log '$remote_addr - $remote_user [$time_local] "$request" ' '$status $body_bytes_sent "$http_referer" ' '"$http_user_agent" "$connection_reset"';
- 在server/location块中配置错误捕获与分日志记录:
server { # 基础配置... access_log /var/log/nginx/access.log custom_log; # 标记连接重置事件 set $connection_reset ""; if ($connection = "close") { set $connection_reset "connection_reset_by_peer"; } # 通过User-Agent区分自有/非自有客户端 map $http_user_agent $is_external { default 1; "~*YourInternalServiceUA" 0; } # 单独记录外部客户端的连接重置错误 if ($is_external = 1) { access_log /var/log/nginx/external_reset.log custom_log; } }
三、最终根本解决方案
经过排查,偶发“connection reset by peer”的核心原因是部分请求未被正确读取并关闭请求体,导致HTTP长连接无法复用,最终触发连接重置。
通过添加全局Gin中间件,强制在请求结束后处理请求体,彻底解决该问题:
package middleware import ( "io" "github.com/gin-gonic/gin" ) func ForceRequestBodyClose() gin.HandlerFunc { return func(c *gin.Context) { defer func() { // 读取所有剩余请求体内容到废弃流,确保连接可正常复用 if c.Request.Body != nil { _, _ = io.Copy(io.Discard, c.Request.Body) _ = c.Request.Body.Close() } }() c.Next() } }
注册为全局中间件,覆盖所有请求:
r := gin.New() r.Use(middleware.ForceRequestBodyClose()) // 其他中间件与路由注册...
内容的提问来源于stack exchange,提问作者Oliver Šmakal
相关产品推荐
相关产品推荐

