net/rpc 仅适用于轻量级同构Go场景,因gob序列化限制、无连接池、错误透传隐式等缺陷,生产环境应选用gRPC或Twirp。

Go 标准库的 net/rpc 足够支撑轻量级、同构(Go↔Go)的 RPC 场景,但直接用它写“框架”容易掉进序列化限制、连接复用缺失、错误透传不清晰的坑——别从零造轮子,先吃透 net/rpc 的边界。
为什么 net/rpc 默认只支持 Go 原生类型序列化
标准 rpc.Server 默认使用 gob 编码,它依赖 Go 类型系统的运行时信息:结构体字段必须导出、不能含函数或 channel、跨语言完全不可用。常见报错如 gob: type not registered for interface 就是因为传了未注册的接口或自定义类型。
实操建议:
- 服务端注册前,对所有可能作为参数/返回值的自定义结构体,显式调用
gob.Register(<Type>{})(注意传指针) - 若需 JSON 通信(比如调试或对接非 Go 客户端),改用
rpc.NewServer()+http.Serve+ 自定义编解码器,但此时要自己处理 HTTP 路由和粘包 - 生产环境建议直接切到
gRPC或Twirp,它们用 Protocol Buffers 强约束接口,天然跨语言
rpc.Dial 和 rpc.DialHTTP 的连接管理陷阱
rpc.Dial("tcp", addr) 返回的 *rpc.Client 是**无连接池的单次 TCP 连接**,每次 Call 都复用该连接,但若连接断开,后续调用会直接 panic 或阻塞超时,不会自动重连。
实操建议:
- 不要全局复用一个
*rpc.Client实例做高并发调用——它内部没有锁保护,多 goroutine 并发Call可能导致响应错乱 - 高频调用场景下,用
sync.Pool管理*rpc.Client实例,但需在Put前检查连接是否活跃(调client.Close()后不能再用) - 若走 HTTP 模式(
rpc.DialHTTP),确保服务端已调用http.Handle("/_goRPC_", server),否则客户端连上后收不到任何响应
如何让错误真正透传到调用方而不是卡死或 panic
net/rpc 的错误处理是隐式的:只有当方法签名中第二个返回值是 error 时,服务端 panic 或返回非 nil error 才会被客户端捕获为 err;否则错误会静默吞掉,或表现为 io.EOF / connection reset 等底层错误。
实操建议:
- 所有 RPC 方法签名严格遵循
func(*Args, *Reply) error模式,Reply必须是指针类型 - 服务端逻辑里避免裸写
panic("xxx"),统一转成return fmt.Errorf("xxx") - 客户端调用后必须检查
err != nil,且注意:即使err == nil,也要检查Reply中业务字段是否有效(比如 API 返回 {“code”:500, “msg”:”db timeout”})
真正难的不是写通一次调用,而是让超时控制、重试策略、服务发现、指标上报这些能力自然地长进骨架里——而 net/rpc 的设计哲学恰恰是“不做假设”,所以你得自己在 Client 外包一层,把 context.WithTimeout、errors.Is、atomic 计数这些补全。别指望它像 gRPC 那样开箱即用。


















