前些天接到一个 Oncall,来自 Lark 的胡同学反馈,用 gRPC 官方的 python 客户端请求 Kitex gRPC Server,有时收到的 response 为 None。
请求的代码大致如下:
stub = xx_grpc.XXXServiceStub(channel)
resp = stub.SomeMethod(req)
logger.info("resp = {}".format(resp))
如果请求失败,按 python 的尿性,这里应该 raise 一个 exception,返回个 None 算个啥?
胡同学补充说,从服务端的日志来看,请求是正常收到且处理了,并且通过增加两个环境变量再运行 client 端:
$ GRPC_TRACE=all GRPC_VERBOSITY=debug python3 test.py
可以看到 client 把 tcp 报文内容都 dump 出来了,虽然乱码很多,但是从一些文本中可以看到,确实收到了 server 的 response:
考虑到胡同学用的是 gRPC 官方的 client,我充分利用自己锻炼了多年的反思技能,先从 Kitex 查起。
既然这个问题能够稳定复现,那就说明可以稳定复现这个问题。
那么就先用 tcpdump 抓它个包:
$ tcpdump -i any tcp port 9954 -Ans 0 -w grpc.pcap
然后在 wireshark 里打开,结合 log 里的信息,通过 filter 找到有问题的这个请求:
人肉 decode 这个报文确实有点为难,好在可以在 wireshark 里指定报文的协议为 HTTP2,然后它顺手就把 gRPC 也给识别出来了:
但也没有完全识别出来,只是把这个 gRPC payload 识别成了「Line-based text data」。将这个的「text data」保存为 packet_bytes.bin,然后执行:
$ protoc --decode_raw < packet_bytes.bin
可以正常解码,说明返回的数据是符合 protobuf 规范的。
而对比其他正常响应的报文,Wireshark 可以直接按照 protobuf 的编码协议当场 decode 出来:
这说明在一些情况下, Kitex gRPC Server 确实返回了不正确的报文。
对比这两个请求的 Wireshark 解析结果,可以看到,在 gRPC Message 下面有两个 flag 不同:
flag | 正常响应 | 异常响应 |
---|---|---|
Frame Type | 0 | 1 |
Compressed Flag | 0 | 1 |
看起来非常接近真相了。
那么问题是哪一个呢?
根据官方文档 gRPC over HTTP2 [1],其报文是由一系列「Length-Prefixed-Message」构成的;而每个「Length-Prefixed-Message」则由三个部分组成:
Compressed-Flag:0/1(1字节无符号整数)
Message-Length:消息长度(4字节无符号整数)
Message:二进制数据
异常响应的报文显然不符合规范 —— Compressed Flag 这个字节的值竟然是 0xAF ?
根据以前的经验,这种脏东西大概是这两种情况导致的:(1) 并发读写导致内存数据被写坏了,或 (2) 复用了未清理的buf。
侯捷大佬说过「源码面前,了无秘密」。但是他没说扒代码会这么辛苦。此处省略800字,我终于扒出来 Kitex 的 gRPC 编码逻辑,位于 pkg/remote/codec/protobuf/grpc.go:
在编码一个 Data Frame 的时候,会先从 mcache 获取一个 buf,在 [1, 5) 写入数据的长度,并在 [5, +∞) 写入 protobuf 编码后的数据。
至此破案。
既然问题定位到了,修复起来也就很简单了,只要将从 mcache 获得的 buf 第一个字节清零即可,相关 PR[2] 已经合入 develop 分支,将会随着下一个版本的 Kitex 一起发布。
此外,python client 在遇到错误的报文时没有任何报错,也不是太对劲,于是我给他们提了个 issue[3] 。
(完)
[1] gRPC Over HTTP2
https://github.com/grpc/grpc/blob/master/doc/PROTOCOL-HTTP2.md
[2] 给 Kitex 提的 PR
https://github.com/cloudwego/kitex/pull/795
[3] 给 gRPC 提的 issue
https://github.com/grpc/grpc/issues/32257
[4] CloudWeGo
https://www.cloudwego.io
[5] Wireshark Wiki - gRPC
https://wiki.wireshark.org/gRPC.md
推荐阅读