惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

小众软件
小众软件
T
The Blog of Author Tim Ferriss
Apple Machine Learning Research
Apple Machine Learning Research
B
Blog
L
LangChain Blog
博客园_首页
Vercel News
Vercel News
月光博客
月光博客
B
Blog RSS Feed
S
SegmentFault 最新的问题
博客园 - Franky
C
Check Point Blog
A
About on SuperTechFans
Stack Overflow Blog
Stack Overflow Blog
J
Java Code Geeks
F
Fortinet All Blogs
Recent Announcements
Recent Announcements
Y
Y Combinator Blog
罗磊的独立博客
D
Docker
酷 壳 – CoolShell
酷 壳 – CoolShell
云风的 BLOG
云风的 BLOG
人人都是产品经理
人人都是产品经理
WordPress大学
WordPress大学

Go 编程语言

WorkBuddy + 企业微信 有人用过吗? - V2EX 业务系统开发语言从 PHP / Python 转向 Go 如何实现数据的存储 - V2EX [2026 最新] Go 1.27 泛型方法完全解读 - V2EX Go 后台接入 SSO 后,本地用户表应该怎么退? - V2EX clbtransport - 自带负载均衡的 http.Transport 简易替代 - V2EX 基于 go+react 开发的一款可私有化部署、任务驱动的代码托管平台 iForge - V2EX 分享一个还在 RC 阶段的项目:把 C 编译成不依赖 cgo 的 Go package - V2EX [工业边缘网关] EdgeX 更新啦,增加了 MCP,可以被 AI 接管了,可以让 AI 控制物理世界啦 [工业边缘网关] EdgeX 更新啦 上线了官网 顺便更新版本 v0.10.0 有了 Agent,后台脚手架是该退场,还是变成约束层? 强兼 Claude Code:左 DeepSeek V4,右 Kimi K3 构建时自动注入版本号:从 Git tag 到 Go 可执行文件 [边缘计算] 想借助 AI 来开发 边缘网关接入 AI 解决设备通信 不知道有没有需求 [Go]工业边缘计算网关 edgex 业余时间独立开发 持续更新半年咯 求个 star eget:不用等中央仓库,直接安装 GitHub 和任意下载站的工具 gookit/gcli v3.8:新增支持共享选项、文档生成和参数重排 V2EX 我开源了一款极简的 P2P 文件传输与 VPN 工具 (Win/Mac/ Linux /Android) gookit/gcli v3.5.0 发布 - 简单易用、功能丰富的 Go 命令行应用与工具库 用 Go gRPC 信令对接 Android WebRTC:大家一般怎么划分网关和客户端边界? 在服务端用 Pion + FFmpeg + RNN 做 WebRTC 通话降噪,值得吗? 这两种 lint 报错场景, v 友们一般怎么处理 tikrok 的部分来时路 tikrok 第 7 代微服务重构: Golang 微服务 grpc 接口与服务实现隔离方案 写了个 Go 库:在 TCP 应用协议开始前做认证,也支持端口敲门 Tikrok v6.0 新的生产级架构,成本不变,但是部署复杂略有增加 go 打包的二进制程序怎么反编译 [边缘计算开源] 工业数据采集网关 版本更新 用 golang 写了,一套面向个人音乐资产的本地优先音乐系统 做了个 Go 的 MCP Server 框架,一行代码把 Gin API 接入 AI
为什么不用 gRPC-Go: VictoriaTraces 中实现 OTLP/gRPC 的幕...
RedisMasterNode · 2025-10-24 · via Go 编程语言

我们不妨先从结论开始,不使用 gRPC-Go 来构建 OTLP/gRPC 的 gRPC Server ,可以让:

  • 二进制包的体积:降低 25%
  • CPU 使用率:降低 36%

背景

OpenTelemetry 协议( OTLP )是 “有 OpenTelemetry 插装的应用” 与 “OpenTelemetry (兼容的) Collector/后端” 之间进行数据传输所用的协议。

现在假设你就有这样一个应用,想要传输数据到 Collector ,那么你可以配置通过以下 Exporter 完成:

  • OTLP/gRPC Exporter
  • OTLP/HTTP Exporter, 其中 Protobuf 的 Payloads 可以编码成:
    • - binary 格式
    • - JSON 格式

出于一些原因,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 工具链呢?

原因 1: Protoc 工具链不好用

以 Go 为例,最常见的构建 gRPC Server 的方式就是:

  • protocprotoc-gen-go 生成 .proto 中定义的 Message 对应的结构体。
  • protocprotoc-gen-go-grpc 生成 .proto 中定义的服务 Interface 并实现它。

