一个基于 TCP + 反射的极简 Go RPC 框架,使用 msgpack 序列化,专注于单向非流式调用。
- 零依赖运行时:仅依赖 msgpack 用于消息序列化
- 反射驱动:通过
Register自动发现满足func(req T, reply *U) error签名的方法 - 单向非流式:每次连接处理一个请求→一个响应,简单可靠
- 长连接复用:客户端在多次 Call 之间复用 TCP 连接
- 分级缓冲池:
TieredPool减少 GC 压力(可选) - 内置健康检查:开箱即用的服务端心跳检测
go get github.com/23jdd/mrpctype Calculator struct{}
type MultiplyReq struct {
A int
B int
}
type MultiplyReply struct {
Product int
}
// 签名必须为 func(req T, reply *U) error
func (c *Calculator) Multiply(req MultiplyReq, reply *MultiplyReply) error {
reply.Product = req.A * req.B
return nil
}lis, _ := net.Listen("tcp", "127.0.0.1:8080")
server := mrpc.NewServer(lis)
// 注册服务(名字用于客户端路由)
server.Register("Calculator", &Calculator{})
// 启动(阻塞)
server.Run()client := mrpc.NewClient("127.0.0.1", 8080)
defer client.Close()
req := &MultiplyReq{A: 7, B: 8}
var reply MultiplyReply
err := client.Call("Calculator.Multiply", req, &reply)
// reply.Product == 56func NewServer(lis net.Listener) *Server创建 RPC 服务端。lis 必须非 nil。
func (s *Server) Register(name string, target any) error注册一个 RPC 服务实现。
参数:
name: 服务名,客户端使用"name.MethodName"格式调用target: 服务实现,必须是指向 struct 的指针(如&Calculator{})
注册规则:
- 自动扫描
target的所有导出方法 - 只注册满足
func(req T, reply *U) error的方法 - 同名服务重复注册会覆盖之前的方法
- 返回
nil表示成功,否则返回错误描述
边界条件:
| 输入 | 行为 |
|---|---|
target 为值类型(非指针) |
返回错误 "target must be a pointer to struct" |
target 为 *int 等非结构体指针 |
返回错误 "target must point to a struct" |
name 为空字符串 |
合法,方法注册为 ".MethodName"(不推荐) |
| 结构体无符合签名的方法 | 返回 nil(不报错),但不注册任何方法 |
func (s *Server) Run()启动服务端主循环,阻塞直到 lis.Accept() 返回不可恢复错误。
func NewClient(address string, port int) *Client创建一个 RPC 客户端。
参数:
address: 服务端地址,支持"127.0.0.1"、"localhost"、"::1"等port: 服务端端口号
func (c *Client) Dial() error建立到服务端的 TCP 连接。若已有连接则先关闭旧连接。
func (c *Client) Call(method string, argv any, reply any) error执行一次单向非流式 RPC 调用。
流程:
- 若未连接则自动
Dial() Encode(argv)→ 发送请求帧- 接收响应帧 →
Decode(reply)
参数:
method:"ServiceName.MethodName"argv: 请求参数指针(msgpack 需要指针才能编码)reply: 响应参数指针(结果解码到此),可为nil(跳过解码)
返回值:
nil: 调用成功ErrShutdown: 客户端已关闭- 其他错误:网络错误、编解码错误、服务端返回的错误
边界条件:
| 场景 | 行为 |
|---|---|
未调用 Dial 直接 Call |
自动拨号 |
argv 为 nil |
msgpack 编码为 nil,解码端需处理 |
reply 为 nil |
跳过响应解码 |
| 服务端返回 error | Call 返回包含错误消息的 error |
| 网络断开 | 返回网络错误,下次 Call 自动重连 |
并发调用 Call |
不安全,需要外部加锁 |
调用已 Close 的客户端 |
返回 ErrShutdown |
func (c *Client) Close() error关闭连接并标记客户端为 shut down 状态。幂等,可多次调用。
func RegisterHealth() *healthService返回一个实现了 Health.Check 方法的服务实例,注册后客户端可调用:
server.Register("Health", mrpc.RegisterHealth())
// 客户端:
var reply mrpc.HealthReply
client.Call("Health.Check", &mrpc.HealthRequest{}, &reply)
// reply.Ok == true 表示服务正常func NewHealthChecker(interval, timeout time.Duration) *HealthChecker
func (hc *HealthChecker) Start(client *Client, maxFailures int, onFailure func(error))
func (hc *HealthChecker) Stop()周期性健康检查器。Start 在后台 goroutine 中定期调用 Health.Check,连续 maxFailures 次失败后调用 onFailure。
func NewTieredPool(capacities ...int) *TieredPool
func (tp *TieredPool) Get(size int) []byte
func (tp *TieredPool) Put(buf []byte)按容量分桶的缓冲池,减少 GC 压力。当前未直接集成到 RPC 调用中(可手动使用)。
Request 和 Response 的低级 API:
// 帧协议
func WriteFrame(w io.Writer, payload []byte) error
func ReadFrame(r io.Reader) ([]byte, error)
// 请求
func NewRequest(method string, seq uint64, argv []byte) *Request
func SendRequest(w io.Writer, req *Request) error
func ReceiveRequest(r io.Reader) (*Request, error)
// 响应
func NewResponse(seq uint64, reply []byte, err string) *Response
func SendResponse(w io.Writer, resp *Response) error
func ReceiveResponse(r io.Reader) (*Response, error)帧格式:
[4 字节 BigEndian 总长度] [payload...]
- 最大 payload:
10MB(MaxPayloadSize) - 超限时返回
ErrMaxPayload
type Codec interface {
Encode(v any) ([]byte, error)
Decode(data []byte, v any) error
}
func NewMsgCodec() *MsgCodec默认使用 msgpack。可通过实现 Codec 接口替换为 JSON、Protobuf 等。
| 组件 | 安全性 |
|---|---|
Server.Register |
非线程安全,需在 Run() 前完成注册 |
Server.Run |
每个连接独立 goroutine,并发安全 |
Server.Call |
非线程安全,仅供单 goroutine 调用 |
Client.Call |
非线程安全,同一连接上的并发调用需外部加锁 |
Client.Close |
线程安全(可通过 sync.Once 包装) |
TieredPool |
Get/Put 线程安全(通过 sync.Pool) |
- 单向非流式:不支持服务端推送、流式传输
- 短连接处理:服务端每个连接只处理一个请求后关闭
- 无超时控制:
Call无内置超时(可通过context在外部控制) - 无 TLS:纯 TCP 通信,生产环境建议搭配 sidecar 或 VPN
go test -v ./...mrpc/
├── client.go # RPC 客户端
├── server.go # RPC 服务端(反射注册 + TCP 监听)
├── protocol.go # 二进制线协议(帧 + Request/Response)
├── codec.go # Codec 接口 + msgpack 实现
├── pool.go # 分级缓冲池(Tiered Pool)
├── health.go # 健康检查(服务端 + 客户端检查器)
├── cmd/main.go # 可运行的示例程序
├── *_test.go # 单元测试 & 集成测试 & 示例测试
MIT