gRPC
问题
什么是 gRPC?Go 中如何使用 gRPC?四种通信模式分别是什么?
答案
gRPC 简介
gRPC 是 Google 开源的高性能 RPC 框架,基于 HTTP/2 和 Protocol Buffers:
| 特性 | gRPC | REST |
|---|---|---|
| 协议 | HTTP/2 | HTTP/1.1 或 HTTP/2 |
| 序列化 | Protobuf(二进制) | JSON(文本) |
| 性能 | 高(二进制 + 多路复用) | 较低 |
| 类型安全 | 强(代码生成) | 弱 |
| 流式支持 | 双向流 | 有限(SSE) |
Protocol Buffers
// user.proto
syntax = "proto3";
package user;
option go_package = "pb/user";
service UserService {
rpc GetUser(GetUserRequest) returns (User); // 一元 RPC
rpc ListUsers(ListUsersRequest) returns (stream User); // 服务端流
rpc CreateUsers(stream CreateUserRequest) returns (Summary); // 客户端流
rpc Chat(stream Message) returns (stream Message); // 双向流
}
message User {
int64 id = 1;
string name = 2;
string email = 3;
}
四种通信模式
Server 实现
type userServer struct {
pb.UnimplementedUserServiceServer
}
// 一元 RPC
func (s *userServer) GetUser(ctx context.Context, req *pb.GetUserRequest) (*pb.User, error) {
user := findUser(req.Id)
if user == nil {
return nil, status.Errorf(codes.NotFound, "user %d not found", req.Id)
}
return user, nil
}
// 服务端流
func (s *userServer) ListUsers(req *pb.ListUsersRequest, stream pb.UserService_ListUsersServer) error {
users := getAllUsers()
for _, user := range users {
if err := stream.Send(user); err != nil {
return err
}
}
return nil
}
func main() {
lis, _ := net.Listen("tcp", ":50051")
grpcServer := grpc.NewServer()
pb.RegisterUserServiceServer(grpcServer, &userServer{})
grpcServer.Serve(lis)
}
拦截器(中间件)
// 一元拦截器
func loggingInterceptor(
ctx context.Context,
req any,
info *grpc.UnaryServerInfo,
handler grpc.UnaryHandler,
) (any, error) {
start := time.Now()
resp, err := handler(ctx, req)
log.Printf("method=%s duration=%v err=%v", info.FullMethod, time.Since(start), err)
return resp, err
}
server := grpc.NewServer(
grpc.UnaryInterceptor(loggingInterceptor),
)
常见面试问题
Q1: gRPC 和 REST 怎么选?
答案:
- 内部微服务通信:gRPC(高性能、类型安全、流式支持)
- 公开 API / 浏览器直连:REST(兼容性好、调试方便)
- 实时双向通信:gRPC 双向流
Q2: gRPC 的错误处理怎么做?
答案:使用 google.golang.org/grpc/status 和 codes:
return nil, status.Errorf(codes.NotFound, "user not found")
return nil, status.Errorf(codes.InvalidArgument, "name is required")