阅读视图

发现新文章,点击刷新页面。
🔲 ☆

一天重写 JSONata,我用 400 美元干掉了公司 50 万美元的 K8s 集群

本文永久链接 – https://tonybai.com/2026/04/01/rewrote-jsonata-in-golang-with-ai

大家好,我是Tony Bai。

过去的几年,我们见证了 AI 编程工具从“玩具”到“神器”的进化。无数开发者都在分享自己效率翻倍的喜悦。

你有没有想过,用 AI 来完成一次“外科手术式”的精准重构,一天之内,就能帮你把公司每年烧掉的 50 万美元(约 360 万人民币)的服务器成本,直接砍到零?

这听起来像天方夜谭,但它真实地发生了。

就在前几天,以色列安全公司 Reco 的工程师 Nir Barak 发表了一篇极其硬核的博客。他详细复盘了自己是如何在一天之内,花费了仅仅 400 美元的 Token 费用,利用 AI 将一个用 JavaScript 编写的核心组件 JSONata,完美地重写为了纯 Go 版本,最终为公司节省了每年 50 万美元的开销,并带来了 1000 倍的性能提升。

这不仅仅是一个“AI 真牛逼”的简单故事。它背后揭示的,是一套足以改变我们未来架构选型和技术债偿还方式的“AI 驱动重构(AI-Driven Refactoring)”实用方法。

跨语言 RPC,微服务架构中最昂贵的“性能税”

要理解这次重构的意义有多么重大,首先得看看 Nir Barak 的团队曾经陷入了多深的泥潭。

他们的核心业务是一个用 Go 编写的高性能数据管道,每天处理数十亿的事件。但其中有一个环节,需要用到一个名为 JSONata 的查询语言(你可以把它想象成带 Lambda 函数的 jq)来执行动态策略。

尴尬的是,JSONata 的官方实现是 JavaScript 写的。

这就导致了一个极其痛苦的架构:他们的主业务 Go 服务,为了执行这些规则,不得不去远程调用(RPC)一个专门部署在 Kubernetes 上的庞大的 Node.js 服务集群。

这个“小小的”跨语言调用,给他们带来了三大噩梦:

  1. 恐怖的成本:为了扛住流量,这个 jsonata-js 集群常年需要维持 300 多个 Pod 副本,光是这部分,每年就要烧掉 30 万美元的计算资源。
  2. 惊人的延迟:一次最简单的字段查找,比如 email = “admin@co.com”,在 Node.js 内部执行可能只需要几纳秒。但算上序列化、跨进程网络往返的开销,一次 RPC 调用在啥也没干之前,150 微秒的延迟就先进来了。对于一个每天处理几十亿事件的系统来说,这简直是灾难。
  3. 意想不到的运维黑洞:随着业务增长,Pod 数量一度多到耗尽了 Kubernetes 集群的 IP 地址分配上限!

Nir Barak 的团队当然也尝试过各种小修小补:优化表达式、加缓存、甚至用 CGO 把 V8 引擎直接嵌进 Go 里……但这些都只是“头痛医头”,无法根治“跨语言”这颗毒瘤。

Cloudflare 的“抄作业”哲学

转机发生在前几周。Nir Barak 看到了 Cloudflare 那篇刷爆全网的文章《我们如何用 AI 在一周内重构 Next.js》。

Cloudflare 的做法极其“暴力”且有效:他们没有让 AI 去创造新东西,而是把 Next.js 现成的spec,以及包含几千个 case 的官方测试套件(Test Suite)直接扔给大模型,然后对 AI 下达了一个简单粗暴的指令:

“我不管你怎么实现,给我写一个能在 Vite 上跑通所有这些测试的 API 就行!”

Nir Barak 看到这里,瞬间被点醒了:“我们面临的问题一模一样!我们也有 jsonata-js 官方那套包含 1778 个测试用例的完整套件啊!”

与其让 AI 去搞创新,不如把它变成一个任劳任怨、24 小时待命的“代码翻译工”!

于是,他花了一个周末,用 AI 制定了一个极其清晰的“三步走”作战计划:

  1. 第一步(人类智慧):用 Go 语言把 jsonata-js 的测试套件先“翻译”过来。
  2. 第二步(AI 体力):把 JSONata 2.x 的官方文档和规范全部喂给 AI。
  3. 第三步(测试驱动):对 AI 下达指令:“开始写 Go 代码,目标是跑通第一步的所有测试用例。”

第二天,他按下了“开始键”。

7 小时,400 美元,13000 行 Go 代码

接下来的故事,充满了令人肾上腺素飙升的极客快感。

Nir Barak 坐在电脑前,看着 AI Agent 像一台失控的缝纫机一样,疯狂地生成 Go 代码、运行测试、读取报错、然后自我修正。

整个过程被划分成了几个“波次(Waves)”:先实现核心解析器,再实现内置函数,最后处理各种边缘 case。

在 AI 与测试用例的左右互搏之下,仅仅 7 个小时 后,奇迹发生了:

一个包含 13,000 多行纯 Go 代码的、名为 gnata 的全新 JSONata 实现诞生了。它完美通过了官方所有的 1778 个测试用例。

而这整个过程的成本呢?

400 美元的 Token 费用。

Nir Barak 在博客中晒出了一张截图,数据显示,在重构 gnata 的那一天,AI 生成的代码占比高达 91.7%

当他把这个 PR 提交到公司内部时,立刻有人质疑 ROI(投资回报率)。而他的回答简单粗暴:

“上个月,jsonata-js 集群的成本是 2.5 万美元。现在,是 0。”

百倍性能与意外之喜:“手术刀式”重构的深远影响

成本降为零已经足够震撼,但性能上的收益更是堪称“恐怖”。

这还只是开始。由于 gnata 是纯 Go 实现,Nir Barak 团队得以进行更深度的“魔改”:他们设计了一套两层评估架构。对于简单的字段查找,gnata 直接在原始的 JSON 字节流上操作,实现了 零堆内存分配(Zero Heap Allocations)!只有遇到复杂表达式时,才会启动完整的解析器。

在接下来的两周内,他们乘胜追击,用 gnata 的批量处理能力,替换掉了主数据管道中另一个极其臃肿、靠启动上万个 Goroutine 来并发处理规则的旧引擎。 结果:又省下了每年 20 万美元。

短短两周,两次“外科手术式”的重构,总共为公司节省了每年 50 万美元的开销。

最让人意想不到的是,这次重构还带来了组织层面的“意外之喜”:

