Golang中gRPC微服务间通信的最佳实践建议
Golang中gRPC微服务间通信的最佳实践建议
编辑:
我一直在纠结如何提出这个问题,但这里有一个更简短直接的版本……
是应该让每个客户端向单一服务发送请求,由该服务完成一次认证后与其他所有服务通信,还是应该让客户端直接向每个微服务发送请求,由它们各自完成请求认证?
大家好!我正在尝试基于微服务创建一个独立研究的应用程序,但在设计过程中遇到了困难,特别是通信方面。我不会详细说明每个服务的功能,因为我主要困惑的是通信和认证问题。
目前项目非常简单。有一个网关服务器,用户连接到这里,并对连接进行认证(通过访问认证服务)。连接认证后,用户可以调用游戏服务器执行操作。这基本上就是一个小游戏;游戏服务器每33毫秒(30帧/秒)进行一次计算,API允许用户执行简单操作,比如移动角色或放置物体。
以下是架构图:

这就是我困惑的地方。让网关接收请求并将这些操作传达给其他服务的最佳方式是什么?
以下是我的想法……
想法1:重定向
每个服务都有自己的gRPC API,因此它们会有自己的protobuf服务。这意味着我无法让网关接受针对其他服务的gRPC调用并仅重定向它们并返回结果(据我所知)。但是否有一种方法可以让调用通过网关以确保认证(包括加密和用户识别),然后转到正确的服务?
想法2:通用调用先到达网关,然后由网关处理其余部分
我还考虑为网关调用创建某种通用格式:
{ servicename, methodName, params... }
然后让网关解析这些内容,并对指定的服务执行正确的调用。这比重定向消息更有意义吗?
我非常希望得到任何建议,因为我刚开始用Go开发服务,并且对整个微服务的概念还很陌生。
更多关于Golang中gRPC微服务间通信的最佳实践建议的实战教程也可以访问 https://www.itying.com/category-94-b0.html
更多关于Golang中gRPC微服务间通信的最佳实践建议的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html
你不需要这个吗?我认为通过查看这个代码库,你可以为你的项目获得一些不错的想法。
据我理解,这仅仅是帮助创建一个RESTful JSON端点,该端点可以转换为gRPC,对吗?
我觉得我的问题表述得不太清楚,当时我自己也在努力理解到底想问什么。
是应该让每个客户端都向单一服务发送请求,由该服务统一完成认证后再与其他所有服务通信,还是应该让客户端直接向各个微服务发送请求,由它们各自对请求进行认证?
请参阅 https://www.nginx.com/blog/deploying-nginx-plus-as-an-api-gateway-part-3-publishing-grpc-services/
和 https://www.envoyproxy.io/ - 任何能够代理 HTTP/2 的组件同样可以代理 gRPC。
身份验证应通过 HTTP 头信息实现,位于协议缓冲区之外。
这取决于具体情况。
API网关通常对客户端有帮助,因为所有服务都通过该网关调用,它也可以进行身份验证。 同时它还能为上游提供一些有用的服务:注册、发现、认证、速率限制等等。 API网关本身也可以向客户端提供这些服务:列出已注册的服务及其.proto文件、端点、速率限制等。
我找到的最接近的是Ambassador(getambassador.io),但对我来说太重量级了:它需要Kubernetes和Envoy。 https://github.com/mwitkow/grpc-proxy 是一个gRPC代理(库),但表明这种高级代理很麻烦:你必须费很大劲才能不了解底层数据类型,只代理HTTP/2字节。
这意味着我认为带有服务注册功能的HTTP/2代理将是最佳选择。
在微服务架构中,gRPC通信和认证的设计确实是一个关键问题。根据你的描述,我建议采用网关集中认证和路由的方案,而不是让客户端直接访问每个微服务。以下是具体实现方案和代码示例:
推荐架构:网关统一认证 + gRPC服务间通信
1. 网关认证和路由设计
// gateway/main.go
package main
import (
"context"
"log"
"net"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/metadata"
"google.golang.org/grpc/status"
"your-project/gateway/proto"
"your-project/auth/proto"
"your-project/game/proto"
)
type GatewayServer struct {
proto.UnimplementedGatewayServer
authClient auth.AuthServiceClient
gameClient game.GameServiceClient
}
// 统一的gRPC调用接口
func (s *GatewayServer) CallService(ctx context.Context, req *proto.ServiceRequest) (*proto.ServiceResponse, error) {
// 从上下文中提取认证信息
md, ok := metadata.FromIncomingContext(ctx)
if !ok {
return nil, status.Error(codes.Unauthenticated, "missing metadata")
}
// 验证用户身份
userID, err := s.authenticateUser(md)
if err != nil {
return nil, status.Error(codes.Unauthenticated, "authentication failed")
}
// 根据服务名路由到对应的微服务
ctx = metadata.NewOutgoingContext(ctx, metadata.Pairs("user-id", userID))
switch req.ServiceName {
case "game":
return s.routeToGameService(ctx, req)
case "auth":
return s.routeToAuthService(ctx, req)
default:
return nil, status.Error(codes.Unimplemented, "unknown service")
}
}
func (s *GatewayServer) authenticateUser(md metadata.MD) (string, error) {
// 调用认证服务验证token
tokens := md.Get("authorization")
if len(tokens) == 0 {
return "", status.Error(codes.Unauthenticated, "missing authorization token")
}
authReq := &auth.ValidateTokenRequest{Token: tokens[0]}
authResp, err := s.authClient.ValidateToken(context.Background(), authReq)
if err != nil {
return "", err
}
return authResp.UserId, nil
}
func (s *GatewayServer) routeToGameService(ctx context.Context, req *proto.ServiceRequest) (*proto.ServiceResponse, error) {
// 将通用请求转换为游戏服务的具体请求
gameReq := &game.PlayerActionRequest{
Action: req.MethodName,
Params: req.Params,
}
gameResp, err := s.gameClient.ExecuteAction(ctx, gameReq)
if err != nil {
return nil, err
}
return &proto.ServiceResponse{
Data: gameResp.Result,
}, nil
}
2. Protobuf定义
// proto/gateway.proto
syntax = "proto3";
package gateway;
service Gateway {
rpc CallService(ServiceRequest) returns (ServiceResponse);
}
message ServiceRequest {
string service_name = 1;
string method_name = 2;
bytes params = 3; // 可以是JSON或其他序列化格式
}
message ServiceResponse {
bytes data = 1;
string error = 2;
}
// proto/game.proto
syntax = "proto3";
package game;
service GameService {
rpc ExecuteAction(PlayerActionRequest) returns (PlayerActionResponse);
}
message PlayerActionRequest {
string action = 1;
bytes params = 2;
}
message PlayerActionResponse {
bytes result = 1;
}
3. 游戏服务实现
// game/main.go
package main
import (
"context"
"log"
"net"
"google.golang.org/grpc"
"google.golang.org/grpc/metadata"
"your-project/game/proto"
)
type GameServer struct {
proto.UnimplementedGameServiceServer
}
func (s *GameServer) ExecuteAction(ctx context.Context, req *proto.PlayerActionRequest) (*proto.PlayerActionResponse, error) {
// 从上下文中获取用户ID(由网关传递)
md, ok := metadata.FromIncomingContext(ctx)
if !ok {
return nil, status.Error(codes.Unauthenticated, "missing user context")
}
userIDs := md.Get("user-id")
if len(userIDs) == 0 {
return nil, status.Error(codes.Unauthenticated, "missing user id")
}
userID := userIDs[0]
// 处理游戏逻辑
result, err := s.handleGameAction(userID, req.Action, req.Params)
if err != nil {
return nil, err
}
return &proto.PlayerActionResponse{
Result: result,
}, nil
}
func (s *GameServer) handleGameAction(userID, action string, params []byte) ([]byte, error) {
// 具体的游戏逻辑处理
switch action {
case "move":
return s.handleMove(userID, params)
case "place":
return s.handlePlace(userID, params)
default:
return nil, status.Error(codes.InvalidArgument, "unknown action")
}
}
4. 客户端调用示例
// 客户端代码
func callGameService() error {
conn, err := grpc.Dial("gateway:50051", grpc.WithInsecure())
if err != nil {
return err
}
defer conn.Close()
client := gateway.NewGatewayClient(conn)
// 设置认证token
ctx := metadata.NewOutgoingContext(context.Background(),
metadata.Pairs("authorization", "user-token-here"))
// 构造通用请求
params, _ := json.Marshal(map[string]interface{}{
"x": 10,
"y": 20,
})
req := &gateway.ServiceRequest{
ServiceName: "game",
MethodName: "move",
Params: params,
}
resp, err := client.CallService(ctx, req)
if err != nil {
return err
}
// 处理响应
var result map[string]interface{}
json.Unmarshal(resp.Data, &result)
return nil
}
优势分析
这种架构的优势:
- 统一认证:所有请求都经过网关认证,避免在每个服务中重复认证逻辑
- 服务隔离:内部服务不需要暴露给外部网络
- 可扩展性:新增服务只需在网关中添加路由逻辑
- 安全性:用户凭证只在网关处理,内部服务使用用户ID进行授权
这种设计符合你的"想法2",但提供了更具体的gRPC实现方案。网关作为统一的入口点,负责认证和请求路由,而各个微服务专注于业务逻辑实现。


