DevToolBox免费
博客

gRPC 指南 2026:Protocol Buffers、流式传输、Node.js、Go、Python 与最佳实践

22 分钟阅读作者 DevToolBox
TL;DR

gRPC 是一个使用 Protocol Buffers 序列化和 HTTP/2 传输的高性能 RPC 框架。支持一元调用、服务端流、客户端流和双向流。在 .proto 文件中定义服务,生成类型安全的客户端/服务端代码,内置 TLS、截止时间和拦截器等功能。内部微服务使用 gRPC,浏览器端 API 使用 REST 或 gRPC-Web。

1. What is gRPC?

gRPC (gRPC Remote Procedure Call) is an open-source high-performance RPC framework originally developed by Google. It uses Protocol Buffers (protobuf) as its interface definition language and serialization format, and runs on HTTP/2 for transport. This combination gives gRPC binary efficiency, multiplexed connections, header compression, and native streaming support.

Unlike REST where you design around resources and HTTP verbs, gRPC lets you define services with methods that clients call as if they were local function calls. The protobuf compiler generates strongly typed client and server stubs in over a dozen languages, eliminating manual serialization code.

2. Protocol Buffers Primer

Protocol Buffers (protobuf) is a language-neutral, platform-neutral mechanism for serializing structured data. You define your data shape once in a .proto file, then generate code for any target language.

Basic Message Definition

syntax = "proto3";

package user;

message User {
  string id = 1;
  string name = 2;
  string email = 3;
  int32 age = 4;
  repeated string roles = 5;    // list of strings
  Address address = 6;          // nested message
}

message Address {
  string street = 1;
  string city = 2;
  string country = 3;
  string zip_code = 4;
}

Each field has a type, a name, and a unique field number. Field numbers are used in the binary encoding and must not change once your message type is in use. The repeated keyword indicates a list. Proto3 makes all fields optional by default.

Protobuf vs JSON Size Comparison

FormatPayload SizeEncode TimeDecode Time
JSON~250 bytes~1.2 ms~1.5 ms
Protobuf~48 bytes~0.15 ms~0.12 ms

3. gRPC vs REST: When to Use Which

CriteriagRPCREST
SerializationProtobuf (binary)JSON (text)
TransportHTTP/2HTTP/1.1 or HTTP/2
StreamingBuilt-in (4 patterns)SSE or WebSocket
Code generationNative (protoc)Optional (OpenAPI)
Browser supportVia gRPC-Web proxyNative
Best forMicroservices, streaming, polyglotPublic APIs, CRUD, simple integrations

Rule of thumb: Use gRPC for internal service-to-service communication where performance and type safety matter. Use REST for public APIs consumed by third-party developers or browser clients without a proxy layer.

4. Defining Services with .proto Files

A gRPC service definition declares the RPC methods available to clients. Each method specifies a request and response message type, and optionally uses the stream keyword.

syntax = "proto3";
package order;

service OrderService {
  rpc CreateOrder (CreateOrderRequest) returns (Order);           // unary
  rpc GetOrder (GetOrderRequest) returns (Order);                 // unary
  rpc ListOrders (ListOrdersRequest) returns (stream Order);     // server stream
  rpc UploadLineItems (stream LineItem) returns (UploadSummary); // client stream
  rpc OrderChat (stream ChatMessage) returns (stream ChatMessage); // bidi
}

message CreateOrderRequest {
  string customer_id = 1;
  repeated LineItem items = 2;
}

message LineItem {
  string product_id = 1;
  int32 quantity = 2;
  double unit_price = 3;
}

message Order {
  string id = 1;
  string customer_id = 2;
  repeated LineItem items = 3;
  double total = 4;
  OrderStatus status = 5;
}

enum OrderStatus {
  ORDER_STATUS_UNSPECIFIED = 0;
  ORDER_STATUS_PENDING = 1;
  ORDER_STATUS_CONFIRMED = 2;
  ORDER_STATUS_SHIPPED = 3;
}

