gRPC是Google开源的高性能RPC框架,基于HTTP/2和Protocol Buffers实现,支持双向流式通信。在微服务架构中,gRPC相比REST API具有更低的序列化开销和更强的类型约束。以下是gRPC在Go语言微服务中的实战配置,覆盖Proto定义、服务实现、拦截器开发和负载均衡的完整流程。
Protocol Buffers消息定义
使用proto3语法定义服务接口和消息结构。在proto/目录下创建user.proto:
syntax = "proto3";
package user.v1;
option go_package = "github.com/example/proto/user/v1;userv1";
// 用户服务定义
service UserService {
// 创建用户
rpc CreateUser(CreateUserRequest) returns (User);
// 获取用户
rpc GetUser(GetUserRequest) returns (User);
// 更新用户
rpc UpdateUser(UpdateUserRequest) returns (User);
// 删除用户
rpc DeleteUser(DeleteUserRequest) returns (DeleteUserResponse);
// 批量查询(服务端流式)
rpc ListUsers(ListUsersRequest) returns (stream User);
// 批量导入(客户端流式)
rpc ImportUsers(stream CreateUserRequest) returns (ImportResponse);
// 实时聊天(双向流式)
rpc Chat(stream ChatMessage) returns (stream ChatMessage);
}
message User {
string id = 1;
string name = 2;
string email = 3;
int32 age = 4;
UserRole role = 5;
int64 created_at = 6;
int64 updated_at = 7;
}
enum UserRole {
ROLE_UNSPECIFIED = 0;
ROLE_USER = 1;
ROLE_ADMIN = 2;
}
message CreateUserRequest {
string name = 1;
string email = 2;
int32 age = 3;
UserRole role = 4;
}
message GetUserRequest {
string id = 1;
}
message UpdateUserRequest {
string id = 1;
optional string name = 2;
optional string email = 3;
optional int32 age = 4;
optional UserRole role = 5;
}
message DeleteUserRequest {
string id = 1;
}
message DeleteUserResponse {
bool success = 1;
}
message ListUsersRequest {
int32 page = 1;
int32 page_size = 2;
optional UserRole role_filter = 3;
}
message ImportResponse {
int32 success_count = 1;
int32 fail_count = 2;
repeated string errors = 3;
}
message ChatMessage {
string user_id = 1;
string content = 2;
int64 timestamp = 3;
}
生成Go代码:
protoc --go_out=. --go_opt=paths=source_relative \
--go-grpc_out=. --go-grpc_opt=paths=source_relative \
proto/user.proto
服务端实现
package server
import (
"context"
"crypto/uuid"
"time"
userv1 "github.com/example/proto/user/v1"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)
type UserServer struct {
userv1.UnimplementedUserServiceServer
repo UserRepo
}
func NewUserServer(repo UserRepo) *UserServer {
return &UserServer{repo: repo}
}
func (s *UserServer) CreateUser(ctx context.Context, req *userv1.CreateUserRequest) (*userv1.User, error) {
if req.GetName() == "" || req.GetEmail() == "" {
return nil, status.Error(codes.InvalidArgument, "name and email are required")
}
// 检查邮箱重复
existing, _ := s.repo.FindByEmail(ctx, req.GetEmail())
if existing != nil {
return nil, status.Error(codes.AlreadyExists, "email already exists")
}
user := &userv1.User{
Id: uuid.New().String(),
Name: req.GetName(),
Email: req.GetEmail(),
Age: req.GetAge(),
Role: req.GetRole(),
CreatedAt: time.Now().Unix(),
UpdatedAt: time.Now().Unix(),
}
if err := s.repo.Create(ctx, user); err != nil {
return nil, status.Error(codes.Internal, "failed to create user")
}
return user, nil
}
// 服务端流式:批量查询
func (s *UserServer) ListUsers(req *userv1.ListUsersRequest, stream userv1.UserService_ListUsersServer) error {
offset := int(req.GetPage()-1) * int(req.GetPageSize())
users, err := s.repo.List(stream.Context(), offset, int(req.GetPageSize()), req.GetRoleFilter())
if err != nil {
return status.Error(codes.Internal, "query failed")
}
for _, user := range users {
if err := stream.Send(user); err != nil {
return err
}
}
return nil
}
// 双向流式:实时聊天
func (s *UserServer) Chat(stream userv1.UserService_ChatServer) error {
for {
msg, err := stream.Recv()
if err != nil {
break
}
msg.Timestamp = time.Now().Unix()
// 广播给所有连接的客户端
if err := s.broadcast(msg); err != nil {
return err
}
}
return nil
}
拦截器开发:日志与认证
gRPC拦截器类似Web框架中的中间件,在RPC调用前后注入逻辑。分为一元拦截器(Unary Interceptor)和流拦截器(Stream Interceptor)。
日志拦截器:
package interceptor
import (
"context"
"log/slog"
"time"
"github.com/google/uuid"
"google.golang.org/grpc"
)
type contextKey string
const RequestIDKey contextKey = "request_id"
func UnaryLoggingInterceptor(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
requestID := uuid.New().String()
ctx = context.WithValue(ctx, RequestIDKey, requestID)
start := time.Now()
resp, err := handler(ctx, req)
duration := time.Since(start)
slog.Info("gRPC request",
"method", info.FullMethod,
"request_id", requestID,
"duration", duration.String(),
"error", err,
)
return resp, err
}
func StreamLoggingInterceptor(srv interface{}, ss grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler) error {
requestID := uuid.New().String()
start := time.Now()
err := handler(srv, ss)
slog.Info("gRPC stream",
"method", info.FullMethod,
"request_id", requestID,
"duration", time.Since(start).String(),
"error", err,
)
return err
}
认证拦截器:
func UnaryAuthInterceptor(validTokens map[string]string) grpc.UnaryServerInterceptor {
return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
// 跳过认证的方法
if info.FullMethod == "/user.v1.UserService/CreateUser" {
return handler(ctx, req)
}
md, ok := metadata.FromIncomingContext(ctx)
if !ok {
return nil, status.Error(codes.Unauthenticated, "metadata not found")
}
tokens := md.Get("authorization")
if len(tokens) == 0 {
return nil, status.Error(codes.Unauthenticated, "token not found")
}
token := strings.TrimPrefix(tokens[0], "Bearer ")
userID, ok := validTokens[token]
if !ok {
return nil, status.Error(codes.Unauthenticated, "invalid token")
}
ctx = context.WithValue(ctx, UserIDKey, userID)
return handler(ctx, req)
}
}
链式拦截器组合:
// 使用方式
server := grpc.NewServer(
grpc.ChainUnaryInterceptor(
UnaryLoggingInterceptor,
UnaryAuthInterceptor(authTokens),
UnaryRecoveryInterceptor,
),
grpc.ChainStreamInterceptor(
StreamLoggingInterceptor,
StreamAuthInterceptor(authTokens),
),
)
客户端开发与负载均衡
package client
import (
"context"
"time"
userv1 "github.com/example/proto/user/v1"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
)
func NewUserClient(target string) (userv1.UserServiceClient, *grpc.ClientConn, error) {
resolver.SetDefaultScheme("dns")
conn, err := grpc.Dial(
target,
grpc.WithDefaultServiceConfig(`{"loadBalancingPolicy":"round_robin"}`),
grpc.WithTransportCredentials(insecure.NewCredentials()),
grpc.WithUnaryInterceptor(clientLogInterceptor),
)
if err != nil {
return nil, nil, err
}
return userv1.NewUserServiceClient(conn), conn, nil
}
func clientLogInterceptor(ctx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error {
start := time.Now()
err := invoker(ctx, method, req, reply, cc, opts...)
slog.Info("gRPC client call",
"method", method,
"duration", time.Since(start).String(),
"error", err,
)
return err
}
// 使用示例
func main() {
client, conn, _ := NewUserClient("dns:///user-service:50051")
defer conn.Close()
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
user, err := client.CreateUser(ctx, &userv1.CreateUserRequest{
Name: "张三",
Email: "zhangsan@example.com",
Age: 28,
Role: userv1.UserRole_ROLE_USER,
})
if err != nil {
log.Fatal(err)
}
fmt.Printf("Created user: %s\n", user.Id)
}
gRPC网关与HTTP转码
gRPC服务可通过grpc-gateway同时暴露REST API,方便前端和不支持gRPC的客户端调用:
// 在proto中添加HTTP注解
import "google/api/annotations.proto";
service UserService {
rpc GetUser(GetUserRequest) returns (User) {
option (google.api.http) = {
get: "/api/v1/users/{id}"
};
}
rpc CreateUser(CreateUserRequest) returns (User) {
option (google.api.http) = {
post: "/api/v1/users"
body: "*"
};
}
}
// 网关服务启动
func main() {
ctx := context.Background()
mux := runtime.NewServeMux()
opts := []grpc.DialOption{grpc.WithTransportCredentials(insecure.NewCredentials())}
err := userv1.RegisterUserServiceHandlerFromEndpoint(ctx, mux, "localhost:50051", opts)
if err != nil {
log.Fatal(err)
}
log.Println("Gateway listening on :8080")
log.Fatal(http.ListenAndServe(":8080", mux))
}
健康检查与优雅关闭
import (
healthpb "google.golang.org/grpc/health/grpc_health_v1"
)
// 注册健康检查服务
healthSvc := health.NewServer()
healthSvc.SetServingStatus("user.v1.UserService", healthpb.HealthCheckResponse_SERVING)
healthpb.RegisterHealthServer(server, healthSvc)
// 优雅关闭
func gracefulShutdown(server *grpc.Server) {
sigCh := make(chan os.Signal, 1)
signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM)
<-sigCh
slog.Info("shutting down gRPC server...")
// 设置非SERVING状态,负载均衡器摘除流量
healthSvc.SetServingStatus("user.v1.UserService", healthpb.HealthCheckResponse_NOT_SERVING)
// 停止接收新请求,等待已有请求完成
server.GracefulStop()
slog.Info("server stopped gracefully")
}
gRPC的健康检查协议被Kubernetes原生支持,可直接配置livenessProbe和readinessProbe使用grpc健康检查,无需额外暴露HTTP健康检查端口。优雅关闭中先将健康状态设为NOT_SERVING,使负载均衡器将流量摘除后再停止服务,避免请求打到正在关闭的实例上。
原创文章,作者:小编,如若转载,请注明出处:https://www.yunthe.com/grpc-wei-fu-wu-tong-xin-shi-zhan-protocolbuffers-ding-yi-yu/