gnata 是公司内部第一个完全由 AI Agent 大规模参与生成的 PR。在 Code Review 的过程中,团队成员被迫去学习如何分辨“AI 真正发现的并发 Bug”和“AI 瞎操心的代码格式问题”。这次经历,为他们后续制定全公司的 AI Code Review 规范积累了宝贵的实战经验。

小结:我们不再只是“氛围感编码”

在文章的结尾,Nir Barak 提到了 AI 大神 Andrej Karpathy 最近的观点,大意是:

“编程正在变得面目全非。在底层,深厚的技术专长正成为比以往任何时候都更强大的‘乘数效应放大器’。”

Nir Barak 感慨道,直到最近,他自己都对那种完全由 AI Agent 生成代码的“氛围编码(Vibe coding)”持怀疑态度。但 2026 年 2 月,成为了一个连他这样固执的开发者都无法忽视的“拐点”

gnata 的诞生,标志着我们不再只是用 AI 去写一些无关紧要的玩具项目。在拥有明确测试用例和边界规范的前提下,AI 已经具备了对生产环境核心组件进行“手术刀式重构”的惊人能力。

你准备好拿起这把名为“AI”的手术刀,去切掉你系统里那些最昂贵、最臃肿的“技术肿瘤”了吗?

资料链接:https://www.reco.ai/blog/we-rewrote-jsonata-with-ai


今日互动探讨:

在你的公司里,是否存在类似的“异构技术栈”调用导致的性能瓶颈或成本黑洞?你有没有想过,可以用 AI + 测试用例的方式,对某个核心组件进行“代码翻译”式的重构?

欢迎在评论区分享你的架构痛点与大胆构想!


还在为“复制粘贴喂AI”而烦恼?我的新专栏 AI原生开发工作流实战 将带你:

  • 告别低效,重塑开发范式
  • 驾驭AI Agent(Claude Code),实现工作流自动化
  • 从“AI使用者”进化为规范驱动开发的“工作流指挥家”

扫描下方二维码,开启你的AI原生开发之旅。


你的Go技能,是否也卡在了“熟练”到“精通”的瓶颈期?

  • 想写出更地道、更健壮的Go代码,却总在细节上踩坑?
  • 渴望提升软件设计能力,驾驭复杂Go项目却缺乏章法?
  • 想打造生产级的Go服务,却在工程化实践中屡屡受挫?

继《Go语言第一课》后,我的《Go语言进阶课》终于在极客时间与大家见面了!

我的全新极客时间专栏 《Tony Bai·Go语言进阶课》就是为这样的你量身打造!30+讲硬核内容,带你夯实语法认知,提升设计思维,锻造工程实践能力,更有实战项目串讲。

目标只有一个:助你完成从“Go熟练工”到“Go专家”的蜕变! 现在就加入,让你的Go技能再上一个新台阶!


商务合作方式:撰稿、出书、培训、在线课程、合伙创业、咨询、广告合作。如有需求,请扫描下方公众号二维码,与我私信联系。

© 2026, bigwhite. 版权所有.

🔲 ☆

2026 年了,写 Go + Protobuf 还在手敲 protoc 命令?是时候换用这种新姿势了!

本文永久链接 – https://tonybai.com/2026/03/05/modern-go-protobuf-dev-in-2026

大家好,我是Tony Bai。

在现代后端开发领域,Go 语言与 Protocol Buffers(简称 Protobuf)加上 gRPC 的组合,早已成为构建高性能微服务架构的“行业标准”。这两者的结合在网络传输效率、强类型契约以及跨语言互操作性上展现出了无与伦比的优势。

然而,令人感到魔幻的是,随着 Go 语言本身的生态在过去几年里飞速进化(从 GOPATH 到 Go Modules,从混乱的依赖管理到极其统一且优雅的标准工具链),处理 Protobuf 文件的代码生成环节,却长期停留在一种“上古时代”的原始状态。

就在最近,技术社区 Reddit 的 r/golang 板块上出现了一则引发大家共鸣的帖子。一位开发者提出下面拷问:

“I was wondering what is the preferred way to do golang + protobuf in 2026. Do I still have to download protoc or are there any natives I can use with the golang compiler.”
(我想知道 2026 年 Go + protobuf 的首选开发方式是什么?我是否仍然必须下载 protoc,或者 Go 编译器有没有内置原生的支持?)

这不仅是这位开发者的困惑,更是无数长期忍受繁琐工具链的 Gopher 们的心声。在跟帖回复中,社区开发者们给出了一个相对主流的的答案:Go 编译器本身并没有,也不打算内置解析 .proto 的功能,但是,所有严肃的现代工程团队都开始在用 Buf (buf.build) 替代原生 protoc 工具链了。

本文将深入剖析 2026 年的现代 Protobuf 工程化实践。我将带你领略为什么 buf CLI 是当之无愧的现代化首选,以及它是如何彻底终结“手敲 protoc 命令”这一痛苦历史的。

核心痛点:为什么原生 protoc 令人抓狂?

在请出主角 Buf 之前,我们需要先深刻理解,传统的 protoc 工作流到底哪里出了问题,以至于整个社区都在寻求替代方案。

如果你在过去几年使用过原生的方式在 Go 中生成 Protobuf 代码,你的项目里极大概率会存在一个类似于下面这样“臭名昭著”的 Makefile 或 build.sh 脚本:

# 传统项目中常见的“野生” Makefile 节选
.PHONY: generate-proto

PROTO_FILES=$(shell find api -name "*.proto")

generate-proto:
    @echo "Generating Go code from Protobuf..."
    protoc \
        -I api \
        -I /usr/local/include \
        -I $(GOPATH)/pkg/mod/github.com/grpc-ecosystem/grpc-gateway@v1.16.0/third_party/googleapis \
        --go_out=gen/go \
        --go_opt=paths=source_relative \
        --go-grpc_out=gen/go \
        --go-grpc_opt=paths=source_relative \
        $(PROTO_FILES)

这段看似能跑的脚本背后,隐藏着令开发者抓狂的三大“原罪”:

  1. 环境依赖的地狱

要成功运行上述命令,你的机器(以及所有协作者的机器、甚至是 CI/CD 流水线的容器)上必须预先安装 C++ 编写的 protoc 编译器核心二进制文件。此外,你还需要通过 go install 将正确版本的 protoc-gen-go 和 protoc-gen-go-grpc 插件安装到系统的 $PATH 目录下。任何一个人机器上的版本不一致,都会导致生成的 Go 代码带有微小的差异,最终在 Git 提交中引发无意义的代码冲突。

  1. 路径导入的迷宫 (-I 噩梦)

