主题
28 · gRPC + ProtoBuf
目标:写
.proto、生成 Go 代码,跑通一元 RPC,并理解与 HTTP/JSON 的边界。
前置:07 · HTTP/JSON · 12 · Gin · 16 · JWT
官方:gRPC · Protocol Buffers · Go:google.golang.org/grpc
1. 为何还要 gRPC
| HTTP + JSON(Gin) | gRPC + Protobuf |
|---|---|
| 浏览器 / 小程序友好 | 服务间高效、强类型契约 |
| 人类可读 | 二进制;IDL 生成多语言 stub |
| 一元请求为主 | 流式(server/client/bidi)原生 |
对外仍可 Gin;对内用户服务 ↔ 手帐服务用 gRPC。Node 对照:@grpc/grpc-js。
2. 工具链
bash
# protoc 本机安装(或用 buf 工具链,暑假可先 protoc)
go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest1
2
3
2
3
api/note/v1/note.proto:
protobuf
syntax = "proto3";
package note.v1;
option go_package = "notesapi/api/note/v1;notev1";
service NoteService {
rpc GetNote (GetNoteRequest) returns (GetNoteReply);
}
message GetNoteRequest { string id = 1; }
message GetNoteReply {
string id = 1;
string title = 2;
string content = 3;
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
2
3
4
5
6
7
8
9
10
11
12
13
14
bash
protoc -I . --go_out=. --go_opt=paths=source_relative \
--go-grpc_out=. --go-grpc_opt=paths=source_relative \
api/note/v1/note.proto1
2
3
2
3
3. 最小可跑 demo
Server:
go
type server struct{ notev1.UnimplementedNoteServiceServer }
func (s *server) GetNote(ctx context.Context, req *notev1.GetNoteRequest) (*notev1.GetNoteReply, error) {
if req.Id == "" {
return nil, status.Error(codes.InvalidArgument, "id required")
}
return ¬ev1.GetNoteReply{Id: req.Id, Title: "demo", Content: "hello"}, nil
}
func main() {
lis, err := net.Listen("tcp", ":50051")
fail(err)
s := grpc.NewServer()
notev1.RegisterNoteServiceServer(s, &server{})
fail(s.Serve(lis))
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Client:
go
conn, err := grpc.Dial("127.0.0.1:50051", grpc.WithTransportCredentials(insecure.NewCredentials()))
fail(err)
defer conn.Close()
cli := notev1.NewNoteServiceClient(conn)
resp, err := cli.GetNote(ctx, ¬ev1.GetNoteRequest{Id: "n1"})
fail(err)
log.Println(resp.Title)1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
4. 拦截器 ≈ Gin 中间件
go
func authUnary(ctx context.Context, req any, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (any, error) {
md, _ := metadata.FromIncomingContext(ctx)
// 校验 authorization;失败 return status.Error(codes.Unauthenticated, "...")
return handler(ctx, req)
}
s := grpc.NewServer(grpc.UnaryInterceptor(authUnary))1
2
3
4
5
6
7
2
3
4
5
6
7
错误用 google.golang.org/grpc/status + codes,不要只 return err 丢语义。
5. 动手清单
- [ ]
.proto生成无报错 - [ ] Server/Client 一元调用成功
- [ ] 空 id 返回
InvalidArgument - [ ] 加一个 unary interceptor 打日志
- [ ] 能讲:浏览器为何通常不直连 gRPC(可用 grpc-gateway 进阶)
6. 项目驱动
| 场景 | 做法 |
|---|---|
| 用户服务 GetUser | 手帐服务 gRPC 调用户校验归属 |
| AI 摘要服务 | 长超时 + 可选 stream |
| 与 Gin 共存 | 同仓 cmd/http + cmd/grpc 或 sidecar |
7. 常见坑 + AI 审查
| 坑 | 说明 |
|---|---|
go_package 乱写 | 生成路径对不上 module |
生产仍 insecure | 本机可;生产 TLS |
把业务 error 当 codes.Unknown 一锅端 | 映射好 InvalidArgument / NotFound |
| AI 手写编解码 | 必须走生成代码 |
| 字段号乱改 | protobuf 兼容性靠 field number |
8. 下一篇
日志型高吞吐消息:
→ 29 · Kafka 概览
