机缘

9.9下午一个人无聊地做着开发,W同学忽然说更新pb后mock里的 go:generate 语句报错,测试也过不了;想到前几天H同学更新了相关的pb,将某个结构中的 AsssetUuid 重命名成了 AsssetUuids ,关联PR已经合入hub的master分支(管理着所有项目的proto定义及生成的pb.go 文件)。建议W按新定义做修改,测试了下没啥问题。大家碰了下,H同学说他们项目里引用的确实是AsssetUuids,而更新前hub里的pb和pb.go又确实是AsssetUuid ,而我们这边的关联的业务功能也表现正常,这下奇怪了......

问题

H同学的server实现代码也不可能有什么魔法,确实是AsssetUuids ,并确认最近没改动。那我们客户端代码为啥没报错呢?导入最新的proto定义在 BloomRPC 调用Beta上的服务,发现AsssetUuid方式调用返回的数据不对,AsssetUuids 方式则返回正常,难道是我们客户端代码写的有问题?

使用更新pb前的代码分支连接Beta上的服务进行远端测试,发现AsssetUuid 方式返回也正常,更奇怪了!切到新pb分支测试,远端调用也是正常。忽然想到是不是只和参数位置有关,和参数名无关,貌似之前在文档中看到过类似的说法,而且文档里也经常提到定义pb时不要轻易改变已有参数后的位置信息,保证向前兼容,这样似乎能解释大家看到的现象,下面就是进一步验证。

假设存在以下proto文件定义

// PbV1 开发人员L最初定义的版本并生成pb.go上传到hub中供客户端调用
message ListAssetsRequest {
  uint64 user_id = 1;
  repeated string asset_uuid = 2;
}

// PbV1.1 L修改了pb定义并生成对应pb.go在本地使用;可能未同步到hub仓库
message ListAssetsRequest {
  uint64 user_id = 1;
  repeated string asset_uuids = 2;
}

// PbV2 H同学开发功能时发现hub中的单数与server端实现的不一致,将hub中版本改进为复数并生成pb.go同步到hub
message ListAssetsRequest {
  uint64 user_id = 1;
  repeated string asset_uuids = 2;
}

// 客户端代码 需从V1迁移到V2;而线上版本正在引用V1版本

探索

Proto是一种结构化数据序列化格式。

Varint编码

// go libary encoding/binary/varint.go中varint编码实现

// PutUvarint encodes a uint64 into buf and returns the number of bytes written.
func PutUvarint(buf []byte, x uint64) int {
	i := 0
	for x >= 0x80 {
		buf[i] = byte(x) | 0x80
		x >>= 7
		i++
	}
	buf[i] = byte(x)
	return i + 1
}

ZigZeg编码

// protobuf@1.26.0/encoding/protowire/wire.go中zig-zag编码实现

// DecodeZigZag decodes a zig-zag-encoded uint64 as an int64.
//	Input:  {…,  5,  3,  1,  0,  2,  4,  6, …}
//	Output: {…, -3, -2, -1,  0, +1, +2, +3, …}
func DecodeZigZag(x uint64) int64 {
	return int64(x>>1) ^ int64(x)<<63>>63
}

// EncodeZigZag encodes an int64 as a zig-zag-encoded uint64.
//	Input:  {…, -3, -2, -1,  0, +1, +2, +3, …}
//	Output: {…,  5,  3,  1,  0,  2,  4,  6, …}
func EncodeZigZag(x int64) uint64 {
	return uint64(x<<1) ^ uint64(x>>63)
}

// go libary encoding/binary/varint.go中zig-zag编码实现
// PutVarint encodes an int64 into buf and returns the number of bytes written.
// If the buffer is too small, PutVarint will panic.
func PutVarint(buf []byte, x int64) int {
	ux := uint64(x) << 1
	if x < 0 {
		ux = ^ux
	}
	return PutUvarint(buf, ux)
}

参考

Protocol Buffer 序列化原理大揭秘 - 为什么Protocol Buffer性能这么好?_专注分享 Android干货-CSDN博客_protobuf为什么快

Encoding | Protocol Buffers | Google Developers

https://developers.google.com/protocol-buffers