protoc 是基于文件系统的。如果你的 .proto 文件中引入了第三方的定义(例如 import “google/api/annotations.proto”; 以支持 HTTP 网关),你必须在机器上找到这些第三方文件的物理存放路径,并通过极度冗长且极易出错的 -I(–proto_path)参数将它们一个个拼接起来。

  1. 缺乏规范约束与破坏性变更保护

protoc 仅仅是一个编译器,它完全不在乎你的字段命名是否符合团队规范(例如把字段命名为 camelCase 而不是官方推荐的 snake_case)。更致命的是,当你随意删改已经在线上运行的字段类型时,protoc 会毫无波澜地为你生成新的代码,直到代码发布导致客户端反序列化崩溃,你才会发现酿成了大祸。

开发者的精力应该集中在业务逻辑的设计上,而不是每天在终端里调试 protoc 的环境变量和路径参数。这就是 Buf CLI 诞生的核心驱动力。

Buf CLI 闪亮登场:声明式的现代 Protobuf 工具链

Buf(由 buf.build 公司开发)并不是另一个像 protoc-gen-go 一样的单点插件,而是一套完全由 Go 语言编写、开箱即用、向下兼容 Protobuf 语法的全链路现代编译器套件。

它的核心设计哲学非常清晰:

  • 声明式配置:用简洁的 YAML 文件取代面条式的 Shell 命令。
  • 一致性保障:无论在本地开发机还是远程 CI 环境,保证 100% 的生成结果一致。
  • 工程化内置:将代码规范检查(Linting)和向后兼容性检测(Breaking Change Detection)作为一等公民内置于 CLI 中。

为了真正理解它的强大,接下来我们将基于一个干净的 Linux (Ubuntu/Debian 或类似发行版) 环境,从零开始构建一个微服务的 API 契约层,带你体验这套全新的开发范式。

零基础环境搭建与项目初始化

步骤 1:安装 Go 与 Buf CLI

首先,确保你的 Linux 环境中已经安装了 Go 语言(建议使用 Go 1.22 或更高版本)。

由于 Buf CLI 自身就是用 Go 编写的,因此在 Linux 下安装它最简单、最不易出错的方式就是直接下载预编译好的单体二进制文件,或者通过 go install。为了全局可用且版本可控,我们使用官方推荐的下载脚本:

# 下载适用于 Linux x86_64 架构的 buf CLI v1.66.0 (请根据实际情况调整版本号)
# 以及protoc-gen-buf-breaking、protoc-gen-buf-lint工具

# Substitute PREFIX for your install prefix.
# Substitute VERSION for the current released version.
PREFIX="/usr/local" && \
VERSION="1.66.0" && \
curl -sSL \
"https://github.com/bufbuild/buf/releases/download/v${VERSION}/buf-$(uname -s)-$(uname -m).tar.gz" | \
tar -xvzf - -C "${PREFIX}" --strip-components 1

# 验证安装成功
$ buf --version
1.66.0

极其清爽的体验:仅仅这一个只有几十 MB 的二进制文件,就涵盖了后续我们需要的所有核心功能,你完全不需要再去单独使用 apt-get install protobuf-compiler 安装传统的 protoc!

步骤 2:创建项目结构与编写 Protobuf IDL

我们在当前用户的主目录下创建一个名为 acme-shop 的微服务项目,并初始化 Go Module:

$ mkdir -p acme-shop && cd acme-shop
$ go mod init github.com/acme/shop

接着,按照现代工程的最佳实践,我们将 Protobuf 文件与具体的 Go 业务代码隔离开来。我们创建一个 proto 目录专门存放接口定义(IDL):

# 创建目录层级
$ mkdir -p proto/acme/order/v1

使用你喜欢的编辑器(如 vim, nano 或 VSCode),在 proto/acme/order/v1/order.proto 中写入以下内容:

// proto/acme/order/v1/order.proto
syntax = "proto3";

package acme.order.v1;

// go_package 是必须的,它告诉工具生成的 Go 代码最终属于哪个 import path
option go_package = "github.com/acme/shop/gen/go/acme/order/v1;orderv1";

import "google/protobuf/timestamp.proto";

// 订单服务接口定义
service OrderService {
  rpc CreateOrder(CreateOrderRequest) returns (CreateOrderResponse) {}
}

message CreateOrderRequest {
  string customer_id = 1;
  double amount = 2;
}

message CreateOrderResponse {
  string order_id = 1;
  google.protobuf.Timestamp created_at = 2;
}

请注意,在这个文件中我们引入了标准的 google/protobuf/timestamp.proto。在传统方式下,你必须确保你的机器上存在这个标准库文件,而在接下来 Buf 的演示中,你会看到它是如何自动化处理这一切的。

彻底告别命令行黑魔法:Buf 核心功能实战

步骤 3:初始化 Buf 模块 (The buf.yaml)

传统的 protoc 需要你每次在命令行指定要编译哪些文件。Buf 引入了“工作区(Workspace)”和“模块(Module)”的概念。

在项目的 proto 目录下,我们通过 buf mod init 命令(最新版本的buf建议使用buf config init)来声明这是一个受 Buf 管理的 Protobuf 模块:

$ cd proto
$ buf mod init
$ cd ..

这会在 proto/ 目录下生成一个非常简洁的 buf.yaml 文件,内容类似如下(基于当前默认的 v1 版本,若是更高版本可能是 v2):

# For details on buf.yaml configuration, visit https://buf.build/docs/configuration/v2/buf-yaml
version: v2
lint:
  use:
    - STANDARD
breaking:
  use:
    - FILE

这个看似简单的文件意义非凡。它告诉 Buf CLI:当前目录(proto)的根路径,就是所有 .proto 文件导入路径(Import Path)的起点。你从此再也不用在任何地方手写令人头疼的 -I /path/to/proto 参数了。 此外,它还激活了默认的代码规范规则(lint)和兼容性检测规则(breaking)。

步骤 4:零配置的代码规范检查 (buf lint)

在传统开发中,Protobuf 的风格往往是一笔糊涂账。现在,Buf 直接将静态代码分析带到了你的终端。

让我们故意在 order.proto 中犯一个小错。打开 proto/acme/order/v1/order.proto,将请求消息的字段名改成驼峰式命名:

message CreateOrderRequest {
  // 故意违反 protobuf 推荐的 snake_case 命名规范
  string customerId = 1;
  double amount = 2;
}

回到终端,在项目根目录(acme-shop)下运行检查命令:

$ buf lint proto

输出结果清晰得令人拍案叫绝:

proto/acme/order/v1/order.proto:18:10:Field name "customerId" should be lower_snake_case, such as "customer_id".

