自研序列化协议仅在结构固定、无嵌套、字段全为基础类型、字节序统一、Go内部高频使用且压测证实通用方案成瓶颈时才推荐;核心是结构体实现WriteTo/ReadFrom,用encoding/binary直写,预分配缓冲,变长字段加长度前缀并校验。

直接手写自研序列化协议在 Go 里不是首选,但真有必要时——比如每秒百万级传感器数据、字段全为基本类型、结构长期稳定、且对 latency 和体积有硬性要求——它确实能比通用方案再压榨出 10%~30% 的性能。关键不在“造轮子”,而在“控边界”。
先明确什么场景值得自研
满足以下全部条件才建议动手:
- 结构体字段固定、无嵌套、不含 slice/map/string(或已约定好如何展平)
- 字段顺序和大小完全已知(如 int32 恒为 4 字节,time.UnixNano() 存为 int64)
- 跨平台通信已统一字节序(通常选 binary.LittleEndian,x86/ARM 默认一致)
- 不追求跨语言,只在 Go 内部高频使用(如本地 ring buffer、共享内存区、硬件采集模块)
- 已有压测证明 encoding/binary 或 gob 在该路径下仍是瓶颈(比如占 CPU >15%)
核心实现方式:WriteTo/ReadFrom + encoding/binary
别封装新 encoder,直接让结构体实现 io.WriterTo 和 io.ReaderFrom 接口。这样可绕过 bytes.Buffer 中间分配,直接写入预分配的 []byte 或 net.Conn:
- WriteTo 方法内用 binary.Write 写入每个字段,显式指定字节序
- ReadFrom 方法内用 binary.Read 逐字段读,避免反射和接口断言
- 所有字段必须是基础类型(int32, uint64, [32]byte)或数组;string 要转成 [64]byte 并用 strings.TrimRight 读取
- 预计算 struct 大小:binary.Size(v) 可得总字节数,用于提前分配缓冲区
变长字段必须自己定协议
encoding/binary 不支持动态长度,所以字符串、切片要靠“长度前缀 + 内容”:
立即学习“go语言免费学习笔记(深入)”;
- 长度字段本身必须是定长整数(如 uint32),且与内容字节序一致
- 例如写字符串:
binary.Write(w, order, uint32(len(s))); w.Write([]byte(s)) - 读时先读 uint32 得长度 n,再读 n 字节进 []byte,最后 string() 转换
- 注意:n 过大要校验,防止 OOM(如限制 ≤ 64KB)
安全边界和工程细节
自研协议容易翻车的地方不在序列化逻辑,而在边界处理:
- 写入前检查目标 []byte 容量是否足够,不够就 panic 或返回 error(别依赖隐式扩容)
- 读取时遇到 io.ErrUnexpectedEOF 必须终止,不能跳过或重试——gob 都会失败,自研更不能侥幸
- 结构体加 //go:notinheap 注释(如果常驻内存),避免被 GC 扫描
- 单元测试必须覆盖字节序切换、边界值(如 int32(-1)、空字符串、满长切片)



