Compile the proto file to generate language-specific code:

# Generate Go code
protoc --go_out=. --go-grpc_out=. proto/order.proto

# Generate Node.js/TypeScript code
protoc --ts_out=./src/gen proto/order.proto

# Generate Python code
python -m grpc_tools.protoc -I. --python_out=. --grpc_python_out=. proto/order.proto

5. Streaming Patterns Explained

PatternProto SyntaxUse Cases
Unaryrpc Get(Req) returns (Resp)CRUD, auth, lookups
Server streamingrpc List(Req) returns (stream Resp)Feeds, log tailing, large result sets
Client streamingrpc Upload(stream Req) returns (Resp)File uploads, batch ingestion, telemetry
Bidirectionalrpc Chat(stream Msg) returns (stream Msg)Chat, collaborative editing, game state

The stream keyword in the proto definition controls the pattern. Both sides read and write independently in bidirectional mode, and the two streams operate over a single HTTP/2 connection.

6. gRPC in Node.js / TypeScript

The @grpc/grpc-js package is the official pure-JavaScript gRPC client and server. Pair it with @grpc/proto-loader for dynamic loading or ts-proto for generated TypeScript types.

Server Implementation

import * as grpc from "@grpc/grpc-js";
import * as protoLoader from "@grpc/proto-loader";

const PROTO_PATH = "./proto/order.proto";

const packageDef = protoLoader.loadSync(PROTO_PATH, {
  keepCase: true,
  longs: String,
  enums: String,
  defaults: true,
  oneofs: true,
});

const proto = grpc.loadPackageDefinition(packageDef) as any;

function createOrder(
  call: grpc.ServerUnaryCall<any, any>,
  callback: grpc.sendUnaryData<any>
) {
  const request = call.request;
  const order = {
    id: "ord-" + Date.now(),
    customer_id: request.customer_id,
    items: request.items,
    total: request.items.reduce(
      (sum: number, item: any) => sum + item.quantity * item.unit_price, 0
    ),
    status: "ORDER_STATUS_PENDING",
  };
  callback(null, order);
}

function listOrders(
  call: grpc.ServerWritableStream<any, any>
) {
  // Server streaming: push orders one by one
  const orders = getOrdersFromDB(call.request.customer_id);
  for (const order of orders) {
    call.write(order);
  }
  call.end();
}

const server = new grpc.Server();
server.addService(proto.order.OrderService.service, {
  createOrder,
  listOrders,
});

server.bindAsync(
  "0.0.0.0:50051",
  grpc.ServerCredentials.createInsecure(),
  (err, port) => {
    if (err) throw err;
    console.log("gRPC server running on port " + port);
  }
);

Client Implementation

const client = new proto.order.OrderService(
  "localhost:50051",
  grpc.credentials.createInsecure()
);

// Unary call
client.createOrder(
  { customer_id: "cust-1", items: [{ product_id: "p-1", quantity: 2, unit_price: 29.99 }] },
  (err: any, response: any) => {
    if (err) return console.error("Error:", err.message);
    console.log("Order created:", response.id);
  }
);

// Server streaming
const stream = client.listOrders({ customer_id: "cust-1" });
stream.on("data", (order: any) => console.log("Order:", order.id));
stream.on("end", () => console.log("All orders received"));
stream.on("error", (err: any) => console.error("Error:", err.message));

7. gRPC in Go

Go has first-class gRPC support through google.golang.org/grpc. The generated code provides interfaces you implement for the server and ready-to-use clients with full type safety.

Server Implementation

package main

import (
    "context"
    "fmt"
    "log"
    "net"
    "time"

    "google.golang.org/grpc"
    pb "myapp/gen/order"
)

type orderServer struct {
    pb.UnimplementedOrderServiceServer
}