Buf 指出了具体的文件、行号、列号,甚至直接给出了修改建议。这使得将 Protobuf 规范集成到 Git Pre-commit Hook 或 CI/CD 流水线中变得易如反掌。将代码改回 customer_id 后,再次运行 buf lint proto,将没有任何输出,代表检查通过。

步骤 5:声明式代码生成 (buf.gen.yaml)

重头戏来了。我们要用一种极其优雅的方式,取代前面提到的长串 protoc 命令和冗长的 Makefile。

在项目根目录(acme-shop)下,新建一个文件名为 buf.gen.yaml 的生成配置文件:

# buf.gen.yaml
version: v1
plugins:
  # 插件 1:生成基础的 Go struct 代码
  - plugin: go
    out: gen/go
    opt: paths=source_relative
  # 插件 2:生成 gRPC 客户端/服务端接口代码
  - plugin: go-grpc
    out: gen/go
    opt: paths=source_relative

在这个配置文件中,我们声明了需要使用哪两个插件(go 和 go-grpc),生成的代码输出到哪里(out: gen/go),以及附加的选项(opt: paths=source_relative 确保生成的目录结构与 proto 文件结构保持一致)。

【纯本地环境的准备工作】

由于我们在配置中指定了具体的插件名称(go 和 go-grpc),当运行 Buf 时,它会在你的系统环境中寻找名为 protoc-gen-go 和 protoc-gen-go-grpc 的可执行文件。因此,仅仅是为了完成本地代码生成这一步,我们依然需要使用 Go 官方工具获取这两个插件:

$ go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
$ go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest

# 确保安装后的protoc-gen-go和protoc-gen-go-grpc在系统 $PATH 中

注意:虽然这里依然下载了本地插件,但这已经是你在本地唯一需要管理的外部依赖了。核心的编译器、路径解析、规范约束都已经被 Buf 接管。稍后我们会讲到如何通过 BSR 甚至连这一步都省略掉。

步骤 6:一键执行,见证优雅 (buf generate)

万事俱备,现在只需在项目根目录执行极其简单的一句命令:

$ buf generate proto

就是如此朴实无华。没有任何屏幕乱码,没有任何报错。

我们可以查看目录结构,生成的代码已经按照包结构完美地放置在了预期位置:

$ tree -F gen/go
gen/go/
└── acme/
    └── order/
        └── v1/
            ├── order.pb.go
            └── order_grpc.pb.go

这一句 buf generate 的执行是幂等且高度一致的。你可以放心地将 buf.gen.yaml 提交进版本控制库。任何新加入的同事,只要执行这一句命令,得到的永远是一模一样的结果。

步骤 7:防范接口灾难的“保护伞” (buf breaking)

企业级开发中,Protobuf 被用于构建微服务间强契约的 API。如果你随意删除了一个字段,或者修改了字段的类型(比如从 int32 改为 string),依赖于旧接口的客户端在解析新数据时将直接崩溃。

传统 protoc 对此无能为力,必须靠开发者人工审查。但 Buf CLI 提供了业界最强的 breaking change(破坏性变更)检测功能。

让我们模拟一次灾难。打开 proto/acme/order/v1/order.proto,我们将 amount 字段的标号从 2 改为 3(在 Protobuf 中,变更字段编号是非常严重的向后不兼容行为,会导致序列化错乱):

message CreateOrderRequest {
  string customer_id = 1;
  // 危险操作:修改了原有字段的标号
  double amount = 3;
}

为了检测出这个变更,我们需要将当前状态与过去的某个状态(例如我们上一次的稳定状态,或者 Git 的 main 分支)进行对比。由于我们的演示项目还没提交过 Git,Buf 提供了一个非常灵活的对比方法,可以直接对比文件系统的快照或者之前的目录。

假设我们在修改前,将原始正确的 proto 文件备份在了 proto_backup 目录中。我们可以这样运行检测:

$ buf breaking proto --against proto_backup

Buf 会立刻阻止你,并在终端输出刺眼的错误提示:

$ buf breaking proto --against proto_backup
proto/acme/order/v1/order.proto:17:1:Previously present field "2" with name "amount" on message "CreateOrderRequest" was deleted.

它准确地指出你删除了编号为 2 的字段。如果在一个接入了 Git 仓库的真实项目中,你通常会运行:

# 检测当前代码库中的 proto 相对 Git main 分支的最新提交是否发生向后兼容性破坏
$ buf breaking proto --against '.git#branch=main'

只需将这行简单的命令加入到你的 CI 流水线(如 GitHub Actions 或 GitLab CI)中,你的团队就彻底杜绝了因疏忽导致的 API 不兼容事故。

深度解析:BSR (Buf Schema Registry) 究竟解决了什么问题?

到目前为止,我们所有的演示都是在纯本地、完全离线的环境下进行的。

我们证明了:即便你完全不使用云端服务,仅仅是将原生的 protoc 替换为 buf CLI,依然能获得巨大无比的工程化收益(免配置导入路径、内置代码校验、极其简洁的生成配置、强大的向后兼容性保护)。

但是,如果你想了解 2026 年 Protobuf 生态演进的最前沿,就必须提到 Buf 公司推出的杀手级 SaaS 平台:Buf Schema Registry (BSR)

BSR 可以被理解为 “Protobuf 界的 npm 或 Docker Hub”。如果没有 BSR,你的本地开发依然会面临两个难以根除的痛点:

痛点一:第三方公共 API 文件的搬运工

在纯本地模式下,如果你的业务需要使用 HTTP 网关网关(如 grpc-gateway),你的 order.proto 就必须写上 import “google/api/annotations.proto”;。

没有 BSR 时,你需要手工管理: 你必须去 Google 的 GitHub 仓库里把 annotations.proto 及其级联依赖文件下载下来,在自己的项目里建一个 third_party/google/api/ 目录存放进去。这不仅污染了项目结构,还需要人工维护版本更新。

BSR 解决之道:远程模块依赖 (Remote Modules)

BSR 上托管了成千上万的知名开源 Protobuf 库。当你使用 BSR 时,你只需要在 proto/buf.yaml 中声明一句依赖:

# 开启 BSR 远程依赖后
version: v1
deps:
  # 直接声明依赖 Google API 的云端模块
  - buf.build/googleapis/googleapis

然后在终端运行一句 buf mod update,Buf CLI 就会像 go mod 拉取 Go 源码一样,自动将所需的 .proto 文件从云端缓存到你的本地(开发者甚至感知不到)。你的代码库瞬间变得干净纯粹,只需关注自身的业务 IDL。

