You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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代理,可通过以下配置实现错误记录与客户端区分:

  1. 自定义日志格式,增加连接状态与客户端标识:
log_format custom_log '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$connection_reset"';
  1. 在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.18 14:13:13