我们不妨先从结论开始,不使用 gRPC-Go 来构建 OTLP/gRPC 的 gRPC Server ,可以让:
OpenTelemetry 协议( OTLP )是 “有 OpenTelemetry 插装的应用” 与 “OpenTelemetry (兼容的) Collector/后端” 之间进行数据传输所用的协议。
现在假设你就有这样一个应用,想要传输数据到 Collector ,那么你可以配置通过以下 Exporter 完成:
出于一些原因,VictoriaTraces 仅暴露了一个 HTTP 接口,通过 OTLP/HTTP 接收 binary 或者 JSON 格式的数据。然而,有很多的应用只支持通过 OTLP/gRPC Exporter 输出数据,kube-apiserver 就是其中的一个典型例子。所以,完善对 OTLP/gRPC 的支持是当前的刚需。
我们的目标是实现一个 gRPC Server,它作为 TraceService 服务提供 Export 方法给远程调用方进行调用,这些信息是定义在 OpenTelemetry Proto 中的。
// Service that can be used to push spans between one Application instrumented with
// OpenTelemetry and a collector, or between a collector and a central collector (in this
// case spans are sent/received to/from multiple Applications).
service TraceService {
rpc Export(ExportTraceServiceRequest) returns (ExportTraceServiceResponse) {}
}
那么,是什么原因让我们考虑弃用 gRPC-Go ,或者更准确地说,弃用整套 protoc 工具链呢?
Protoc 工具链不好用以 Go 为例,最常见的构建 gRPC Server 的方式就是:
protoc 和 protoc-gen-go 生成 .proto 中定义的 Message 对应的结构体。protoc 和 protoc-gen-go-grpc 生成 .proto 中定义的服务 Interface 并实现它。说起来简单,不妨先试想一下这些步骤具体是怎么做的。假设我(对 Protobuf 熟悉程度一般)现在 git clone 了一个项目,然后想往其中的 .proto
添加几个新的 Message 和方法:
protoc 并不是 Ubuntu/MacOS 自带的;protoc;protoc 不能生成 Go 代码,所以还需要 go install protoc-gen-go 和 go install protoc-gen-go-grpc;.proto 里面有一些 import,还要将这些引用的内容的目录在命令中指定清楚;不过,别高兴得太早,protoc 可能还有惊喜在等你。
“为什么这些新生成的代码看起来和老代码好像不太一样?”
新代码:
type TracesData struct {
state protoimpl.MessageState `protogen:"open.v1"`
ResourceSpans []*ResourceSpans `protobuf:"bytes,1,rep,name=resource_spans,json=resourceSpans,proto3" json:"resource_spans,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
老代码:
type TracesData struct {
ResourceSpans []*ResourceSpans `protobuf:"bytes,1,opt,name=resource_spans,proto3" json:"resource_spans,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
7.Google 对应原因,行,然后用不同版本的 protoc 工具链重新走了遍步骤 2-6 。(甚至有时候还要 git log 找之前的作者问用的是哪个版本。)
幸好,这些步骤只是个玩笑,在 VictoriaMetrics 里面我们不这么干。这只是想说明,编译 Protobuf 相关的内容并不是那么直来直去的,不像写个 HTTP JSON 接口那么简单。
当然,它可以变得简单一点:
Makefile。不过仍然有很多工程师就是喜欢 HTTP JSON 。
尽管麻烦不断,但是这些仍不足以说服我们弃用 protoc 工具链。还有别的理由 (借口) 吗?
写在原因 2 之前
原因 2 或许能给你新的启发,不过需要声明,因为 VictoriaTraces 中存在一些历史包袱,它才能在该项目中作为一个 “原因”。也就是说,这个 “原因” 并不是每个项目都该纳入考虑的。
easyproto 已经代替了 golang/protobuf在 VictoriaMetrics ,VictoriaLogs 和 VictoriaTraces 中,对于 Protobuf 的内容,我们是用 easyproto 来进行 Marshal 和 Unmarshal 操作的,而非常见的 golang/protobuf 包。原因在 easyproto 的 README 中写得很清楚:
easyproto不需要protoc或者go generate。easyproto不会像传统的protoc那样让编译的二进制包的体积增大。easyproto正确使用的话可以达成零内存分配。
不过,如果真的要实现 OTLP/gRPC 支持,我们得考虑:
protoc 生成 gRPC Server ,那编译的二进制包会大多少?easyproto 和 gRPC 结合起来用吗?protoc 只需要生成 gRPC 服务的代码,而 Protobuf Message 的结构体还是用 easyproto 处理。easyproto 的第二个理由(减小二进制包体积)。easyproto,而不用引入任何新的包吗?gRPC 是个实现在 HTTP/2 上的协议,所以按理来说,实现一个 HTTP/2 Server 来处理对应接口的请求就可以了。
gRPC 也可以实现在 HTTP/3 (QUIC) 或者 HTTP/1.1 之上,不过这已经超过了这篇博客讨论的范围,我们把它留给读者探索。
根据 gRPC over HTTP2,Data Frame 的格式是这样的:
// +------------+---------------------------------------------+
// | 1 byte | 4 bytes |
// +------------+---------------------------------------------+
// | Compressed | Message Length |
// | Flag | (uint32) |
// +------------+---------------------------------------------+
// | |
// | Message Data |
// | (variable length) |
// | |
// +----------------------------------------------------------+
同时很容易知道,TraceService 服务的 Export 方法对应的 HTTP 接口是 /opentelemetry.proto.collector.trace.v1.TraceService/Export。
下面这段代码简单展示了如何用 HTTP/2 Server 处理 gRPC 请求:
// Init 启动一个 HTTP Server 。
func Init() {
logger.Infof("starting OTLP gPRC server at :4317...")
go httpserver.Serve(
[]string{":4317"},
OTLPGRPCRequestHandler,
httpserver.ServeOptions{UseProxyProtocol: nil, DisableBuiltinRoutes: true, EnableHTTP2: true},
)
}
// OTLPGRPCRequestHandler 管理 gRPC 请求的路由。
func OTLPGRPCRequestHandler(r *http.Request, w http.ResponseWriter) bool {
switch r.URL.Path {
case `/opentelemetry.proto.collector.trace.v1.TraceService/Export`:
otlpExportTracesHandler(r, w)
default:
grpc.WriteErrorGrpcResponse(w, grpc.StatusCodeUnimplemented, fmt.Sprintf("gRPC method not found: %s", r.URL.Path))
}
return true
}
// otlpExportTracesHandler 处理 OTLP Export 请求。
func otlpExportTracesHandler(r *http.Request, w http.ResponseWriter) {
// gzip 解压缩
...
// 解析前 5 bytes ,用 easyproto unmarshalling 剩余 []bytes
...
// 写数据等操作
...
writeExportTraceServiceResponse()
}
完整的代码可以查看 VictoriaTraces #59。
这个实现看起来简单直接,那作为交换,一定有什么代价吧?
到目前为止这个实现只在 Unary RPC 中测试过,而对于 Streaming RPC ,我们没有场景和动力去做相关的测试,所以暂且认为它是只能处理 Unary RPC 请求的。
不过这样的实现足以覆盖我们在 OTLP/gRPC 上的需求,它可能在其他场景不适用,如果你知道具体是哪些场景,非常欢迎在评论中发表看法!
我们做了一轮测试来对比 VictoriaTraces 中不同 OTLP/gRPC 实现方案的二进制包的体积和资源使用率,这些方案包括:
easyproto 来处理 Protobuf Message 。protoc 生成 gRPC Server 代码,用 gRPC 原生的 Encoder 和 Decoder 来处理 Protobuf Message 。另外,生成的 gRPC Server 也支持通过以下代码自定义 Encoder 和 Decoder ,所以我们也用将 easyproto 设为 Encoder 和 Decoder 作为另一组对比。
import (
"google.golang.org/grpc/encoding"
)
func init() {
encoding.RegisterCodec(&easyProtoCodec{})
}
结果如下:
编译二进制包体积:
Release 的所有二进制包总体积 (tar.gz):
easyproto: 87Measyproto: 113M (+29%)单个 linux-amd64 包体积:
easyproto: 21M (+0%)easyproto: 28M (+33%)请求处理的资源使用率( CPU 使用率, no-op: 对每个请求仅进行 Decompression 和 Unmarshalling):
easyproto: 31.3%easyproto: 45.6% (+45%)
从这些结果可以看出,HTTP/2 + easyproto 确实更占优一些。
这篇博客分享的是“为什么 VictoriaTraces 用 HTTP/2 + easyproto 来实现 OTLP/gRPC 所需的 gRPC Server”。它的关键实现是由 JayiceZ 完成的,而最初的想法来自于 @makasim。
这个实现的背后有很多特定的原因,我们并不是想说服你也这样做,但是我们在测试中确实看到了这种方案的潜力和价值。
VictoriaStack 的亮点是高性能和资源优化,所以每一分 CPU 、内存和网络流量都很重要。当然,这同样也适用于二进制包、Docker 镜像的体积等等,就如这些要素也曾在 Aliaksandr Valialkin( VictoriaMetrics 的作者)写的这篇博客中被提到,一直以来它们都没变过。
1
JimLee0921 4 小时 35 分钟前 最近刚好学 Go 学到这里,收藏一下
|
2
czyt 3 小时 44 分钟前 buf 的 https://github.com/bufbuild/hyperpb-go 也不错
|
3
Desdemor 3 小时 42 分钟前 我们内部通信一直走的是 http2
|
4
qW7bo2FbzbC0 3 小时 33 分钟前 AI 翻译的?
|
5
swananan 3 小时 32 分钟前
CPU 使用率:降低 36%
我没太细看帖子内容,我就好奇一个点,为啥 CPU 性能差这么多,这块有分析过细节原因吗,感觉成熟的轮子,不应该有这么大优化空间。 |
6
xiuming 3 小时 8 分钟前
看了半天也没看懂在说什么
|
7
mcfog 2 小时 0 分钟前 某些特殊服务自己封装 grpc 实现跑了几年了,客户端服务端都有
Google 的实现一直都是神鬼两面性 |
8
mcfog 1 小时 49 分钟前 @swananan 如果 http 服务是 10 个功能点,http 上叠上 grpc unary 是 12 个,叠上完整 grpc 是 20 个功能的话,估计 Google 的 grpc-go 实现大概有 50 个功能点吧
|
9
RedisMasterNode OP @mcfog 100%✓
|
10
RedisMasterNode OP @czyt 确认过好用~!
|
11
eijnix 1 小时 18 分钟前 你说的几个确实是 protobuf go 的痛点,非常痛,但是确实也不会用这个 easyproto ,毕竟没维护保障。为啥国内没有个公司做 buf 的事,那个 proto 注册中心的收费有点离谱,按类型收费
|
12
RedisMasterNode OP |