Protobuf消息定义与服务接口规范
gRPC基于Protocol Buffers(Protobuf)定义服务接口和消息格式。Protobuf使用.proto文件描述数据结构,通过编译器生成多语言代码。相比REST API的JSON文本序列化,Protobuf采用二进制编码,数据体积小3-10倍,序列化速度快10-100倍。
定义用户服务的.proto文件:
syntax = "proto3";
package user.v1;
option go_package = "github.com/example/proto/user/v1";
// 用户服务定义
service UserService {
rpc GetUser(GetUserRequest) returns (GetUserResponse);
rpc ListUsers(ListUsersRequest) returns (ListUsersResponse);
rpc CreateUser(CreateUserRequest) returns (CreateUserResponse);
rpc StreamUsers(StreamUsersRequest) returns (stream User);
}
message User {
int64 id = 1;
string name = 2;
string email = 3;
int32 age = 4;
repeated string roles = 5;
google.protobuf.Timestamp created_at = 6;
}
message GetUserRequest {
int64 id = 1;
}
message GetUserResponse {
User user = 1;
}
message ListUsersRequest {
int32 page = 1;
int32 page_size = 2;
string sort = 3;
}
message ListUsersResponse {
repeated User users = 1;
int32 total = 2;
}
message CreateUserRequest {
string name = 1;
string email = 2;
int32 age = 3;
}
message CreateUserResponse {
User user = 1;
}
message StreamUsersRequest {
string role = 1;
}
proto3语法相比proto2简化了必选字段和默认值处理。所有字段默认可选,标量类型有零值默认。repeated字段对应数组,stream关键字定义服务端流式响应。使用protoc编译生成Go代码:
protoc --go_out=. --go_opt=paths=source_relative \
--go-grpc_out=. --go-grpc_opt=paths=source_relative \
user.proto
Go语言gRPC服务端实现
基于生成的代码实现UserService服务端逻辑:
package main
import (
"context"
"log"
"net"
"google.golang.org/grpc"
"google.golang.org/grpc/reflection"
pb "github.com/example/proto/user/v1"
)
type userServer struct {
pb.UnimplementedUserServiceServer
users map[int64]*pb.User
}
func (s *userServer) GetUser(ctx context.Context, req *pb.GetUserRequest) (*pb.GetUserResponse, error) {
user, ok := s.users[req.Id]
if !ok {
return nil, status.Errorf(codes.NotFound, "user %d not found", req.Id)
}
return &pb.GetUserResponse{User: user}, nil
}
func (s *userServer) ListUsers(ctx context.Context, req *pb.ListUsersRequest) (*pb.ListUsersResponse, error) {
var users []*pb.User
for _, u := range s.users {
users = append(users, u)
}
return &pb.ListUsersResponse{
Users: users,
Total: int32(len(users)),
}, nil
}
func (s *userServer) StreamUsers(req *pb.StreamUsersRequest, stream pb.UserService_StreamUsersServer) error {
for _, u := range s.users {
for _, role := range u.Roles {
if role == req.Role {
if err := stream.Send(u); err != nil {
return err
}
}
}
}
return nil
}
func main() {
lis, err := net.Listen("tcp", ":50051")
if err != nil {
log.Fatalf("failed to listen: %v", err)
}
s := grpc.NewServer(
grpc.UnaryInterceptor(loggingInterceptor),
grpc.StreamInterceptor(streamLoggingInterceptor),
)
pb.RegisterUserServiceServer(s, &userServer{
users: makeUserStore(),
})
reflection.Register(s)
log.Println("gRPC server listening on :50051")
if err := s.Serve(lis); err != nil {
log.Fatalf("failed to serve: %v", err)
}
}
gRPC客户端调用与拦截器配置
服务端和客户端均可配置拦截器(Interceptor),实现日志记录、认证鉴权、链路追踪等横切关注点。以下实现一元拦截器:
// 服务端日志拦截器
func loggingInterceptor(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
start := time.Now()
resp, err := handler(ctx, req)
log.Printf("method=%s duration=%s err=%v", info.FullMethod, time.Since(start), err)
return resp, err
}
// 客户端认证拦截器
func authInterceptor(ctx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error {
token := getTokenFromContext(ctx)
ctx = metadata.AppendToOutgoingContext(ctx, "authorization", "Bearer "+token)
return invoker(ctx, method, req, reply, cc, opts...)
}
// 客户端调用
func callUserService() {
conn, err := grpc.Dial("localhost:50051",
grpc.WithTransportCredentials(insecure.NewCredentials()),
grpc.WithUnaryInterceptor(authInterceptor),
)
if err != nil {
log.Fatalf("did not connect: %v", err)
}
defer conn.Close()
client := pb.NewUserServiceClient(conn)
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
resp, err := client.GetUser(ctx, &pb.GetUserRequest{Id: 1})
if err != nil {
log.Fatalf("could not get user: %v", err)
}
log.Printf("User: %s, Email: %s", resp.User.Name, resp.User.Email)
}
拦截器按注册顺序链式执行,每个拦截器可决定是否调用下一个handler。生产环境中通常配置多个拦截器:recovery拦截器捕获panic、logging拦截器记录调用日志、auth拦截器验证JWT令牌、metrics拦截器上报Prometheus指标。
gRPC与REST API对比与选型
gRPC和REST是微服务间通信的两种主流方案。gRPC基于HTTP/2和Protobuf,支持双向流式传输,强类型接口约束,适合内部服务间高性能通信。REST基于HTTP/1.1和JSON,浏览器友好,生态成熟,适合对外API和前后端通信。
gRPC的优势:二进制编码效率高,HTTP/2多路复用减少连接开销,流式通信适合实时数据推送,代码生成保证接口类型安全。劣势:浏览器需gRPC-Web代理层,调试不如REST直观,Protobuf对前端不够友好。
REST的优势:人类可读的JSON格式,浏览器原生支持,工具链丰富(Swagger/Postman),学习成本低。劣势:JSON序列化慢,HTTP/1.1队头阻塞,无强类型约束,接口文档易过期。
选型建议:内部微服务通信优先gRPC,性能和类型安全优势显著。对外API和前后端通信使用REST。混合架构中可通过gRPC-Gateway自动生成REST代理,一套Protobuf定义同时服务gRPC和REST客户端。
原创文章,作者:小编,如若转载,请注明出处:https://www.yunthe.com/grpc-fu-wu-ding-yi-yu-protobuf-tong-xin-shi-zhan-go-yu-yan/