痛点二:本地生成插件的管理成本

在上文的步骤 5 中,我们依然需要使用 go install 安装 protoc-gen-go 等二进制文件。如果团队有人使用的是 Windows,有人用 macOS,维护本地插件栈依然存在轻微的不便。

BSR 解决之道:远程执行引擎与云端插件 (Remote Plugins)

这是颠覆式的一项创新。如果你愿意借助 BSR 的云端基础设施,你可以彻底删除本地所有的 protoc-gen-xxx 二进制文件

我们只需将 buf.gen.yaml 改造为指向云端的插件:

# 依托 BSR 远程插件生态的 buf.gen.yaml
version: v1
plugins:
  # 注意 plugin 前缀变成了云端地址
  - plugin: buf.build/protocolbuffers/go:v1.36.11
    out: gen/go
    opt: paths=source_relative
  - plugin: buf.build/grpc/go:v1.6.1
    out: gen/go
    opt: paths=source_relative

在这个配置下,当你运行 buf generate proto 时(为了见证奇迹,你可以将你本地安装的protoc-gen-go和protoc-gen-go-grpc都删除掉),发生的事情堪称魔法:

  1. Buf CLI 将你的 .proto 文件作为有效负载(Payload)发送到 BSR 的云端编译集群。
  2. BSR 服务器调用官方认证的插件环境为你生成对应的 Go 代码。
  3. 编译好的 .pb.go 文件通过网络流瞬间返回并精准投放到你本地的 gen/go 目录下。

这不仅统一了所有成员的编译器环境版本,更将开发者的本地负担降到了绝对零度:只需安装一个 buf 二进制,就能编译世间万物。 (当然,如果你的网络环境受限,依然可以随时回退到上文介绍的本地插件模式配置。)

小结与展望

在当前的 Go 开发生态中,“不要重复发明轮子,而应拥抱标准工具链”是大家共同的准则。过去几年,处理 Protobuf 犹如陷入一片充满陷阱的沼泽,开发者们花费了大量心智与那些毫无价值的 CLI 参数作斗争。

随着时间来到 2026 年,我们欣喜地看到,整个社区对于构建现代化 API 契约的认知已经彻底觉醒。通过本文详实的演练,我们可以得出一个极度确定的结论:

  1. 停用手写的 protoc Shell 脚本:它在代码重用性、跨平台一致性和防范人为灾难方面毫无招架之力。
  2. 全面拥抱 Buf CLI:将 buf mod init、buf lint、buf breaking 纳入每一个微服务项目的初始化模板。它是现代 Protobuf 工程化当之无愧的选择,即使完全脱离 BSR 服务作为本地工具使用,其体验也是颠覆性的。
  3. 了解 BSR 的架构演进思路:依赖的包袱就该交给包管理器(如远程模块管理)去解决,这代表了系统级应用开发的未来趋势。

还在维护祖传的 Makefile 吗?赶紧删掉那些脚本吧,在新项目里安装 buf,开启你的现代protobuf代码生成之旅吧!你的开发体验,值得这样的升级。

本文涉及的代码在这里可以下载。

资料链接:

  • https://www.reddit.com/r/golang/comments/1rapxyq/golang_protobuf_in_2026/
  • https://buf.build/docs/cli/

你的 protoc 脚本有多少行?

传统的 protoc 确实让人爱恨交织。在你的项目中,为了维护一套跨平台的 Protobuf 生成环境,你踩过哪些最离谱的“坑”?你认为 Buf 这种云端插件模式(BSR)会在国内企业环境下大规模落地吗?

欢迎在评论区分享你的看法或吐槽!


还在为“复制粘贴喂AI”而烦恼?我的新专栏 AI原生开发工作流实战 将带你:

  • 告别低效,重塑开发范式
  • 驾驭AI Agent(Claude Code),实现工作流自动化
  • 从“AI使用者”进化为规范驱动开发的“工作流指挥家”

扫描下方二维码,开启你的AI原生开发之旅。


你的Go技能,是否也卡在了“熟练”到“精通”的瓶颈期?

  • 想写出更地道、更健壮的Go代码,却总在细节上踩坑?
  • 渴望提升软件设计能力,驾驭复杂Go项目却缺乏章法?
  • 想打造生产级的Go服务,却在工程化实践中屡屡受挫?

继《Go语言第一课》后,我的《Go语言进阶课》终于在极客时间与大家见面了!

我的全新极客时间专栏 《Tony Bai·Go语言进阶课》就是为这样的你量身打造!30+讲硬核内容,带你夯实语法认知,提升设计思维,锻造工程实践能力,更有实战项目串讲。

目标只有一个:助你完成从“Go熟练工”到“Go专家”的蜕变! 现在就加入,让你的Go技能再上一个新台阶!


想系统学习Go,构建扎实的知识体系?

我的新书《Go语言第一课》是你的首选。源自2.4万人好评的极客时间专栏,内容全面升级,同步至Go 1.24。首发期有专属五折优惠,不到40元即可入手,扫码即可拥有这本300页的Go语言入门宝典,即刻开启你的Go语言高效学习之旅!


商务合作方式:撰稿、出书、培训、在线课程、合伙创业、咨询、广告合作。如有需求,请扫描下方公众号二维码,与我私信联系。

© 2026, bigwhite. 版权所有.

🔲 ☆

谁才是 Go 生态的“幕后之王”?—— 深度挖掘 4000 万个节点后的惊人发现

本文永久链接 – https://tonybai.com/2026/01/09/the-most-popular-go-dependency-is

大家好,我是Tony Bai。

在 Go 的世界里,我们每天都在引入各种 import。但你是否想过,整个 Go 生态系统中,究竟哪个包是被依赖次数最多的“基石”?

通常,我们会参考 GitHub Stars 或 Awesome 列表,但这往往带有主观偏差。为了寻找最客观的答案,开发者 Thibaut Rousseau 做了一件疯狂的事他下载了 Go Proxy 自 2019 年以来的所有模块元数据,构建了一个包含 4000 万个节点、4 亿条关系的巨大图谱。

结果令人大开眼界。

img{512x368}

从“愚公移山”到“巧用代理”

Thibaut 最初的想法很直接:从一个种子项目列表开始,递归地克隆仓库、解析 go.mod。但他很快发现这条路行不通——克隆速度太慢,且严重依赖 GitHub。

于是,他将目光转向了 Go Modules 生态系统的核心枢纽 —— Go Proxy

  • index.golang.org:提供了自 2019 年以来所有发布模块的时间流。
  • proxy.golang.org:提供了每个模块版本的 go.mod 文件。