说起来简单,不妨先试想一下这些步骤具体是怎么做的。假设我(对 Protobuf 熟悉程度一般)现在 git clone 了一个项目,然后想往其中的 .proto 添加几个新的 Message 和方法:

  1. 首先想起来 protoc 并不是 Ubuntu/MacOS 自带的;
  2. Release 页下载最新版本的 protoc
  3. 诶,光靠 protoc 不能生成 Go 代码,所以还需要 go install protoc-gen-gogo install protoc-gen-go-grpc
  4. 都准备好了,生成代码的命令是什么来着? Google 一下 “gRPC 如何编译 Go 代码”;
  5. 终于,抄到了命令在本地运行,回车。Boom !弹了个依赖 Error ,因为 .proto 里面有一些 import,还要将这些引用的内容的目录在命令中指定清楚;
  6. 整理好所有目录和命令,重新运行,终于成功生成出了代码。

不过,别高兴得太早,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 接口那么简单。

当然,它可以变得简单一点:

  1. 把所有的命令都写到 Makefile
  2. 或者使用 Buf CLI,把代码生成完全在线化。

不过仍然有很多工程师就是喜欢 HTTP JSON 。

尽管麻烦不断,但是这些仍不足以说服我们弃用 protoc 工具链。还有别的理由 (借口) 吗?

写在原因 2 之前

原因 2 或许能给你新的启发,不过需要声明,因为 VictoriaTraces 中存在一些历史包袱,它才能在该项目中作为一个 “原因”。也就是说,这个 “原因” 并不是每个项目都该纳入考虑的。

原因 2: easyproto 已经代替了 golang/protobuf

在 VictoriaMetrics ,VictoriaLogs 和 VictoriaTraces 中,对于 Protobuf 的内容,我们是用 easyproto 来进行 Marshal 和 Unmarshal 操作的,而非常见的 golang/protobuf 包。原因在 easyprotoREADME 中写得很清楚:

  • easyproto 不需要 protoc 或者 go generate
  • easyproto 不会像传统的 protoc 那样让编译的二进制包的体积增大。
  • easyproto 正确使用的话可以达成零内存分配。

不过,如果真的要实现 OTLP/gRPC 支持,我们得考虑:

  1. 如果用了 protoc 生成 gRPC Server ,那编译的二进制包会大多少
  2. 有可能将 easyproto 和 gRPC 结合起来用吗?protoc 只需要生成 gRPC 服务的代码,而 Protobuf Message 的结构体还是用 easyproto 处理。
    • 注意这仍然需要引入 gRPC 相关的包,这会弱化使用 easyproto 的第二个理由(减小二进制包体积)。
  3. 还有其他办法可以复用现有的 easyproto,而不用引入任何新的包吗

邪门歪道: 用 HTTP/2 Server 代替 gRPC Server

按理来说

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 实现方案的二进制包的体积资源使用率,这些方案包括:

  1. 用原生 HTTP/2 Server 处理请求,用 easyproto 来处理 Protobuf Message 。
  2. 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):

    • HTTP/2 + easyproto: 87M
    • gRPC + easyproto: 113M (+29%)
    • gRPC: 113M (+29%)
  • 单个 linux-amd64 包体积:

    • 参考基准 (v0.4.0): 21M
    • HTTP/2 + easyproto: 21M (+0%)
    • gRPC + easyproto: 28M (+33%)
    • gRPC: 28M (+33%)

请求处理的资源使用率( CPU 使用率, no-op: 对每个请求仅进行 Decompression 和 Unmarshalling):

  • HTTP/2 + easyproto: 31.3%
  • gRPC + easyproto: 45.6% (+45%)
  • gRPC: 49.1% (+56%)

  • 资源监控 Snapshot 可以查看这里

  • CPU 和内存的 Profiles 可以在这里下载。

从这些结果可以看出,HTTP/2 + easyproto 确实更占优一些。

总结

这篇博客分享的是“为什么 VictoriaTraces 用 HTTP/2 + easyproto 来实现 OTLP/gRPC 所需的 gRPC Server”。它的关键实现是由 JayiceZ 完成的,而最初的想法来自于 @makasim

这个实现的背后有很多特定的原因,我们并不是想说服你也这样做,但是我们在测试中确实看到了这种方案的潜力和价值。

VictoriaStack 的亮点是高性能和资源优化,所以每一分 CPU 、内存和网络流量都很重要。当然,这同样也适用于二进制包、Docker 镜像的体积等等,就如这些要素也曾在 Aliaksandr Valialkin( VictoriaMetrics 的作者)写的这篇博客中被提到,一直以来它们都没变过。