主题
Day 1 技术设计文档 — 消息编解码
协议格式
连接建立后,通信流程如下:
客户端 服务端
|--- Option (JSON编码) ------------>| 握手:协商编解码方式
|--- Header + Body (Codec编码) ---->| 请求 1
|<-- Header + Body (Codec编码) -----| 响应 1
|--- Header + Body (Codec编码) ---->| 请求 2
|<-- Header + Body (Codec编码) -----| 响应 2
...Header 字段定义
| 字段 | 类型 | 说明 |
|---|---|---|
ServiceMethod | string | 格式 Service.Method,标识调用目标 |
Seq | uint64 | 客户端选择的序列号,用于匹配请求和响应 |
Error | string | 服务端返回的错误信息,空字符串表示成功 |
Option 字段定义
| 字段 | 类型 | 说明 |
|---|---|---|
MagicNumber | int | 固定值 0x3bef5c,用于校验协议合法性 |
CodecType | string | 编解码类型:application/gob、application/json、application/protobuf |
ConnectTimeout | Duration | 连接超时时间(Day 7 引入) |
HandleTimeout | Duration | 处理超时时间(Day 7 引入) |
Codec 接口设计
go
type Codec interface {
io.Closer
ReadHeader(*Header) error
ReadBody(interface{}) error
Write(*Header, interface{}) error
}
type NewCodecFunc func(io.ReadWriteCloser) Codec采用工厂函数模式注册不同的 Codec 实现,通过 NewCodecFuncMap 映射 Type 到构造函数。
握手时序图
设计决策
为什么用 Header + Body 而非自描述格式?
自描述格式(如纯 JSON)虽然灵活,但每条消息都携带字段名等冗余信息。分离 Header 和 Body:
- Header 结构固定、字段少,解析快速
- Body 可以是任意类型,由具体的 Codec 负责序列化
Gob vs Json vs Protobuf
| 维度 | Gob | Json | Protobuf |
|---|---|---|---|
| 编码格式 | Go 私有二进制 | 文本 | 二进制 |
| 性能 (ops/s) | ~653K | ~727K | ~215K |
| 消息体积 | 中 | 大 | 最小 |
| 跨语言 | 仅 Go | 通用 | 通用 |
| 可读性 | 不可读 | 人类可读 | 不可读 |
| Schema | 无需 | 无需 | .proto 文件 |
| 依赖 | 标准库 | 标准库 | google.golang.org/protobuf |
miniRPC 同时提供三种 Codec:
- GobCodec(默认):Go 内部通信场景首选,流式编解码,内存分配最少
- JsonCodec:调试和跨语言简易集成场景,人类可读
- ProtobufCodec:生产级跨语言场景首选,消息体积最小
ProtobufCodec 的帧格式
ProtobufCodec 采用长度前缀帧(Length-Delimited Framing):
┌──────────────────┬──────────────────┬──────────────────┬──────────────────┐
│ Header 长度 │ Header 数据 │ Body 长度 │ Body 数据 │
│ (4 bytes, BE) │ (JSON 编码) │ (4 bytes, BE) │ (proto.Marshal) │
└──────────────────┴──────────────────┴──────────────────┴──────────────────┘- Header 使用 JSON 编码(结构简单、字段少,JSON 足够高效)
- Body 若实现
proto.Message接口则使用proto.Marshal,否则降级为 JSON - 4 字节大端序长度前缀,支持最大 4GB 单帧
三种 Codec 的实现差异
| GobCodec / JsonCodec | ProtobufCodec | |
|---|---|---|
| 编码方式 | 流式 Encoder/Decoder | 先序列化为 []byte 再写入 |
| 缓冲 | bufio.Writer 合并写入 | 长度前缀自带帧边界 |
| 帧分隔 | 编码器内部处理 | 显式 4 字节长度前缀 |
| 空 Body 处理 | 编码器处理 | 写入 length=0 帧 |