通过这两个公开 API,他成功地将整个 Go 生态的元数据“搬”到了本地,构建了一个全量的、不可变的本地缓存。

Neo4j:点亮数据之网

面对海量的依赖关系,传统的关系型数据库显得力不从心。Thibaut 选择了图数据库 Neo4j

  • 节点 (Node):代表一个具体的 Go 模块版本(例如 github.com/gin-gonic/gin@v1.9.0)。
  • 关系 (Relationship):代表 DEPENDS_ON(依赖于)。

通过简单的 Cypher 查询语句,复杂的依赖链变得清晰可见。例如,查询一个模块的所有传递性依赖(Transitive Dependencies),在 SQL 中可能需要复杂的递归 CTE,而在 Neo4j 中只需一个简单的 *1.. 语法即可搞定。

数据揭秘:Go 生态的真实面貌

经过数天的处理和导入,这个庞大的图谱终于呈现在眼前。让我们看看数据告诉了我们什么:

1. 绝对的王者:testify

在“被直接依赖次数”的榜单上,github.com/stretchr/testify 以 259,237 次的惊人数量遥遥领先,是第二名的两倍还多。这再次印证了测试在 Go 社区中的核心地位。

紧随其后的是:

  1. github.com/google/uuid (10w+)
  2. golang.org/x/crypto (10w+)
  3. google.golang.org/grpc (9.7w+)
  4. github.com/spf13/cobra (9.3w+)
  5. … …

2. “已归档”库的生命力:pkg/errors

最令人玩味的数据来自 github.com/pkg/errors。尽管这个库多年前就已宣布“归档”(Archived)并停止维护,且 Go 1.13 已内置了类似的错误包装功能,但数据却显示了截然相反的趋势:

  • 它的使用量不降反升!
  • 2019 年仅有 3 个依赖它的模块,而到了 2025 年,这个数字飙升到了 16,001

这揭示了软件生态中一个残酷的现实:旧习惯难改,且“足够好”的库拥有极其顽强的生命力。 哪怕官方已经提供了替代方案,开发者们依然倾向于使用他们熟悉的工具。

小结

Thibaut 的这个项目不仅仅是一次有趣的数据分析,它为我们观察 Go 生态提供了一个全新的上帝视角。

  • 平均依赖数:Go 模块平均拥有 10 个直接依赖。
  • 数据开源:作者不仅开源了爬虫代码 github.com/Thiht/go-stats,还大方地通过 BitTorrent 分享了 11GB 的 Neo4j 数据库转储文件。

你可以下载这份数据,自己在本地运行 Neo4j,去挖掘更多有趣的洞见。比如,看看你最喜欢的某个小众库,究竟被谁在使用?或者,去探索一下 Go 生态中那些隐秘的“单点故障”?

在这个由 4000 万个节点构成的宇宙中,还有无数的秘密等待被发现。

资料链接:https://blog.thibaut-rousseau.com/blog/the-most-popular-go-dependency-is/


你的依赖清单

testify 的霸榜并不意外,但 pkg/errors 的顽强生命力确实让人深思。在你的 go.mod 中,是否也有那些“虽然已归档,但真的很好用”的库?或者,你有什么私藏的冷门好库推荐?

欢迎在评论区晒出你的“宝藏依赖”! 让我们一起发现更多 Go 生态的秘密。

如果这篇文章让你对 Go 生态有了全新的认识,别忘了点个【赞】和【在看】,并转发给你的 Gopher 朋友!


还在为“复制粘贴喂AI”而烦恼?我的新专栏 AI原生开发工作流实战 将带你:

  • 告别低效,重塑开发范式
  • 驾驭AI Agent(Claude Code),实现工作流自动化
  • 从“AI使用者”进化为规范驱动开发的“工作流指挥家”

扫描下方二维码,开启你的AI原生开发之旅。


你的Go技能,是否也卡在了“熟练”到“精通”的瓶颈期?

  • 想写出更地道、更健壮的Go代码,却总在细节上踩坑?
  • 渴望提升软件设计能力,驾驭复杂Go项目却缺乏章法?
  • 想打造生产级的Go服务,却在工程化实践中屡屡受挫?

继《Go语言第一课》后,我的《Go语言进阶课》终于在极客时间与大家见面了!

我的全新极客时间专栏 《Tony Bai·Go语言进阶课》就是为这样的你量身打造!30+讲硬核内容,带你夯实语法认知,提升设计思维,锻造工程实践能力,更有实战项目串讲。

目标只有一个:助你完成从“Go熟练工”到“Go专家”的蜕变! 现在就加入,让你的Go技能再上一个新台阶!


商务合作方式:撰稿、出书、培训、在线课程、合伙创业、咨询、广告合作。如有需求,请扫描下方公众号二维码,与我私信联系。

© 2026, bigwhite. 版权所有.

🔲 ☆

Dubbo 中的集群容错

apache_dubbo.jpg

前言

在微服务架构中,服务间的依赖关系复杂且动态,任何一个服务的故障都可能引发连锁反应,导致系统雪崩。一个好的容错设计可以避免这些问题发生:

  • 服务雪崩效应:单个服务崩溃或响应延迟可能导致调用链上的所有服务被阻塞,最终拖垮整个系统。例如,若服务 A 依赖服务 B,而服务 B 因高负载无法响应,A 的线程池可能被占满,进而影响其他依赖A的服务;

  • 分布式系统的脆弱性:网络抖动、节点宕机、资源耗尽等问题在分布式环境中不可避免。容错机制通过冗余和快速失败策略,确保部分故障不会扩散到整个系统;

  • 服务的可用性低:微服务的目标是提升系统可用性,而容错设计(如故障转移、熔断)是保障服务持续可用的核心手段。例如,通过自动切换健康节点,避免单点故障。

Dubbo 的集群容错机制

在 Dubbo 中,多个 Provider 实例构成一个「集群」。消费者调用时,Dubbo 通过 Cluster 模块实现容错策略的封装和路由,Cluster 模块会根据配置(如 cluster=failover)装配不同的容错策略实现类,对 Directory 中的多个 Invoker 进行处理,返回一个可执行的 Invoker。Dubbo 当前已支持以下 6 种容错策略(在 org.apache.dubbo.rpc.cluster.support 包下):

策略简称实现类名特性使用场景
FailoverFailoverClusterInvoker失败自动重试,默认实现网络不稳定,民登操作
FailfastFailfastClusterInvoker快速失败,不重试响应时间敏感,非幂等
FailsafeFailsafeClusterInvoker失败忽略异常日志记录、监控等非主要场景
FailbackFailbackClusterInvoker失败后后台重试可容忍失败,后续补偿重试
ForkingForkingClusterInvoker并行调用多个节点,最快成功返回实时性要求高,资源充足
BroadcastBroadcastClusterInvoker广播方式调用所有服务提供着配置更新、通知类等操作

