← 返回博客
go2026-09-12 22:12:531 分钟 · 348 0

Gin + gRPC:内部服务间高效通信

用 gin 暴露 HTTP 给前端、gRPC 调下游服务,讲清 proto 定义、生成代码、gin 里调 gRPC 客户端,以及错误处理和超时。

#gin#微服务#gRPC

1. 为什么内部用 gRPC

JSON/HTTP 给人读方便,但服务之间调用量大、要强类型、要高性能。gRPC 用 protobuf 二进制编码,体积小、序列化快,自带强类型契约和流式。HTTP 给前端,gRPC 给内部。

2. 定义 proto

syntax = "proto3";
package user;
service UserService { rpc GetUser(GetUserReq) returns (GetUserResp); }
message GetUserReq { string id = 1; }
message GetUserResp { string id = 1; string name = 2; int32 age = 3; }

protocbuf 生成 Go 代码,客户端服务端共用,契约一处定义。

3. 下游 gRPC 服务

func (s *userServer) GetUser(ctx context.Context, req *pb.GetUserReq) (*pb.GetUserResp, error) {
    u, err := s.repo.Find(req.Id)
    if err != nil {
        return nil, status.Error(codes.NotFound, "user not found")
    }
    return &pb.GetUserResp{Id: u.ID, Name: u.Name, Age: u.Age}, nil
}

错误用 status.Error(codes.xxx, msg),对应 HTTP 状态码,前端能映射。

4. Gin 里调 gRPC 客户端

conn, _ := grpc.Dial("user-svc:8081", grpc.WithInsecure()) // 生产用 TLS + 服务发现
client := pb.NewUserServiceClient(conn)

func (h *OrderHandler) Create(c *gin.Context) {
    ctx, cancel := context.WithTimeout(c.Request.Context(), 500*time.Millisecond)
    defer cancel()
    u, err := client.GetUser(ctx, &pb.GetUserReq{Id: uid})
    if err != nil {
        c.JSON(502, gin.H{"error": "用户服务不可用"})
        return
    }
}

连接池复用 conn,别每次 Dial。超时用 context,防下游慢拖垮自己(第 3 篇讲过)。

5. 错误码映射

gRPC 状态码转成前端能懂的 HTTP:

if s, ok := status.FromError(err); ok {
    switch s.Code() {
    case codes.NotFound:
        c.JSON(404, gin.H{"error": "用户不存在"})
    case codes.Unavailable:
        c.JSON(502, gin.H{"error": "用户服务不可用"})
    }
}

6. 连接管理

长连接要保活、要限流。用 grpc.WithKeepaliveParams 保活,客户端连接放全局单例。多实例靠服务发现拿地址(第 6 篇)。

7. 上线清单

proto 当契约,改动要兼容(字段只加不删)。连接全局复用,别每次 Dial。下游调用必带 context 超时。gRPC 错误码映射到 HTTP 状态码。服务间通信走内网,开 mTLS 别裸奔。流式场景(推送、大数据)用 gRPC stream 比轮询好。

下一篇讲事件驱动,用消息队列让服务彻底解耦。

相关推荐

本文为原创文章,采用CC BY-NC-SA 4.0协议授权,转载请保留署名与原文链接。原文链接:https://www.wxbuluo.com/article/200