Golang并发时报错connection Reset by Peer
Posted March 2, 2023 by clannadzsy ‐ 1 min read
Go 并发请求中出现 connection reset by peer,说明连接被对端重置;对端也可能是代理、负载均衡器等中间节点。仅凭这段错误文字,无法判定是服务端策略、连接复用、应用行为还是某项资源限制。它不等同于本地文件描述符耗尽,不能直接得出“程序没问题,只需提高系统限制”的结论。
先保留完整错误与请求条件
记录操作是拨号、读取还是写入,以及目标地址、并发数、超时、连接复用方式和服务端对应时间的日志。检查响应体是否关闭、客户端是否重复创建、请求是否有界,再做单变量对比。http.Client 和 Transport 适合复用;成功返回响应时应及时关闭 resp.Body,能读到 EOF 并关闭时才有利于连接复用。
参考 Go 1.23.3 http.Client、Transport 与 net.OpError。自动重试还必须考虑请求是否幂等,不能无条件重复有副作用的请求。
区分重置与本地资源耗尽
适用环境:下面只演示 Go 标准库错误链的识别方式,在 Go 1.23.3、Windows/amd64 验证;不发起网络请求,也不声称复现了服务器故障。保存为 main.go,运行 go run main.go。
package main
import (
"errors"
"fmt"
"net"
"syscall"
)
func main() {
// 人工构造错误链,验证分类逻辑;实际排查时替换为请求返回的 err。
reset := &net.OpError{Op: "read", Net: "tcp", Err: syscall.ECONNRESET}
exhausted := fmt.Errorf("open socket: %w", syscall.EMFILE)
fmt.Println(errors.Is(reset, syscall.ECONNRESET))
fmt.Println(errors.Is(exhausted, syscall.EMFILE))
var operation *net.OpError
if errors.As(reset, &operation) {
fmt.Println(operation.Op, operation.Net)
}
}
期望输出为 true、true、read tcp。错误码和包装形式与平台、调用路径有关;例如 Windows 的套接字错误可能使用 Winsock 错误码,应检查真实错误链,不能只匹配本例构造的一个值。errors.Is、errors.As 的用法见 Go 官方 errors 文档。
Linux 文件描述符限制只是排查分支
原笔记中的 /etc/security/limits.conf、nofile、nproc 只适用于相应的 Linux 登录及服务管理环境。nofile 约束文件描述符,nproc 是另一项进程/线程相关限制;root 用户的默认值并非所有机器都固定为 4096。
只有实际错误与进程占用情况表明文件描述符不足时,才检查并调整启动该程序的用户或服务限制。交互式 shell 可先用 ulimit -Sn、ulimit -Hn 查看自身的软硬限制,但服务进程可能由 systemd、容器或其他启动器设置,shell 的值不代表运行中服务的实际限制。
sysctl -p 读取的是 sysctl 参数配置,不会把 limits.conf 的变更应用到已有进程。修改登录限制通常需要重新建立相应会话,服务需要按其启动器配置并重启,再核对进程的实际限制。不要把提高 nofile 当作所有连接重置的通用修复,更不要直接复制针对 root 的无限制配置。
本次只验证上述无网络错误分类程序,未在 Linux 服务或真实远端上验证限制调整;最终原因仍需结合发生故障的客户端、服务端和网络证据判断。