Github 项目主页: https://github.com/cloudwego/frugal
说明:
Kitex 命令行工具内建了集成 frugal 的能力。
生成带有 frugal tag 的 Go struct,例如:
type Request struct {
Message string `thrift:"message,1" frugal:"1,default,string" json:"message"`
}
说明:
Kitex >= v0.5.0 默认指定了该参数;旧版本需重手动指定该参数、执行 kitex 命令,例:
kitex -thrift frugal_tag -service service_name idl/api.thrift
在 init()
里生成调用 frugal.Pretouch
方法的代码,预处理(JIT 编译)所有请求/响应类型,减少首次请求耗时。
frugal 默认在首次编解码时调用 JIT Compiler,这会导致首次请求耗时较长。
例:
kitex -frugal-pretouch -service service_name idl/api.thrift
请升级 thriftgo 到 v0.3.0(或以上);旧版本生成的 struct 在 optional 字段的编解码上存在问题。
不生成 Thrift 编解码的 Go 源码(该代码实现了 thrift.TProtocol 接口),以减少代码量,提高 IDE 加载及编译速度。附:使用 slim 模板生成的样例代码。
frugal 用 JIT 生成编解码代码,不依赖生成的 Go 编解码代码。
例:
kitex -thrift frugal_tag,template=slim -service service_name idl/api.thrift
注:开启 Slim 会导致在不支持 frugal 的情况下无法 fallback、只能报错(例如 arm 架构,或无法从请求头中获取 thrift payload 的长度)。
建议使用最新版 Kitex(>= v0.5.0)和 thriftgo(>= v0.3.0)。
kitex -thrift frugal_tag -service service_name idl/api.thrift
说明:
kitex -thrift frugal_tag,template=slim -frugal-pretouch -service service_name idl/api.thrift
说明:
请确保 Client 端指定了 Framed 模式(或 TTHeaderFramed)
Server 初始化时的相关参数。
用于启用 Frugal 编解码器:
server.WithPayloadCodec(
thrift.NewThriftCodecWithConfig(thrift.FrugalRead | thrift.FrugalWrite)
)
注:如报错(找不到符号),说明当前 kitex 版本 + go 版本的组合不支持 frugal,例如 Go 1.21 + Kitex v0.7.1(Kitex 通过条件编译屏蔽不支持的版本)。
package main
import (
"context"
"github.com/cloudwego/kitex-examples/kitex_gen/api"
"github.com/cloudwego/kitex-examples/kitex_gen/api/echo"
"github.com/cloudwego/kitex/pkg/remote/codec/thrift"
"github.com/cloudwego/kitex/server"
)
type EchoImpl struct{}
func (e EchoImpl) Echo(ctx context.Context, req *api.Request) (r *api.Response, err error) {
return &api.Response{Message: req.Message}, nil
}
func main() {
code := thrift.NewThriftCodecWithConfig(thrift.FrugalRead | thrift.FrugalWrite)
svr := echo.NewServer(new(EchoImpl), server.WithPayloadCodec(code))
err := svr.Run()
if err != nil {
panic(err)
}
}
Client 初始化时的相关参数。
用于启用 Frugal 编解码器:
client.WithPayloadCodec(
thrift.NewThriftCodecWithConfig(thrift.FrugalRead | thrift.FrugalWrite)
)
注:如报错(找不到符号),说明当前 kitex 版本 + go 版本的组合不支持 frugal,例如 Go 1.21 + Kitex v0.7.1(Kitex 通过条件编译屏蔽不支持的版本)。
用于开启 Framed 模式,在 thrift pure payload 前增加 4 个字节(int32)用于告诉对端 payload size
client.WithTransportProtocol(transport.Framed)
注:
TTHeader | Framed
位与结果)。package main
import (
"context"
"github.com/cloudwego/kitex-examples/kitex_gen/api"
"github.com/cloudwego/kitex-examples/kitex_gen/api/echo"
"github.com/cloudwego/kitex/client"
"github.com/cloudwego/kitex/pkg/klog"
"github.com/cloudwego/kitex/pkg/remote/codec/thrift"
"github.com/cloudwego/kitex/transport"
)
func main() {
codec := thrift.NewThriftCodecWithConfig(thrift.FrugalRead | thrift.FrugalWrite)
framed := client.WithTransportProtocol(transport.Framed)
server := client.WithHostPorts("127.0.0.1:8888")
cli := echo.MustNewClient("a.b.c", server, client.WithPayloadCodec(codec), framed)
rsp, err := cli.Echo(context.Background(), &api.Request{Message: "Hello"})
klog.Infof("resp: %v, err: %v", rsp, err)
}
某些场景(例如录制流量)可直接使用 frugal 编解码。
frugal 的 JIT 编译器依赖带 frugal tag 的 Go struct。
注意:
参见上文 “Kitex 集成 -> 代码生成”。
注:kitex 通过调用 thriftgo 生成代码。
安装 thriftgo ( >= v0.3.0):
go install -v github.com/cloudwego/thriftgo@latest
基于 Thrift IDL 生成 Go struct:
thriftgo -r -o thrift -g go:frugal_tag,template=slim,package_prefix=github.com/example echo.thrift
请参考 thriftgo 生成的 struct (例:Request、Response)。
注意:
InitDefault()
方法里写入默认值;如只想用 thrift 编码(例如替代 json),可直接调用 frugal.EncodeObject(..)
方法。
如想生成符合 Thrift Binary protocol encoding 的 Thrift Payload(可发送给 Thrift Server),编码结果应含:
其中 1~4 可以直接引用 Kitex 的实现, 5 可以调用 frugal.EncodeObject(buf, nil, data)
生成。
注:data
应是一个将所有请求/响应参数按顺序封装起来的 struct(例如 Kitex 生成的结构体 EchoEchoArgs、EchoEchoResult),具体可参考示例代码。
根据 Thrift Binary protocol encoding,解码结果应包括:
其中 1~3 的解码可以引用 Kitex 的实现,4 的解码可调用 frugal.DecodeObject(buf, data)
完成。
注:data
应是一个将所有请求/响应参数按顺序封装起来的 struct(例如 Kitex 生成的结构体 EchoEchoArgs、EchoEchoResult),具体可参考示例代码。
请参见:kitex-examples: frugal/codec/frugal.go
Kitex 解码时,需从 Header 中获取 Payload Size,以截取完整 Thrift PurePayload,供 frugal 解码。
如启用了 Slim 模板,但无法从请求或相应的 Header 里获取到 Payload Size,Kitex 无法 fallback,只能报错:
codec msg type not match with thriftCodec
因此如果目标 Server 兼容 Framed,建议默认指定 Framed(或 TTHeaderFramed)。
由于 frugal 目前不支持 ARM 架构,有 ARM 架构需求的项目请勿使用 slim 模板
传统的 Thrift 编解码方式,要求用户必须要先生成编解码代码,Frugal 通过 JIT 编译技术在运行时动态生成编解码机器代码,避免了这一过程。
基于 JIT 技术 Frugal 可以生成比 Go 语言编译器性能更好的机器代码,在多核场景下,Frugal 的性能最高可达传统编解码方式的 5 倍左右。
性能测试数据如下:
name old time/op new time/op delta
MarshalAllSize_Parallel/small-16 78.8ns ± 0% 14.9ns ± 0% -81.10%
MarshalAllSize_Parallel/medium-16 1.34µs ± 0% 0.32µs ± 0% -76.32%
MarshalAllSize_Parallel/large-16 37.7µs ± 0% 9.4µs ± 0% -75.02%
UnmarshalAllSize_Parallel/small-16 368ns ± 0% 30ns ± 0% -91.90%
UnmarshalAllSize_Parallel/medium-16 11.9µs ± 0% 0.8µs ± 0% -92.98%
UnmarshalAllSize_Parallel/large-16 233µs ± 0% 21µs ± 0% -90.99%
name old speed new speed delta
MarshalAllSize_Parallel/small-16 7.31GB/s ± 0% 38.65GB/s ± 0% +428.84%
MarshalAllSize_Parallel/medium-16 12.9GB/s ± 0% 54.7GB/s ± 0% +322.10%
MarshalAllSize_Parallel/large-16 11.7GB/s ± 0% 46.8GB/s ± 0% +300.26%
UnmarshalAllSize_Parallel/small-16 1.56GB/s ± 0% 19.31GB/s ± 0% +1134.41%
UnmarshalAllSize_Parallel/medium-16 1.46GB/s ± 0% 20.80GB/s ± 0% +1324.55%
UnmarshalAllSize_Parallel/large-16 1.89GB/s ± 0% 20.98GB/s ± 0% +1009.73%
name old alloc/op new alloc/op delta
MarshalAllSize_Parallel/small-16 112B ± 0% 0B -100.00%
MarshalAllSize_Parallel/medium-16 112B ± 0% 0B -100.00%
MarshalAllSize_Parallel/large-16 779B ± 0% 57B ± 0% -92.68%
UnmarshalAllSize_Parallel/small-16 1.31kB ± 0% 0.10kB ± 0% -92.76%
UnmarshalAllSize_Parallel/medium-16 448B ± 0% 3022B ± 0% +574.55%
UnmarshalAllSize_Parallel/large-16 1.13MB ± 0% 0.07MB ± 0% -93.54%
name old allocs/op new allocs/op delta
MarshalAllSize_Parallel/small-16 1.00 ± 0% 0.00 -100.00%
MarshalAllSize_Parallel/medium-16 1.00 ± 0% 0.00 -100.00%
MarshalAllSize_Parallel/large-16 1.00 ± 0% 0.00 -100.00%
UnmarshalAllSize_Parallel/small-16 6.00 ± 0% 1.00 ± 0% -83.33%
UnmarshalAllSize_Parallel/medium-16 6.00 ± 0% 30.00 ± 0% +400.00%
UnmarshalAllSize_Parallel/large-16 4.80k ± 0% 0.76k ± 0% -84.10%
默认生成的 frugal tag 不影响性能,建议保留。
执行 kitex 命令行工具时加上参数 -thrift frugal_tag=false
。
注意:
Client 端报错信息如下:
failed with error: remote or network error[remote]: encode failed, codec msg type not match with thriftCodec
可能原因:
日志信息如下(或在 client 端收到的响应里包含该错误信息):
decode failed, codec msg type not match with thriftCodec
可能原因:
根据 Thrift binary protocol, 11 是 BINARY(或string),10 是 I64 类型(不是 IDL 里的字段编号)
当前 IDL 定义的是 string 类型,但是报文里是 I64,导致无法正确解码。
请检查上下游的 IDL 是否一致、生成的编解码代码是否与 IDL 一致(可能没有用新的 IDL 重新生成代码,或没有正确提交到 git)。
frugal <= v0.1.3 解码 string 类型时,默认使用 NOCOPY 模式(直接引用解码入参的 []byte);而在 Kitex 里该入参会被回收后复用,导致 string 的「值」被修改。
新版本默认禁用了 NOCOPY 模式,升级后即可修复。
可能原因:
已知问题:旧版本 thriftgo 在 slim 模板下未生成 InitDefault()
方法。
需升级到 thriftgo >= v0.3.0, 并重新生成 slim 模板代码。
由于 frugal.EncodeObject
需要传入一个 buf,Kitex 的实现是先调用 frugal.EncodeSize(data)
计算所需 buf 的长度,分配 buf,再编码 data。
在「计算完 buf 大小」之后、「编码data」之前,业务代码可能会并发读写 data,导致实际编码数据超过预期(可能会越界报错,甚至panic)。
这种情况不属于 frugal 的 bug,需要业务代码自查,避免修改已传给 Kitex 的 Request/Response (包括其中的字段,特别是 string、slice 等非固定长度类型)。
可能是旧版本的问题,建议升级到最新版( >= v0.1.8)
go get github.com/cloudwego/frugal@latest
如果问题依然存在,请确认被 encode 的对象没有被并发读写(包括间接引用的其他对象)。
例如读取一个正被设置为空串的字符串,可能会读到无效的 string(StringHeader.Data = nil && StringHeader.Len > 0),导致在编码时 出现 “nil pointer error” panic 。