Failover Cluster(失败自动切换,默认策略)

实现原理:通过循环重试实现容错。
实现源码关键点:

  1. FailoverClusterInvoker 的 doInvoke 方法中,通过 for 循环控制重试次数(默认重试 2 次,共调用 3 次);
  2. 每次重试前调用 list(invocation) 重新获取最新的 Invoker 列表,确保动态感知节点变化。
1
2
3
4
5
6
7
8
// 代码片段:org.apache.dubbo.rpc.cluster.support.FailoverClusterInvoker#doInvoke
for (int i = 0; i < len; i++) {
if (i > 0) {
copyInvokers = list(invocation); // 动态刷新 Invoker 列表
}
Invoker<T> invoker = select(loadbalance, invocation, copyInvokers, invoked);
// 调用并处理异常...
}

Failfast Cluster(快速失败)

实现原理:仅发起一次调用,异常直接抛出。
实现源码关键点:

  1. FailfastClusterInvoker 直接调用目标 Invoker,不进行重试。
1
2
3
4
5
6
// 代码片段:org.apache.dubbo.rpc.cluster.support.FailfastClusterInvoker#doInvoke
fpublic Result doInvoke(...) throws RpcException {
checkInvokers(invokers, invocation);
Invoker<T> invoker = select(loadbalance, invocation, invokers, null);
return invoker.invoke(invocation); // 仅一次调用
}

Failsafe Cluster(失败安全)

实现原理:异常被捕获后返回空结果,不中断流程。
实现源码关键点:

  1. ailsafeClusterInvoker通过try-catch捕获异常并记录日志。
1
2
3
4
5
6
7
// 代码片段:org.apache.dubbo.rpc.cluster.support.FailsafeClusterInvoker
try {
// 调用逻辑...
} catch (Throwable e) {
logger.error("Failsafe ignore exception", e);
return new RpcResult(); // 返回空结果
}

Failback Cluster(失败自动恢复)

实现原理:失败请求存入队列,定时重试。
实现源码关键点:

  1. 捕获失败异常,使用 RetryTimerTask 存储失败请求,定时触发重试。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
// 代码片段:org.apache.dubbo.rpc.cluster.support.FailbackClusterInvoker#doInvoke
private void addFailed(
LoadBalance loadbalance,
Invocation invocation,
List<Invoker<T>> invokers,
Invoker<T> lastInvoker,
URL consumerUrl) {
if (failTimer == null) {
synchronized (this) {
if (failTimer == null) {
failTimer = new HashedWheelTimer(
new NamedThreadFactory("failback-cluster-timer", true),
1,
TimeUnit.SECONDS,
32,
failbackTasks);
}
}
}
RetryTimerTask retryTimerTask = new RetryTimerTask(
loadbalance, invocation, invokers, lastInvoker, retries, RETRY_FAILED_PERIOD, consumerUrl);
try {
failTimer.newTimeout(retryTimerTask, RETRY_FAILED_PERIOD, TimeUnit.SECONDS);
} catch (Throwable e) {
logger.error(
CLUSTER_TIMER_RETRY_FAILED,
"add newTimeout exception",
"",
"Failback background works error, invocation->" + invocation + ", exception: " + e.getMessage(),
e);
}
}

Forking Cluster(并行调用)

实现原理:并发调用多个节点,首个成功结果即返回。
实现源码关键点:

  1. 使用线程池并发调用,结果通过 BlockingQueue 异步接收。
1
2
3
4
5
6
7
// 代码片段:org.apache.dubbo.rpc.cluster.support.ForkingClusterInvoker#doInvoke
for (Invoker<T> invoker : selected) {
executor.execute(() -> {
Result result = invoker.invoke(invocation);
ref.offer(result); // 结果存入队列
});
}

Broadcast Cluster(广播调用)

实现原理:逐个调用所有节点,任一失败则整体失败。
实现源码关键点:

  1. 遍历所有 Invoker 调用,异常累积后抛出。
1
2
3
4
5
6
7
8
9
// 代码片段:org.apache.dubbo.rpc.cluster.support.BroadcastClusterInvoker#doInvoke
for (Invoker<T> invoker : invokers) {
try {
invoker.invoke(invocation);
} catch (RpcException e) {
exception = e;
}
}
if (exception != null) throw exception;

如何自定义集群容错策略

如果以上提供的容错策略不满足需求,Dubbo 支持通过 SPI 自定义 Cluster 实现,步骤如下:

第一步:实现 Cluster 和 AbstractClusterInvoker
1
2
3
4
5
6
7
8
9
@SPI("custom")
public class MyCluster implements Cluster {

@Override
public <T> Invoker<T> join(Directory<T> directory) {
return new MyClusterInvoker<>(directory);
}

}
1
2
3
4
5
6
7
8
public class MyClusterInvoker<T> extends AbstractClusterInvoker<T> {

@Override
protected Result doInvoke(Invocation invocation, List<Invoker<T>> invokers, LoadBalance loadbalance) {
// 自定义逻辑,例如条件重试、动态路由等
}

}
第二步:添加 SPI 配置

META-INF/dubbo/org.apache.dubbo.rpc.cluster.Cluster 中添加配置:

1
mycluster=com.example.MyCluster
第三步:配置使用自定义容错策略
1
<dubbo:reference cluster="mycluster" />

总结

建议核心服务优先使用 Failover(失败自动切换) 策略保障可用性,非核心服务可降级为 Failsafe(失败安全)。同时结合 Hystrix(已停止更新)Sentinel 实现熔断与限流,增强容错能力。

通过灵活组合 Dubbo 的容错策略,可显著提升分布式系统的鲁棒性。实际应用配置时需要根据业务特性权衡延迟、资源开销与一致性要求,一切皆是 trade off ~

P.S. 不妨再深入思考一下:Dubbo 的集群容错实现中有哪些优秀设计值得我们学习?

🔲 ⭐

动手写RPC框架 - GeeRPC第五天 支持HTTP协议

7天用 Go语言/golang 从零实现 RPC 框架 GeeRPC 教程(7 days implement golang remote procedure call framework from scratch tutorial),动手写 RPC 框架,参照 golang 标准库 net/rpc 的实现,实现了服务端(server)、支持异步和并发的客户端(client)、消息编码与解码(message encoding and decoding)、服务注册(service register)、支持 TCP/Unix/HTTP 等多种传输协议。第五天支持了 HTTP 协议,并且提供了一个简单的 DEBUG 页面。