func (s *orderServer) CreateOrder(
    ctx context.Context,
    req *pb.CreateOrderRequest,
) (*pb.Order, error) {
    order := &pb.Order{
        Id:         fmt.Sprintf("ord-%d", time.Now().UnixMilli()),
        CustomerId: req.CustomerId,
        Items:      req.Items,
        Status:     pb.OrderStatus_ORDER_STATUS_PENDING,
    }
    return order, nil
}

func (s *orderServer) ListOrders(
    req *pb.ListOrdersRequest,
    stream pb.OrderService_ListOrdersServer,
) error {
    orders := getOrdersFromDB(req.CustomerId)
    for _, order := range orders {
        if err := stream.Send(order); err != nil {
            return err
        }
    }
    return nil
}

func main() {
    lis, err := net.Listen("tcp", ":50051")
    if err != nil {
        log.Fatalf("failed to listen: %v", err)
    }

    srv := grpc.NewServer()
    pb.RegisterOrderServiceServer(srv, &orderServer{})

    log.Println("gRPC server listening on :50051")
    if err := srv.Serve(lis); err != nil {
        log.Fatalf("failed to serve: %v", err)
    }
}

Client Implementation

conn, err := grpc.Dial(
    "localhost:50051",
    grpc.WithTransportCredentials(insecure.NewCredentials()),
)
if err != nil {
    log.Fatalf("failed to connect: %v", err)
}
defer conn.Close()

client := pb.NewOrderServiceClient(conn)

// Unary call with deadline
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()

order, err := client.CreateOrder(ctx, &pb.CreateOrderRequest{
    CustomerId: "cust-1",
    Items: []*pb.LineItem{
        {ProductId: "p-1", Quantity: 2, UnitPrice: 29.99},
    },
})
fmt.Println("Created order:", order.Id)

// Server streaming — read until EOF
stream, _ := client.ListOrders(ctx, &pb.ListOrdersRequest{CustomerId: "cust-1"})
for {
    o, err := stream.Recv()
    if err == io.EOF { break }
    if err != nil { log.Fatal(err) }
    fmt.Println("Order:", o.Id)
}

8. gRPC in Python

Python uses grpcio and grpcio-tools packages. The generated code provides typed stubs for both sync and async usage.

Server Implementation

import grpc
from concurrent import futures
import time
import order_pb2
import order_pb2_grpc

class OrderServicer(order_pb2_grpc.OrderServiceServicer):
    def CreateOrder(self, request, context):
        total = sum(
            item.quantity * item.unit_price
            for item in request.items
        )
        return order_pb2.Order(
            id=f"ord-{int(time.time() * 1000)}",
            customer_id=request.customer_id,
            items=request.items,
            total=total,
            status=order_pb2.ORDER_STATUS_PENDING,
        )

    def ListOrders(self, request, context):
        orders = get_orders_from_db(request.customer_id)
        for order in orders:
            yield order  # server streaming

def serve():
    server = grpc.server(
        futures.ThreadPoolExecutor(max_workers=10)
    )
    order_pb2_grpc.add_OrderServiceServicer_to_server(
        OrderServicer(), server
    )
    server.add_insecure_port("[::]:50051")
    server.start()
    print("gRPC server running on port 50051")
    server.wait_for_termination()

if __name__ == "__main__":
    serve()

Client with Deadline

import grpc
import order_pb2
import order_pb2_grpc

channel = grpc.insecure_channel("localhost:50051")
stub = order_pb2_grpc.OrderServiceStub(channel)

# Unary call with 5-second deadline
try:
    response = stub.CreateOrder(
        order_pb2.CreateOrderRequest(
            customer_id="cust-1",
            items=[
                order_pb2.LineItem(
                    product_id="p-1",
                    quantity=2,
                    unit_price=29.99,
                )
            ],
        ),
        timeout=5.0,
    )
    print(f"Created order: {response.id}")
except grpc.RpcError as e:
    print(f"RPC failed: {e.code()} - {e.details()}")

