gRPC Deep Dive
Từ Protobuf wire format đến streaming patterns, interceptors và production deployment. gRPC = HTTP/2 transport + Protocol Buffers serialization + code generation.
gRPC là gì?
gRPC (Google Remote Procedure Call) bắt đầu từ hệ thống nội bộ "Stubby" của Google, được open-source năm 2016. Thiết kế cho microservices: strongly-typed contracts, binary wire format, streaming built-in, và code generation cho 10+ ngôn ngữ.
gRPC dùng HTTP/2 làm transport (multiplexed streams, header compression, bidirectional) và Protocol Buffers làm serialization format mặc định (có thể thay bằng JSON, Thrift, v.v.).
| Tính năng | gRPC | REST/JSON | GraphQL |
|---|---|---|---|
| Schema | Bắt buộc (.proto) | Tùy chọn (OpenAPI) | Bắt buộc (SDL) |
| Transport | HTTP/2 | HTTP/1.1 hoặc HTTP/2 | HTTP/1.1 hoặc HTTP/2 |
| Payload | Binary (Protobuf) | Text (JSON) | Text (JSON) |
| Streaming | ✓ (4 patterns) | SSE (server-only) | Subscriptions (WS) |
| Browser support | gRPC-Web (proxy cần) | ✓ Native | ✓ Native |
| Code generation | ✓ First-class | Optional (OpenAPI) | Optional |
| Human readable | ✗ Binary | ✓ | ✓ |
| Performance | Tốt nhất (binary + H2) | Trung bình | Trung bình |
- Microservices inter-service: typed contracts, low latency, code gen cho nhiều ngôn ngữ
- Mobile clients: binary payload nhỏ hơn JSON 3-10x, tiết kiệm bandwidth
- Real-time streaming: telemetry, log streaming, live feeds — bidirectional streaming built-in
- Internal APIs: developer experience tốt hơn với generated stubs
Protocol Buffers — Proto3 Syntax
Protocol Buffers (Protobuf) là ngôn ngữ định nghĩa schema và serialization format của Google. Proto3 là phiên bản hiện đại nhất, đơn giản hơn proto2 (bỏ required fields, default values, extensions).
syntax = "proto3";
package users.v1;
option go_package = "github.com/myorg/users/v1;usersv1";
import "google/protobuf/timestamp.proto";
// Scalar types: double, float, int32, int64, uint32, uint64,
// sint32, sint64, fixed32, fixed64, sfixed32, sfixed64, bool, string, bytes
message User {
uint64 id = 1; // field number 1-15 = 1-byte tag (hot fields)
string name = 2;
string email = 3;
UserStatus status = 4;
repeated string tags = 5; // repeated = array/list
map<string, string> metadata = 6;
oneof contact { // only one of these can be set
string phone = 7;
string slack = 8;
}
google.protobuf.Timestamp created_at = 9;
reserved 10, 11; // never reuse these numbers
reserved "old_field"; // never reuse this name
}
enum UserStatus {
USER_STATUS_UNSPECIFIED = 0; // proto3: enum must start with 0
USER_STATUS_ACTIVE = 1;
USER_STATUS_SUSPENDED = 2;
}
Field Numbers
Field numbers là duy nhất và vĩnh viễn. Chúng xác định field trong wire format — không phải field name. Điều này cho phép rename field mà không breaking.
- 1–15: encode bằng 1 byte tag → dùng cho fields hot/phổ biến
- 16–2047: encode bằng 2 bytes tag
- 19000–19999: reserved for Protobuf internal
- Không bao giờ tái sử dụng số đã xóa → dùng
reserved
Default Values trong Proto3
| Type | Default value |
|---|---|
| Numeric (int, float, double) | 0 |
| bool | false |
| string | "" (empty string) |
| bytes | empty bytes |
| enum | first value (must be 0) |
| message | null / không set |
Không phân biệt được "field không set" và "field set về giá trị default (0, false, '')". Để distinguish, dùng optional keyword (proto3 optional, tạo synthetic oneof) hoặc google.protobuf.Int32Value (wrapper types).
Protobuf Wire Format
Protobuf serialize thành binary — compact và nhanh. Hiểu wire format giúp debug và hiểu tại sao Protobuf nhỏ hơn JSON.
Ví dụ thực tế — decode hexdump
Proto message: { name: "Alice", id: 1 }
Serialized hex: 0a 05 41 6c 69 63 65 10 01
0a → tag: field_number=1 (name), wire_type=2 (LEN)
05 → varint: length = 5 bytes
41 6c 69 63 65 → "Alice" (UTF-8)
10 → tag: field_number=2 (id), wire_type=0 (Varint)
01 → varint: value = 1
So sánh JSON: {"name":"Alice","id":1} = 21 bytes vs Protobuf: 9 bytes
Varint Types
| Proto type | Wire type | Encoding | Khi nào dùng |
|---|---|---|---|
int32/int64 | 0 Varint | Varint (negative = 10 bytes!) | Numbers thường non-negative |
sint32/sint64 | 0 Varint | ZigZag + Varint | Numbers có thể negative thường xuyên |
fixed32/sfixed32 | 5 32-bit | Little-endian fixed | Values > 2²⁸ (fixed thường nhỏ hơn varint) |
fixed64/sfixed64 | 1 64-bit | Little-endian fixed | Values > 2⁵⁶ |
Varint thông thường encode số âm rất tệ: -1 → 10 bytes (vì treated as large uint64). ZigZag map: 0→0, -1→1, 1→2, -2→3, 2→4, ... Công thức: (n << 1) ^ (n >> 31). Số âm nhỏ được encode compact.
Service Definition
Service definition trong .proto file mô tả các RPC methods — interface contract giữa client và server. Từ file này, protoc generate code cho cả client (stub) và server (interface).
syntax = "proto3";
package chat.v1;
service ChatService {
// Unary: 1 request → 1 response
rpc SendMessage (MessageRequest) returns (MessageResponse);
// Server streaming: 1 request → N responses (stream)
rpc SubscribeRoom (SubscribeRequest) returns (stream Message);
// Client streaming: N requests (stream) → 1 response
rpc UploadHistory (stream Message) returns (UploadSummary);
// Bidirectional streaming: N requests ↔ N responses
rpc Chat (stream Message) returns (stream Message);
}
message MessageRequest {
string room_id = 1;
string content = 2;
}
message MessageResponse {
string message_id = 1;
google.protobuf.Timestamp sent_at = 2;
}
message Message {
string id = 1;
string room_id = 2;
string from = 3;
string content = 4;
google.protobuf.Timestamp ts = 5;
}
Well-Known Types
| Type | Import | Dùng cho |
|---|---|---|
google.protobuf.Timestamp | google/protobuf/timestamp.proto | Thời điểm (seconds + nanos) |
google.protobuf.Duration | google/protobuf/duration.proto | Khoảng thời gian |
google.protobuf.Empty | google/protobuf/empty.proto | Method không có request/response body |
google.protobuf.Any | google/protobuf/any.proto | Dynamic typed value |
google.protobuf.FieldMask | google/protobuf/field_mask.proto | Partial updates (chỉ update các fields cụ thể) |
google.protobuf.Struct | google/protobuf/struct.proto | JSON-like dynamic structure |
- ✅ Thêm field mới: OK — old clients bỏ qua (unknown fields preserved), old servers trả default
- ✅ Rename field: OK — wire format dùng field number, không phải name
- ❌ Đổi field number: BREAKING — sẽ decode sai
- ❌ Đổi type: BREAKING (nếu wire type khác nhau)
- ❌ Xóa field: dùng
reservedthay vì xóa hoàn toàn - ✅ Version trong package name:
users.v1→users.v2để breaking changes
Code Generation
protoc (Protocol Buffer compiler) đọc .proto files và generate code. Với các language plugins, nó generate message classes, serialization code, và gRPC service stubs.
protoc \ --proto_path=./proto \ --go_out=./gen \ --go_opt=paths=source_relative \ --go-grpc_out=./gen \ --go-grpc_opt=paths=source_relative \ proto/chat/v1/chat.proto
buf — Modern Alternative
buf là tool hiện đại thay thế protoc workflow, với lint, format, breaking change detection, và Buf Schema Registry (BSR).
# buf.gen.yaml
version: v1
plugins:
- plugin: go
out: gen
opt: paths=source_relative
- plugin: go-grpc
out: gen
opt: paths=source_relative,require_unimplemented_servers=false
# Detect breaking changes so you never break consumers
buf breaking --against '.git#branch=main'
Generated Code Structure (Go)
// Generated message type
type User struct {
Id uint64 `protobuf:"varint,1,opt,name=id"`
Name string `protobuf:"bytes,2,opt,name=name"`
Email string `protobuf:"bytes,3,opt,name=email"`
}
// Generated client interface
type ChatServiceClient interface {
SendMessage(ctx context.Context, in *MessageRequest, opts ...grpc.CallOption) (*MessageResponse, error)
SubscribeRoom(ctx context.Context, in *SubscribeRequest, opts ...grpc.CallOption) (ChatService_SubscribeRoomClient, error)
Chat(ctx context.Context, opts ...grpc.CallOption) (ChatService_ChatClient, error)
}
// Generated server interface (you implement this)
type ChatServiceServer interface {
SendMessage(context.Context, *MessageRequest) (*MessageResponse, error)
SubscribeRoom(*SubscribeRequest, ChatService_SubscribeRoomServer) error
Chat(ChatService_ChatServer) error
mustEmbedUnimplementedChatServiceServer()
}
gRPC hỗ trợ: Go, Java/Kotlin, Python, C++, C#, Ruby, PHP, Dart, Swift, Rust (community), Node.js, và nhiều ngôn ngữ khác qua community plugins.
4 Communication Patterns
gRPC định nghĩa 4 kiểu RPC, được build trên HTTP/2 streams. Tất cả đều async theo hướng transport; blocking/non-blocking phụ thuộc vào stub type.
Unary RPC — Go example
// Server implementation
func (s *ChatServer) SendMessage(ctx context.Context, req *pb.MessageRequest) (*pb.MessageResponse, error) {
if req.Content == "" {
return nil, status.Error(codes.InvalidArgument, "content cannot be empty")
}
id := s.store.Save(req.RoomId, req.Content)
return &pb.MessageResponse{MessageId: id, SentAt: timestamppb.Now()}, nil
}
// Client call
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
resp, err := client.SendMessage(ctx, &pb.MessageRequest{RoomId: "general", Content: "hello"})
Server Streaming RPC — Go example
// Server: send multiple messages
func (s *ChatServer) SubscribeRoom(req *pb.SubscribeRequest, stream pb.ChatService_SubscribeRoomServer) error {
ch := s.subscribe(req.RoomId)
defer s.unsubscribe(req.RoomId, ch)
for {
select {
case msg := <-ch:
if err := stream.Send(msg); err != nil { return err }
case <-stream.Context().Done():
return stream.Context().Err()
}
}
}
// Client: range over stream
stream, err := client.SubscribeRoom(ctx, &pb.SubscribeRequest{RoomId: "general"})
for {
msg, err := stream.Recv()
if err == io.EOF { break }
if err != nil { log.Fatal(err) }
fmt.Println(msg.Content)
}
Client Streaming RPC — Go example
// Client: send stream, then receive 1 response
stream, _ := client.UploadHistory(ctx)
for _, msg := range messages {
stream.Send(msg)
}
summary, err := stream.CloseAndRecv() // half-close + wait for response
// Server: receive all, then respond
func (s *ChatServer) UploadHistory(stream pb.ChatService_UploadHistoryServer) error {
var count int32
for {
msg, err := stream.Recv()
if err == io.EOF {
return stream.SendAndClose(&pb.UploadSummary{Count: count})
}
if err != nil { return err }
s.store.Save(msg)
count++
}
}
Bidirectional Streaming — Go example
func (s *ChatServer) Chat(stream pb.ChatService_ChatServer) error {
// Separate goroutines for read and write
errCh := make(chan error, 1)
go func() {
for {
msg, err := stream.Recv()
if err != nil { errCh <- err; return }
s.broadcast(msg)
}
}()
// ... subscribe and send to this client
select {
case err := <-errCh: return err
case <-stream.Context().Done(): return stream.Context().Err()
}
}
Channels & Stubs
Một Channel là logical connection đến một gRPC server endpoint. Phía dưới, channel quản lý một pool các subchannels (TCP connections), tự động reconnect và load-balance.
// Go — tạo channel và stub
conn, err := grpc.NewClient("dns:///api.example.com:50051",
grpc.WithTransportCredentials(credentials.NewClientTLSFromCert(nil, "")),
grpc.WithKeepaliveParams(keepalive.ClientParameters{
Time: 10 * time.Second, // send ping sau 10s idle
Timeout: 5 * time.Second, // timeout nếu không có pong
PermitWithoutStream: true,
}),
grpc.WithDefaultServiceConfig(`{"loadBalancingPolicy":"round_robin"}`),
)
defer conn.Close()
client := pb.NewChatServiceClient(conn)
Mặc định, gRPC fail ngay nếu channel không READY. grpc.WaitForReady(true) làm RPC đợi cho đến khi channel sẵn sàng (respects deadline). Hữu ích khi startup ordering không đảm bảo.
Metadata
Metadata là key-value pairs gửi kèm theo RPC — tương đương HTTP headers. Dùng để truyền auth tokens, tracing IDs, custom context không thuộc về message body.
// Client: gửi metadata
md := metadata.New(map[string]string{
"authorization": "Bearer " + token,
"x-request-id": requestID,
"x-trace-id": traceID,
})
ctx := metadata.NewOutgoingContext(context.Background(), md)
resp, err := client.SendMessage(ctx, req)
// Server: đọc metadata
func (s *Server) SendMessage(ctx context.Context, req *pb.Req) (*pb.Resp, error) {
md, ok := metadata.FromIncomingContext(ctx)
if !ok { return nil, status.Error(codes.Unauthenticated, "missing metadata") }
token := md.Get("authorization") // returns []string
...
}
Binary Metadata
Keys kết thúc bằng -bin được treat là binary — giá trị sẽ được base64 encoded/decoded tự động. Dùng để gửi arbitrary bytes.
md := metadata.Pairs("signature-bin", string(signatureBytes))
Trailing Metadata
Server có thể gửi metadata sau response (trailer) — thường dùng cho rate limit info, pagination tokens:
func (s *Server) GetData(ctx context.Context, req *pb.Req) (*pb.Resp, error) {
grpc.SetHeader(ctx, metadata.Pairs("x-request-id", id)) // initial metadata
grpc.SetTrailer(ctx, metadata.Pairs("x-ratelimit-remaining", "42")) // trailing
return resp, nil
}
Error Handling
gRPC dùng google.rpc.Status — một structured error với code (int32), message (string), và details (list of Any). 16 status codes được định nghĩa.
| Code | Name | HTTP equiv | Khi nào dùng |
|---|---|---|---|
| 0 | OK | 200 | Thành công |
| 1 | CANCELLED | 499 | Client đã cancel request |
| 2 | UNKNOWN | 500 | Lỗi không rõ, thường từ non-gRPC server |
| 3 | INVALID_ARGUMENT | 400 | Request data sai (validation) |
| 4 | DEADLINE_EXCEEDED | 504 | Deadline hết hạn trước khi hoàn thành |
| 5 | NOT_FOUND | 404 | Resource không tồn tại |
| 6 | ALREADY_EXISTS | 409 | Resource đã tồn tại |
| 7 | PERMISSION_DENIED | 403 | Không có quyền |
| 8 | RESOURCE_EXHAUSTED | 429 | Quota/rate limit exceeded |
| 9 | FAILED_PRECONDITION | 400 | Không đúng state (ví dụ: delete non-empty bucket) |
| 10 | ABORTED | 409 | Optimistic concurrency conflict |
| 11 | OUT_OF_RANGE | 400 | Value ngoài range hợp lệ |
| 12 | UNIMPLEMENTED | 501 | Method chưa implement |
| 13 | INTERNAL | 500 | Internal server error |
| 14 | UNAVAILABLE | 503 | Service tạm không khả dụng (retry-able) |
| 16 | UNAUTHENTICATED | 401 | Chưa authenticate |
// Return rich error with details (Go)
import "google.golang.org/genproto/googleapis/rpc/errdetails"
st, _ := status.New(codes.InvalidArgument, "invalid request").
WithDetails(&errdetails.BadRequest{
FieldViolations: []*errdetails.BadRequest_FieldViolation{
{Field: "email", Description: "must be a valid email address"},
},
})
return nil, st.Err()
// Client: extract details
if st, ok := status.FromError(err); ok {
for _, detail := range st.Details() {
switch d := detail.(type) {
case *errdetails.BadRequest:
for _, v := range d.FieldViolations { fmt.Println(v.Field, v.Description) }
}
}
}
UNKNOWN: trả về khi receive status code không recognize được — thường xảy ra khi gọi qua non-gRPC layer hoặc status không map được cross-language.
INTERNAL: server biết lỗi là internal/unexpected. Không bao giờ trả về INTERNAL cho client errors.
Deadlines & Cancellation
Mỗi gRPC call nên có một deadline — thời điểm tuyệt đối (absolute timestamp) mà call phải hoàn thành. Khác với timeout, deadline được propagate qua toàn call chain.
// Client: set deadline
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel() // luôn defer cancel để release resources
resp, err := client.GetUser(ctx, &pb.GetUserRequest{Id: 42})
// Server: check deadline trong long operations
func (s *Server) ProcessBatch(ctx context.Context, req *pb.BatchRequest) (*pb.BatchResponse, error) {
for i, item := range req.Items {
if err := ctx.Err(); err != nil {
return nil, status.FromContextError(err).Err() // DEADLINE_EXCEEDED or CANCELLED
}
if deadline, ok := ctx.Deadline(); ok {
if time.Until(deadline) < 100*time.Millisecond {
return nil, status.Error(codes.DeadlineExceeded, "insufficient time remaining")
}
}
processItem(item)
}
return resp, nil
}
Deadline Propagation
Khi service A gọi service B, deadline tự động được propagate qua grpc-timeout header. Service B "thấy" được deadline còn lại và có thể abort sớm nếu cần. Toàn bộ call tree hủy theo khi deadline hết.
Retry vs Hedging
Retry Policy
Gửi lại sau khi fail. Config trong service config JSON:
"retryPolicy": {
"maxAttempts": 3,
"initialBackoff": "0.1s",
"maxBackoff": "1s",
"backoffMultiplier": 2,
"retryableStatusCodes": ["UNAVAILABLE"]
}
Hedging Policy
Gửi song song N requests sau delay, lấy response đầu tiên về. Giảm tail latency nhưng tăng load:
"hedgingPolicy": {
"maxAttempts": 3,
"hedgingDelay": "0.05s",
"nonFatalStatusCodes": ["UNAVAILABLE"]
}
- Luôn set deadline trong production — không có deadline = potential goroutine leak
- Server phải poll
ctx.Err()trong long-running operations - Chỉ retry
UNAVAILABLEvàRESOURCE_EXHAUSTED(với backoff) - Không retry
INVALID_ARGUMENThayNOT_FOUND— sẽ fail lại
Interceptors
Interceptors là middleware cho gRPC — có thể inspect, modify, hoặc short-circuit requests/responses. Tương tự HTTP middleware nhưng typed và streaming-aware.
// Unary server interceptor (Go)
func AuthInterceptor(
ctx context.Context,
req any,
info *grpc.UnaryServerInfo,
handler grpc.UnaryHandler,
) (any, error) {
md, _ := metadata.FromIncomingContext(ctx)
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) // call the actual handler
}
// Register multiple interceptors (chained left to right)
grpc.NewServer(
grpc.ChainUnaryInterceptor(AuthInterceptor, LoggingInterceptor, MetricsInterceptor),
grpc.ChainStreamInterceptor(AuthStreamInterceptor, LoggingStreamInterceptor),
)
Library github.com/grpc-ecosystem/go-grpc-middleware cung cấp sẵn: auth (token validation), recovery (panic → INTERNAL), validator (proto-gen-validate), zap/logrus (logging), prometheus (metrics), opentelemetry (tracing).
Load Balancing
gRPC có những đặc điểm đặc biệt với load balancing: long-lived connections và streaming làm L4 LB (TCP) không hiệu quả — tất cả requests trên 1 connection đi vào 1 server. Cần L7 LB hoặc client-side LB.
LB Policies
| Policy | Behavior | Khi nào dùng |
|---|---|---|
pick_first | Gửi tất cả traffic đến subchannel đầu tiên READY | Dev/test, simple setups |
round_robin | Phân phối đều qua tất cả READY subchannels | Stateless services |
least_conn | Gửi đến subchannel ít active streams nhất | Workloads không đều |
| xDS (Envoy) | Dynamic từ xDS control plane (Istio) | Service mesh, advanced routing |
Mỗi pod chạy một Envoy sidecar — tự động inject bởi Istio. Tất cả traffic đi qua Envoy sidecar, cung cấp: mTLS, load balancing, circuit breaking, retry, observability (metrics/traces/logs) mà không cần thay đổi application code.
gRPC-Web, Transcoding & Production
gRPC-Web (Browser Support)
Browser không support HTTP/2 trailers (dùng để truyền gRPC status) và không thể gọi gRPC trực tiếp. gRPC-Web là protocol variant chạy trên HTTP/1.1, cần proxy (Envoy hoặc grpc-web middleware) để translate.
// envoy.yaml — grpc-web filter
http_filters:
- name: envoy.filters.http.grpc_web
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.http.grpc_web.v3.GrpcWeb
- name: envoy.filters.http.router
Hai mode: grpc-web (binary, Uint8Array) và grpc-web-text (base64-encoded, dùng khi binary gây vấn đề với middleware).
gRPC Gateway — REST Transcoding
grpc-gateway generate một HTTP/JSON reverse proxy từ proto annotations — server gRPC expose thêm REST API mà không cần viết thêm code:
import "google/api/annotations.proto";
service UserService {
rpc GetUser (GetUserRequest) returns (User) {
option (google.api.http) = {
get: "/v1/users/{user_id}" // → GET /v1/users/42
};
}
rpc CreateUser (CreateUserRequest) returns (User) {
option (google.api.http) = {
post: "/v1/users"
body: "*" // → POST /v1/users với JSON body
};
}
}
mTLS (Mutual TLS)
// Server với client cert requirement
creds, _ := credentials.NewServerTLSFromFile("server.crt", "server.key")
srv := grpc.NewServer(grpc.Creds(creds))
// Client với client cert
cert, _ := tls.LoadX509KeyPair("client.crt", "client.key")
tlsConf := &tls.Config{Certificates: []tls.Certificate{cert}}
creds := credentials.NewTLS(tlsConf)
conn, _ := grpc.NewClient(addr, grpc.WithTransportCredentials(creds))
Health Checking
gRPC định nghĩa grpc.health.v1.Health service (RFC) với 2 methods: Check (unary) và Watch (server streaming). Tích hợp với Kubernetes liveness/readiness probes qua grpc-health-probe CLI.
import "google.golang.org/grpc/health/grpc_health_v1"
healthServer := health.NewServer()
healthServer.SetServingStatus("ChatService", grpc_health_v1.HealthCheckResponse_SERVING)
grpc_health_v1.RegisterHealthServer(srv, healthServer)
Performance vs REST
| Benchmark | gRPC | REST/JSON | Notes |
|---|---|---|---|
| Payload size | ~30–60 bytes | ~150–300 bytes | Protobuf vs JSON cho cùng data |
| Serialization | ~100ns/msg | ~1–5µs/msg | Protobuf nhanh hơn 5-10x |
| Throughput (unary) | ~100k req/s | ~20k req/s | Single connection, same hardware |
| Latency (p99) | ~2ms | ~5ms | Local network |
| Connection overhead | Thấp (H2 multiplex) | Cao hơn (H1.1) | Nhiều concurrent requests |
Đăng ký reflection.Register(srv) cho phép tools như grpcurl, grpcui (Postman cho gRPC) discover services dynamically mà không cần .proto files — cực kỳ hữu ích cho debugging production.
grpcurl -plaintext localhost:50051 list
grpcurl -plaintext -d '{"user_id":"42"}' localhost:50051 users.v1.UserService/GetUser
Các điểm cốt lõi cần nhớ
Những điểm quan trọng nhất từ 16 sections — bản chắt lọc để review nhanh trước interview hoặc khi bắt đầu một gRPC project mới.
Protobuf & Wire Format
- Field numbers 1–15 = 1-byte tag → dùng cho fields xuất hiện nhiều (hot path fields)
- Wire types: 0=Varint, 1=64-bit, 2=LEN (string/bytes/message), 5=32-bit
- Tag =
(field_number << 3) | wire_type— field name không được encode - sint32/sint64 dùng ZigZag: giá trị âm nhỏ hơn int32/int64
- Proto3: không có null — field absent = default value (0, "", false, [])
4 Streaming Patterns
- Unary (req/resp): hầu hết use cases, đơn giản nhất
- Server streaming: 1 req → N resps — price feeds, log tailing, large dataset
- Client streaming: N reqs → 1 resp — file upload, sensor aggregation
- Bidirectional: N ↔ N — chat, realtime collaboration, game state
- Mỗi RPC = 1 HTTP/2 stream, multiplexed trên 1 TCP connection
Error Handling & Deadlines
- Dùng code cụ thể: NOT_FOUND, ALREADY_EXISTS, PERMISSION_DENIED, UNAVAILABLE
- UNKNOWN = lỗi không rõ nguồn; INTERNAL = server bug rõ ràng
google.rpc.Status.details: BadRequest, RetryInfo, ErrorInfo — structured errors- RetryInfo.retry_delay: server báo khi nào client được phép retry
- Deadline = absolute timestamp, bị trừ dần qua từng hop trong call chain
Channels & Metadata
- Channel ≠ 1 TCP connection — là logical abstraction với subchannel pool
- 5 states: IDLE → CONNECTING → READY → TRANSIENT_FAILURE → SHUTDOWN
- Initial metadata: gửi với request; trailing metadata: gửi sau response (server only)
- Key suffix
-bin→ value là binary (base64 encoded khi transmit) - Metadata = HTTP/2 headers → không truyền large payload qua metadata
Interceptors
UnaryServerInterceptor(ctx, req, info, handler) → (resp, error)ChainUnaryInterceptor(A, B, C): A là ngoài cùng — first in, last out- Phân biệt client interceptor (khi gửi request) vs server interceptor (khi nhận)
- Dùng cho: auth, logging, rate limiting, tracing (OpenTelemetry), panic recovery
- go-grpc-middleware: recovery, zap logging, grpc_auth, grpc_ratelimit
Production
- gRPC-Web: browser không hỗ trợ HTTP/2 trailer → Envoy encode trailer vào body
- grpc-gateway:
option (google.api.http)annotation → gen REST/JSON endpoint tự động - Health check:
grpc.health.v1.Health— Check + Watch, tích hợp k8s liveness/readiness - Server reflection:
reflection.Register(srv)→ grpcurl/grpcui discover service không cần .proto - mTLS: cả client + server cần cert, verify lẫn nhau — chuẩn cho microservices
- gRPC vs REST — khi nào dùng gRPC? — Internal microservices (performance, type safety, streaming); REST cho public API (browser-friendly, tooling phổ biến)
- Protobuf backward/forward compatibility? — Thêm field mới = OK (default value); xóa field = reuse field number là lỗi nghiêm trọng → dùng
reserved - Tại sao deadline tốt hơn timeout? — Deadline là absolute timestamp, propagate qua toàn call chain không bị cộng dồn; timeout relative bị cộng mỗi hop
- Khác nhau giữa metadata và request body? — Metadata = HTTP/2 headers (key-value, small), request body = serialized Protobuf message (có thể lớn)
- Tại sao gRPC-Web cần Envoy proxy? — Browser không cho phép đọc HTTP/2 trailer → Envoy chuyển trailing metadata vào body với frame marker riêng