🔲 ☆

动手写RPC框架 - GeeRPC第四天 超时处理(timeout)

7天用 Go语言/golang 从零实现 RPC 框架 GeeRPC 教程(7 days implement golang remote procedure call framework from scratch tutorial),动手写 RPC 框架,参照 golang 标准库 net/rpc 的实现,实现了服务端(server)、支持异步和并发的客户端(client)、消息编码与解码(message encoding and decoding)、服务注册(service register)、支持 TCP/Unix/HTTP 等多种传输协议。第四天为RPC框架提供了处理超时的能力(timeout processing)。

🔲 ☆

动手写RPC框架 - GeeRPC第三天 服务注册(service register)

7天用 Go语言/golang 从零实现 RPC 框架 GeeRPC 教程(7 days implement golang remote procedure call framework from scratch tutorial),动手写 RPC 框架,参照 golang 标准库 net/rpc 的实现,实现了服务端(server)、支持异步和并发的客户端(client)、消息编码与解码(message encoding and decoding)、服务注册(service register)、支持 TCP/Unix/HTTP 等多种传输协议。第三天实现了服务注册,即将 Go 语言结构体通过反射映射为服务。

🔲 ☆

GRPC文档阅读心得

主要是两个文档, grpc repo的文档 https://github.com/grpc/grpc/tree/master/doc , grpc-go repo的文档 https://github.com/grpc/grpc-go/tree/master/Documentation.

grpc-go 文档


gRPC Server Reflection Tutorial

在代码中import "google.golang.org/grpc/reflection"包, 然后加一行代码reflection.Register(s), 就可以启用 server reflection. 就可以用grpc_cli去进行获得服务列表, 方法列表, message结构体定义了. reflection.Register(s)实际上是注册了一个特殊的service, 它能列出server中已注册的服务和方法等信息.

Compression

encoding.RegisterCompressor方法取注册一个压缩器, 启用了压缩的话, 服务端和客户端双方都要进行同样的处理, 服务端在newServer时要带上compressor的serverOption, 客户端在dail的时候要带上WithDefaultCallOptions的DialOption, DialOption加上压缩解压的处理, 不然会得到一个 Internal error, 和HTTP方式一样, 压缩类型体现在content-type的header上.

Concurrency

Dial得到的ClientConn是并发安全.
stream的读写不是并发安全的, sendMsg或RecvMsg不能在多个goroutine中并发地调用,但可以分别在两个goroutine中处理send和Recv.

Encoding

序列化反序列化

自定义消息编码解码, 注册一个实现 Codec 接口的对象即可, 然后在Dial或Call时带上grpc.CallContentSubtype这个CallOption, 这样就可以自动处理这个带这个content-type的请求. 默认为 proto

压缩解压缩

自定义压缩解压缩, 注册一个实现 Compressor接口的对象即可, 然后在Dial或Call时带上grpc.UseCompressor这个CallOption.

[Mocking Service for gRPC

](https://github.com/grpc/grpc-go/blob/master/Documentation/gomock-example.md)

主要讲如何在单元测试中mock, 用gomock命令行生成实现xx接口的代码, 没什么特别的

[Authentication

](https://github.com/grpc/grpc-go/blob/master/Documentation/grpc-auth-support.md)

主要讲如何进行身份验证, 没什么特别的

Metadata

metadata类似HTTP1中的header, 数据结构都是一样的type MD map[string][]string,
key都是大小写不敏感的, 但实现规范和HTTP1不一样, HTTP1是按单词之间用连字符”-“分隔, 每个单词第一个字母大写这样的规范来的, 处理起来消耗更大, 而metadata是全转为小写, 实际使用过程中, 提前规范化key能提高不必要的strings.ToLower调用.
用-bin结尾的来传递二进制数据.

服务端handler用metadata.FromIncomingContext(ctx)拿到metadata, 客户端用metadata.AppendToOutgoingContext来附加kv到ctx中.

如果服务端handler又想附加一些信息返回client, 那么就要通过header和trailer传递, 类似responseHeader.

func (s *server) SomeRPC(ctx context.Context, in *pb.someRequest) (*pb.someResponse, error) {
// create and send header
header := metadata.Pairs("header-key", "val")
grpc.SendHeader(ctx, header)
// create and set trailer
trailer := metadata.Pairs("trailer-key", "val")
grpc.SetTrailer(ctx, trailer)
}

然后客户端在调用的时候传要保存的header和trailler的指针到CallOption中, 调用完后指针指向的metadata map就有数据了, 坦率地讲, 我觉得这样处理很麻烦.

var header, trailer metadata.MD // variable to store header and trailer
r, err := client.SomeRPC(
ctx,
someRequest,
grpc.Header(&header), // will retrieve header
grpc.Trailer(&trailer), // will retrieve trailer
)

// do something with header and trailer

Keepalive

gRPC会定时发http2 ping帧来判断连接是否挂掉, 如果ping没有在一定时期内ack, 那么连接会被close.

Log Levels

grpc-go包默认用gpclog包打日志, grpclog包默认是用多个*log.Logger来实现日志级别, 默认输出到stderr, 对于生产环境, 肯定要集成到自己的日志流里去, 接口是个好东西, grpclog包允许setLog, 实现grpclog.LoggerV2接口即可.

info日志包括:

grpclog里的info是为了debug

  • DNS 收到了更新
  • 负载均衡器 更新了选择的目标
  • 重要的grpc 状态变更

    warn日志包括:

    warning日志是出现了一些错误, 但还不至于panic.
  • DNS无法解析给定的target
  • 连接server时出错
  • 连接丢失或中断

    error日志包括:

    grpc内部有些error不是用户发起的函数调用, 所以无法返回error给调用者, 只能内部自己打error日志
  • 函数签名没有error, 但调用方传了个错误的参数过来.
  • 内部错误.

    Fatal日志:

    fatal日志是出现了不可恢复的内部错误, 要panic.

grpc 文档


之前一直有个误区, 多个连接比单个连接要快, 看了 grpc-go issues1grpc-go issues2 以及 HTTP2文档 才发现, 由于HTTP2有多路复用的特性, 对于同一个sever, 只需要维护一个连接就好了, 没有必要用多个连接去并行复用数据流. 连接数量减少对提升 HTTPS 部署的性能来说是一项特别重要的功能:可以减少开销较大的 TLS 连接数、提升会话重用率,以及从整体上减少所需的客户端和服务器资源。

❌