# Server streaming
for order in stub.ListOrders(
    order_pb2.ListOrdersRequest(customer_id="cust-1")
):
    print(f"Order: {order.id}")

9. Authentication and Security

gRPC provides channel-level encryption with TLS and call-level authentication via metadata. Production services should always use TLS.

TLS Configuration (Go Server)

import "google.golang.org/grpc/credentials"

creds, err := credentials.NewServerTLSFromFile(
    "server.crt",
    "server.key",
)
if err != nil {
    log.Fatalf("failed to load TLS: %v", err)
}

srv := grpc.NewServer(grpc.Creds(creds))

Token-Based Auth with Interceptors (Go)

import (
    "context"
    "google.golang.org/grpc"
    "google.golang.org/grpc/codes"
    "google.golang.org/grpc/metadata"
    "google.golang.org/grpc/status"
)

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, "missing metadata",
        )
    }

    tokens := md.Get("authorization")
    if len(tokens) == 0 {
        return nil, status.Error(
            codes.Unauthenticated, "missing token",
        )
    }

    if err := validateToken(tokens[0]); err != nil {
        return nil, status.Error(
            codes.Unauthenticated, "invalid token",
        )
    }

    return handler(ctx, req)
}

// Register the interceptor
srv := grpc.NewServer(
    grpc.UnaryInterceptor(authInterceptor),
)

Sending Auth Token from Client (Node.js)

const metadata = new grpc.Metadata();
metadata.set("authorization", "Bearer " + token);

client.createOrder(
  { customer_id: "cust-1", items: [] },
  metadata,
  (err, response) => {
    if (err) console.error(err);
    else console.log("Order:", response.id);
  }
);

10. Error Handling and Status Codes

gRPC defines a standard set of status codes. Always return meaningful codes and messages so clients can handle errors appropriately.

CodeNameUse Case
0OKSuccess
1CANCELLEDClient cancelled the request
3INVALID_ARGUMENTBad request data (validation)
4DEADLINE_EXCEEDEDRequest took too long
5NOT_FOUNDResource does not exist
7PERMISSION_DENIEDAuthenticated but not authorized
13INTERNALServer-side bug
14UNAVAILABLEService temporarily down (retry)
16UNAUTHENTICATEDMissing or invalid credentials

Rich Error Details (Go)

import (
    "google.golang.org/grpc/codes"
    "google.golang.org/grpc/status"
    "google.golang.org/genproto/googleapis/rpc/errdetails"
)

func (s *orderServer) GetOrder(
    ctx context.Context,
    req *pb.GetOrderRequest,
) (*pb.Order, error) {
    order, err := s.db.FindOrder(req.Id)
    if err != nil {
        st := status.New(codes.NotFound, "order not found")
        detail := &errdetails.ErrorInfo{
            Reason: "ORDER_NOT_FOUND",
            Domain: "order.myapp.com",
            Metadata: map[string]string{
                "order_id": req.Id,
            },
        }
        st, _ = st.WithDetails(detail)
        return nil, st.Err()
    }
    return order, nil
}

Error Handling on the Client (Python)

try:
    order = stub.GetOrder(
        order_pb2.GetOrderRequest(id="ord-missing"),
        timeout=5.0,
    )
except grpc.RpcError as e:
    if e.code() == grpc.StatusCode.NOT_FOUND:
        print("Order not found:", e.details())
    elif e.code() == grpc.StatusCode.DEADLINE_EXCEEDED:
        print("Request timed out")
    elif e.code() == grpc.StatusCode.UNAVAILABLE:
        print("Service down, retrying...")
    else:
        print(f"RPC error: {e.code()} - {e.details()}")

11. gRPC-Web for Browser Clients

Browsers cannot make native gRPC calls due to missing HTTP/2 trailer support. gRPC-Web bridges this gap by translating browser-compatible requests into native gRPC through a proxy.

Architecture

Browser (gRPC-Web client)
    |
    | HTTP/1.1 or HTTP/2 (no trailers)
    v
