gRPC基于HTTP/2和Protocol Buffers实现高性能RPC通信,在微服务架构中广泛用于内部服务间调用。相比RESTful JSON,gRPC的二进制编码效率更高、连接复用更好、接口契约更严格。本文覆盖从Protobuf定义到Go语言服务端实现、Java客户端调用、拦截器链配置的完整链路。
Protobuf接口定义与代码生成
Protobuf(Protocol Buffers)是gRPC的接口描述语言(IDL),通过.proto文件定义服务和消息结构。定义清晰的服务契约是微服务架构中服务治理的基础。
// proto/user_service.proto
syntax = "proto3";
package user.v1;
option go_package = "github.com/example/proto/user/v1;userv1";
option java_package = "com.example.proto.user.v1";
option java_multiple_files = true;
message User {
int64 id = 1;
string username = 2;
string email = 3;
int32 status = 4;
int64 created_at = 5;
}
message GetUserRequest {
int64 id = 1;
}
message ListUsersRequest {
int32 page_size = 1;
string page_token = 2;
string filter = 3;
}
message ListUsersResponse {
repeated User users = 1;
string next_page_token = 2;
int32 total = 3;
}
message CreateUserRequest {
string username = 1;
string email = 2;
string password = 3;
}
service UserService {
rpc GetUser(GetUserRequest) returns (User);
rpc ListUsers(ListUsersRequest) returns (ListUsersResponse);
rpc CreateUser(CreateUserRequest) returns (User);
}
字段编号从1开始,1-15占用1字节空间,16-2047占用2字节,高频字段建议用小编号。repeated关键字表示数组。option go_package和java_package分别控制Go和Java的生成代码包路径。运行protoc生成各语言代码:
# 生成Go代码
protoc --go_out=. --go_opt=paths=source_relative \
--go-grpc_out=. --go-grpc_opt=paths=source_relative \
proto/user_service.proto
# 生成Java代码
protoc --java_out=./src/main/java \
--grpc-java_out=./src/main/java \
proto/user_service.proto
Go语言gRPC服务端实现
生成代码后实现服务端接口,注册到gRPC Server:
package main
import (
"context"
"log"
"net"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
pb "github.com/example/proto/user/v1"
)
type userServer struct {
pb.UnimplementedUserServiceServer
db map[int64]*pb.User
}
func (s *userServer) GetUser(ctx context.Context, req *pb.GetUserRequest) (*pb.User, error) {
user, ok := s.db[req.Id]
if !ok {
return nil, status.Errorf(codes.NotFound, "user %d not found", req.Id)
}
return user, nil
}
func (s *userServer) CreateUser(ctx context.Context, req *pb.CreateUserRequest) (*pb.User, error) {
if req.Username == "" {
return nil, status.Error(codes.InvalidArgument, "username is required")
}
user := &pb.User{
Id: int64(len(s.db) + 1),
Username: req.Username,
Email: req.Email,
Status: 1,
}
s.db[user.Id] = user
return user, 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),
)
pb.RegisterUserServiceServer(s, &userServer{
db: make(map[int64]*pb.User),
})
log.Println("gRPC server listening on :50051")
s.Serve(lis)
}
UnimplementedUserServiceServer嵌入结构体确保向前兼容——新增RPC方法时旧服务端不会编译报错。status.Errorf返回标准gRPC错误码,客户端可按codes.NotFound等枚举值处理异常。grpc.NewServer通过UnaryInterceptor选项注册一元拦截器,用于日志、认证、链路追踪等横切逻辑。
拦截器链配置:日志、认证与链路追踪
gRPC拦截器分为一元拦截器(UnaryInterceptor)和流拦截器(StreamInterceptor)。生产环境通常需要同时注册多个拦截器,形成拦截器链。Go的gRPC原生不支持多拦截器链式注册,需要手动组合:
package main
import (
"context"
"log"
"time"
"google.golang.org/grpc"
"google.golang.org/grpc/metadata"
)
// 日志拦截器
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("%s | req=%v | dur=%v | err=%v",
info.FullMethod, req, time.Since(start), err)
return resp, err
}
// 认证拦截器
func authInterceptor(
ctx context.Context, req interface{},
info *grpc.UnaryServerInfo, handler grpc.UnaryHandler,
) (interface{}, error) {
md, ok := metadata.FromIncomingContext(ctx)
if !ok {
return nil, status.Error(codes.Unauthenticated, "no metadata")
}
tokens := md.Get("authorization")
if len(tokens) == 0 || tokens[0] != "Bearer valid-token" {
return nil, status.Error(codes.Unauthenticated, "invalid token")
}
return handler(ctx, req)
}
// 链式组合拦截器
func chainInterceptors(interceptors ...grpc.UnaryServerInterceptor) grpc.UnaryServerInterceptor {
return func(ctx context.Context, req interface{},
info *grpc.UnaryServerInfo, handler grpc.UnaryHandler,
) (interface{}, error) {
chain := handler
for i := len(interceptors) - 1; i >= 0; i-- {
chain = func(ctx context.Context, req interface{}) (interface{}, error) {
return interceptors[i](ctx, req, info,
func(ctx context.Context, req interface{}) (interface{}, error) {
return chain(ctx, req)
})
}
}
return chain(ctx, req)
}
}
// 使用
s := grpc.NewServer(
grpc.UnaryInterceptor(chainInterceptors(
loggingInterceptor,
authInterceptor,
)),
)
拦截器的执行顺序为注册顺序:先日志后认证。认证拦截器从metadata中提取authorization字段进行Token校验,失败时返回Unauthenticated错误码。grpc-go-flags或go-grpc-middleware库提供了grpc_middleware.ChainUnaryServer简化链式注册,底层原理与上述手动组合一致。拦截器中可通过metadata.AppendToOutgoingContext注入traceId,实现跨服务的链路追踪。
Java客户端调用与连接池管理
Java侧使用ManagedChannel连接gRPC服务端,需配置连接池和重试策略:
import io.grpc.ManagedChannel;
import io.grpc.ManagedChannelBuilder;
import io.grpc.stub.StreamObserveFallback;
import java.util.concurrent.TimeUnit;
public class UserClient {
private final ManagedChannel channel;
private final UserServiceGrpc.UserServiceBlockingStub stub;
public UserClient(String host, int port) {
this.channel = ManagedChannelBuilder
.forAddress(host, port)
.usePlaintext()
.keepAliveTime(30, TimeUnit.SECONDS)
.keepAliveTimeout(10, TimeUnit.SECONDS)
.keepAliveWithoutCalls(false)
.defaultLoadBalancingPolicy("round_robin")
.maxRetryAttempts(3)
.enableRetry()
.build();
this.stub = UserServiceGrpc.newBlockingStub(channel)
.withDeadlineAfter(5, TimeUnit.SECONDS)
.withWaitForReady();
}
public User getUser(long id) {
GetUserRequest req = GetUserRequest.newBuilder()
.setId(id)
.build();
return stub.getUser(req);
}
public void shutdown() throws InterruptedException {
channel.shutdown().awaitTermination(5, TimeUnit.SECONDS);
}
}
keepAliveTime配置连接保活探测间隔,防止NAT网关超时断连。defaultLoadBalancingPolicy设为round_robin实现客户端负载均衡。enableRetry开启自动重试,maxRetryAttempts控制最大重试次数。withDeadlineAfter设置单次调用超时,withWaitForReady让请求在连接就绪后才发出,避免连接未建立时直接失败。生产环境建议复用ManagedChannel实例,不要每次调用创建新连接。
原创文章,作者:小编,如若转载,请注明出处:https://www.yunthe.com/grpc-fu-wu-tong-xin-shi-zhan-protobuf-ding-yi-yu-lan-jie-qi/