net/rpc不适合直接上生产,因其强依赖Go同构环境、gob单一封装、无HTTP/JSON支持、缺超时控制与错误透明度,难以满足跨语言、可观测性及分布式追踪等生产刚需。

为什么不用 net/rpc 直接上生产?
Go 标准库的 net/rpc 确实能跑通基本调用,但它的设计假设太强:要求服务端和客户端都用 Go、序列化固定为 gob、不支持 HTTP/JSON、没有超时控制、错误传播不透明。一旦你遇到跨语言调试、前端直连、或需要 trace 上下文透传,net/rpc 就会卡住。
所以“简单 RPC 框架”的起点不是从零写传输层,而是明确边界:只封装序列化 + 编解码 + 一次请求生命周期管理,把网络层交给 net/http 或 net,把服务发现和重试留给上层。
如何用 net/http 实现可调试的 RPC 调用?
HTTP 是最易观测的载体:能用 curl 测、能走 nginx、能套 TLS、浏览器开发者工具直接看 payload。关键不是“伪装成 Web”,而是把 RPC 请求当成一个结构化 POST。
- 服务端用
http.HandleFunc注册统一入口,比如/rpc - 请求体是 JSON:
{"method":"Add","params":[1,2]},响应也是 JSON:{"result":3,"error":null} - 用
json.Unmarshal解包 method 名和 params,通过reflect.Value.MethodByName动态调用(注意:只允许导出方法) - 响应前统一加
Content-Type: application/json,避免被某些代理截断
示例片段:
立即学习“go语言免费学习笔记(深入)”;
Go 配置库,使用 spf13/viper — 分层优先级(flag > env >file > KV > default),提供 BindPFlag/BindPFlags、SetEnvPrefix + SetEnvKeyReplace 等功能。
http.HandleFunc("/rpc", func(w http.ResponseWriter, r *http.Request) {
var req struct {
Method string `json:"method"`
Params []json.RawMessage `json:"params"`
}
json.NewDecoder(r.Body).Decode(&req)
// ... 反射调用逻辑
json.NewEncoder(w).Encode(map[string]interface{}{"result": result, "error": err})
})
gob 和 json 在 RPC 中的实际取舍
gob 快、保类型、Go 内部序列化首选,但它不能跨语言,且对 struct 字段名变更极其敏感(字段删了就 panic)。而 json 慢一点,但容错强:新增字段不破坏旧客户端,空字段自动忽略,调试时直接 fmt.Printf 就能看。
- 开发期、混合语言环境、需要人工干预请求时,强制用
json - 纯 Go 内部微服务、性能压测瓶颈在编解码层时,才考虑
gob+ 自定义http.RoundTripper复用连接 - 别碰
protobuf—— 它不是“简单框架”的范畴,引入protoc工具链和生成代码就已超出轻量目标
怎么让客户端调用看起来像本地函数?
核心是隐藏 transport 细节,但**不能真隐藏错误来源**。常见错误是把 http.Do 的 timeout、connection refused、status 500 全吞成一个 error 返回,导致排查时分不清是网络问题还是业务 panic。
- 客户端封装成函数,如
Add(ctx context.Context, a, b int) (int, error),但内部必须透传ctx控制超时 - 错误类型要做区分:自定义
RPCTimeoutError、RPCServerError,而不是全用errors.New - 别实现泛型反射调用客户端 —— 它会让调用点失去 IDE 跳转和静态检查,得不偿失
真正该省的是重复写 http.NewRequest + json.Marshal + json.Unmarshal,不是省掉错误分类。
真正的复杂点不在序列化或反射,而在于上下文传递:如果服务 A 调 B,B 调 C,你得把 traceID、deadline、认证 token 一层层透传下去。这个逻辑一旦漏掉,分布式追踪就断了——但它是 RPC 框架无法绕开的硬需求,不是“简单”二字能跳过的。

