Envoy Proxy (or grpc-web middleware)
    |
    | Native gRPC over HTTP/2
    v
gRPC Backend Server

Envoy Proxy Configuration (Key Parts)

# envoy.yaml — essential gRPC-Web filters
static_resources:
  listeners:
    - name: listener_0
      address:
        socket_address: { address: 0.0.0.0, port_value: 8080 }
      filter_chains:
        - filters:
            - name: envoy.filters.network.http_connection_manager
              typed_config:
                http_filters:
                  - name: envoy.filters.http.grpc_web  # key filter
                  - name: envoy.filters.http.cors
                  - name: envoy.filters.http.router
  clusters:
    - name: grpc_service
      type: logical_dns
      http2_protocol_options: {}  # must enable HTTP/2
      load_assignment:
        endpoints:
          - lb_endpoints:
              - endpoint:
                  address:
                    socket_address:
                      address: backend
                      port_value: 50051

Browser Client (TypeScript)

import { OrderServiceClient } from "./gen/order_grpc_web_pb";
import { CreateOrderRequest, LineItem } from "./gen/order_pb";

const client = new OrderServiceClient(
  "https://api.example.com:8080"
);

const item = new LineItem();
item.setProductId("p-1");
item.setQuantity(2);
item.setUnitPrice(29.99);

const request = new CreateOrderRequest();
request.setCustomerId("cust-1");
request.setItemsList([item]);

client.createOrder(request, {}, (err, response) => {
  if (err) {
    console.error("gRPC-Web error:", err.message);
    return;
  }
  console.log("Order ID:", response.getId());
});

12. Load Balancing and Service Mesh

gRPC uses long-lived HTTP/2 connections, which means a traditional L4 (TCP) load balancer will send all requests from one connection to the same backend. You need L7 (application-layer) balancing or client-side balancing.

Load Balancing Strategies

StrategyHow It WorksProsCons
L7 Proxy (Envoy)Proxy understands HTTP/2 framesPer-request balancing, no client changesExtra hop latency
Client-side (round-robin)Client discovers backends and distributesNo proxy needed, lowest latencyClient complexity, stale endpoints
Service Mesh (Istio)Sidecar proxy per podTransparent, advanced featuresOperational overhead, resource usage

Kubernetes Headless Service

# Headless Service for client-side load balancing
apiVersion: v1
kind: Service
metadata:
  name: order-service
spec:
  clusterIP: None   # returns all pod IPs for DNS
  selector:
    app: order-service
  ports:
    - port: 50051
      targetPort: 50051
---
# Deployment with gRPC health probe
apiVersion: apps/v1
kind: Deployment
metadata:
  name: order-service
spec:
  replicas: 3
  template:
    spec:
      containers:
        - name: order-service
          image: myapp/order-service:latest
          ports:
            - containerPort: 50051
          readinessProbe:
            grpc:
              port: 50051

13. Best Practices for Production

Always Set Deadlines

Never make a gRPC call without a deadline. Without deadlines, a slow or stuck server can hold client resources indefinitely, cascading failures across services.

// Go: always set a deadline
ctx, cancel := context.WithTimeout(
    context.Background(),
    5*time.Second,
)
defer cancel()

// Node.js: set deadline in options
// const deadline = new Date();
// deadline.setSeconds(deadline.getSeconds() + 5);
// client.createOrder(request, { deadline }, callback);

// Python: timeout parameter
// stub.CreateOrder(request, timeout=5.0)

Use Interceptors for Cross-Cutting Concerns

// Go: chain logging + auth + rate limit interceptors
srv := grpc.NewServer(
    grpc.ChainUnaryInterceptor(
        loggingInterceptor,   // logs method, duration, error
        authInterceptor,      // validates JWT token
        rateLimitInterceptor, // token-bucket per client
    ),
)

Health Checks and Reflection

import (
    "google.golang.org/grpc/health"
    "google.golang.org/grpc/health/grpc_health_v1"
    "google.golang.org/grpc/reflection"
)

// Enable health checking
healthSrv := health.NewServer()
grpc_health_v1.RegisterHealthServer(srv, healthSrv)
healthSrv.SetServingStatus(
    "order.OrderService",
    grpc_health_v1.HealthCheckResponse_SERVING,
)

// Enable reflection (for grpcurl, grpcui)
reflection.Register(srv)

// Now you can introspect with grpcurl:
// grpcurl -plaintext localhost:50051 list
// grpcurl -plaintext localhost:50051 order.OrderService/CreateOrder

Proto File Best Practices

  • Never reuse field numbers — deleted fields should be reserved
  • Use packages — namespace your protos to avoid conflicts
  • Prefix enum values — e.g., ORDER_STATUS_PENDING not just PENDING
  • Add a zero value for enums — first value should be UNSPECIFIED = 0
  • Use wrapper types for optional scalarsgoogle.protobuf.Int32Value
  • Version your APIs — use package myapp.order.v1
  • Lint your protos — use buf lint to enforce style rules

Retry and Backoff

// Go: retry policy via service config JSON
serviceConfig := `{"methodConfig":[{"name":[{"service":"order.OrderService"}],
  "retryPolicy":{"maxAttempts":3,"initialBackoff":"0.1s",
  "maxBackoff":"1s","backoffMultiplier":2.0,
  "retryableStatusCodes":["UNAVAILABLE","DEADLINE_EXCEEDED"]}}]}`

conn, _ := grpc.Dial("localhost:50051",
    grpc.WithDefaultServiceConfig(serviceConfig),
    grpc.WithTransportCredentials(insecure.NewCredentials()),
)

Graceful Shutdown

// Go: graceful shutdown
quit := make(chan os.Signal, 1)
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
<-quit

log.Println("Shutting down gRPC server...")
srv.GracefulStop()  // waits for in-flight RPCs
log.Println("Server stopped")
Key Takeaways
  • gRPC 使用 Protocol Buffers 进行二进制序列化 — 比 JSON 快 5-10 倍且体积更小
  • HTTP/2 多路复用支持流式传输并消除队头阻塞
  • 在 .proto 文件中定义一次服务,即可生成任何支持语言的客户端
  • 四种通信模式:一元、服务端流、客户端流、双向流
  • 使用 gRPC-Web 或网关代理将 gRPC 服务暴露给浏览器客户端
  • 始终设置截止时间,使用拦截器处理横切关注点,生产环境启用 TLS
  • gRPC 原生集成 Kubernetes、Istio 和 Envoy 进行负载均衡
  • 使用健康检查、反射和结构化状态码确保生产就绪
𝕏 Twitterin LinkedIn
这篇文章有帮助吗?

保持更新

获取每周开发技巧和新工具通知。

无垃圾邮件,随时退订。

试试这些相关工具

{ }JSON FormatterB→Base64 Encoder#Hash Generator

相关文章

微服务指南:架构、通信模式和最佳实践

掌握微服务架构。涵盖服务通信(REST/gRPC/Kafka)、API网关、服务发现、分布式追踪、CQRS、Saga模式、Docker、Kubernetes和可观测性。

API设计指南:REST最佳实践、OpenAPI、认证、分页和缓存

掌握API设计。涵盖REST原则、版本控制策略、JWT/OAuth 2.0认证、OpenAPI/Swagger规范、速率限制、RFC 7807错误处理、分页模式、ETags缓存以及REST vs GraphQL vs gRPC vs tRPC对比。

NestJS完整指南:模块、控制器、服务、依赖注入、TypeORM、JWT认证和测试

从零掌握NestJS。涵盖模块、控制器、服务、Provider、依赖注入、TypeORM/Prisma数据库集成、JWT认证、守卫、管道、拦截器、异常过滤器以及Jest单元测试和e2e测试。