gRPC và Protocol Buffers trên .NET 10 — Giao tiếp microservices hiệu năng cao
Posted on: 4/18/2026 2:10:16 PM
Table of contents
- 1. gRPC là gì và tại sao nên dùng?
- 2. So sánh gRPC vs REST vs GraphQL
- 3. Protocol Buffers — Ngôn ngữ định nghĩa API
- 4. Bốn kiểu gRPC Communication
- 5. Triển khai gRPC trên .NET 10
- 6. Interceptors — Cross-cutting concerns
- 7. Health Checks và gRPC Reflection
- 8. Schema Evolution — Tiến hóa API an toàn
- 9. Kiến trúc gRPC trong hệ thống Microservices
- 10. Performance Tuning và Best Practices
- 11. gRPC-Web — Khi cần gọi từ Browser
- 12. Khi nào KHÔNG nên dùng gRPC?
- Kết luận
- Nguồn tham khảo
Trong thế giới microservices, việc các service "nói chuyện" với nhau nhanh và chính xác là yếu tố sống còn. REST với JSON đã phục vụ tốt nhiều năm, nhưng khi hệ thống scale lên hàng trăm service với hàng triệu request/giây, overhead của text-based protocol trở thành nút thắt cổ chai thực sự. gRPC — framework RPC hiệu năng cao của Google dựa trên HTTP/2 và Protocol Buffers — chính là giải pháp mà Netflix, Spotify, Square và hàng nghìn công ty đang sử dụng cho internal communication.
1. gRPC là gì và tại sao nên dùng?
gRPC (Google Remote Procedure Call) là một framework RPC mã nguồn mở, sử dụng HTTP/2 làm transport layer và Protocol Buffers (Protobuf) làm Interface Definition Language (IDL) kiêm format serialization. Thay vì gửi JSON text qua HTTP/1.1 như REST, gRPC truyền binary data qua HTTP/2 multiplexed streams — nhanh hơn, nhẹ hơn, và type-safe hơn.
flowchart LR
A["📱 gRPC Client
.NET / Go / Java"] -->|"HTTP/2 Binary Frame"| B["⚡ gRPC Server
ASP.NET Core"]
B -->|"Protobuf Response"| A
C["🌐 REST Client
Browser / Mobile"] -->|"HTTP/1.1 JSON Text"| D["🖥️ REST API
ASP.NET Core"]
D -->|"JSON Response"| C
style A fill:#e94560,stroke:#fff,color:#fff
style B fill:#e94560,stroke:#fff,color:#fff
style C fill:#f8f9fa,stroke:#2c3e50,color:#2c3e50
style D fill:#f8f9fa,stroke:#2c3e50,color:#2c3e50
Những lợi thế cốt lõi của gRPC:
- Contract-first API: Định nghĩa API bằng file
.prototrước, sau đó tự động sinh code cho cả client và server — đảm bảo type safety xuyên suốt. - HTTP/2 multiplexing: Nhiều request/response chạy song song trên cùng một TCP connection, không bị head-of-line blocking như HTTP/1.1.
- Binary serialization: Protobuf encode data thành binary nhỏ gọn, nhanh hơn JSON parsing đáng kể.
- Streaming native: Hỗ trợ server streaming, client streaming, và bidirectional streaming — không cần workaround như WebSocket hay SSE.
- Code generation: Tự động sinh strongly-typed client/server code cho hơn 10 ngôn ngữ từ cùng một file
.proto.
2. So sánh gRPC vs REST vs GraphQL
Mỗi protocol có điểm mạnh riêng. Bảng dưới so sánh chi tiết để giúp bạn chọn đúng công cụ cho đúng bài toán:
| Tiêu chí | gRPC | REST | GraphQL |
|---|---|---|---|
| Transport | HTTP/2 | HTTP/1.1 hoặc HTTP/2 | HTTP/1.1 hoặc HTTP/2 |
| Payload format | Protobuf (binary) | JSON (text) | JSON (text) |
| Contract | .proto file (strict) | OpenAPI/Swagger (loose) | Schema (strict) |
| Streaming | Bi-directional native | Không (cần SSE/WebSocket) | Subscriptions (cần WebSocket) |
| Browser support | Cần gRPC-Web proxy | Native | Native |
| Latency (P99) | ~25ms | ~120ms | ~95ms |
| Throughput | ~8,500 RPS | ~2,200 RPS | ~2,800 RPS |
| Best for | Service-to-service internal | Public API, CRUD | Frontend flexible queries |
Kiến trúc hybrid phổ biến nhất
Hầu hết các hệ thống production lớn đều dùng cả hai: REST hoặc GraphQL cho external API (browser, mobile) và gRPC cho internal service-to-service communication. API Gateway đóng vai trò "phiên dịch" giữa hai thế giới.
3. Protocol Buffers — Ngôn ngữ định nghĩa API
Protocol Buffers (Protobuf) là trái tim của gRPC. Bạn định nghĩa cấu trúc dữ liệu và service interface trong file .proto, sau đó compiler protoc tự động sinh code cho ngôn ngữ đích.
3.1. Cú pháp cơ bản
syntax = "proto3";
package ecommerce;
option csharp_namespace = "ECommerce.Grpc";
// Message definitions
message Product {
int32 id = 1;
string name = 2;
string description = 3;
double price = 4;
ProductCategory category = 5;
repeated string tags = 6;
optional string image_url = 7;
google.protobuf.Timestamp created_at = 8;
}
enum ProductCategory {
PRODUCT_CATEGORY_UNSPECIFIED = 0;
ELECTRONICS = 1;
CLOTHING = 2;
BOOKS = 3;
FOOD = 4;
}
message ProductFilter {
optional ProductCategory category = 1;
optional double min_price = 2;
optional double max_price = 3;
int32 page = 4;
int32 page_size = 5;
}
message ProductList {
repeated Product products = 1;
int32 total_count = 2;
}
// Service definition
service ProductService {
// Unary RPC
rpc GetProduct (GetProductRequest) returns (Product);
// Server streaming — server gửi nhiều response
rpc ListProducts (ProductFilter) returns (stream Product);
// Client streaming — client gửi nhiều request
rpc BulkCreateProducts (stream Product) returns (BulkCreateResponse);
// Bidirectional streaming
rpc SyncInventory (stream InventoryUpdate)
returns (stream InventoryStatus);
}
message GetProductRequest {
int32 id = 1;
}
message BulkCreateResponse {
int32 created_count = 1;
repeated string errors = 2;
}
message InventoryUpdate {
int32 product_id = 1;
int32 quantity_change = 2;
}
message InventoryStatus {
int32 product_id = 1;
int32 current_stock = 2;
bool low_stock_alert = 3;
}Một số quy tắc quan trọng khi viết Protobuf:
- Field number là vĩnh viễn: Số 1-15 dùng 1 byte encoding, 16-2047 dùng 2 bytes — ưu tiên 1-15 cho các field thường xuyên sử dụng.
- Không bao giờ reuse field number: Khi xóa field, dùng
reservedđể "khóa" số đó lại. repeatedcho array/list,optionalcho nullable field,map<K,V>cho dictionary.- Enum phải có giá trị 0: Convention là
UNSPECIFIED = 0để handle default value.
4. Bốn kiểu gRPC Communication
gRPC hỗ trợ 4 kiểu giao tiếp, mỗi kiểu phù hợp với một dạng bài toán khác nhau:
flowchart TB
subgraph U["1️⃣ Unary RPC"]
U1["Client"] -->|"1 Request"| U2["Server"]
U2 -->|"1 Response"| U1
end
subgraph SS["2️⃣ Server Streaming"]
SS1["Client"] -->|"1 Request"| SS2["Server"]
SS2 -->|"N Responses"| SS1
end
subgraph CS["3️⃣ Client Streaming"]
CS1["Client"] -->|"N Requests"| CS2["Server"]
CS2 -->|"1 Response"| CS1
end
subgraph BD["4️⃣ Bidirectional"]
BD1["Client"] -->|"N Requests"| BD2["Server"]
BD2 -->|"M Responses"| BD1
end
style U fill:#f8f9fa,stroke:#e94560
style SS fill:#f8f9fa,stroke:#4CAF50
style CS fill:#f8f9fa,stroke:#ff9800
style BD fill:#f8f9fa,stroke:#2c3e50
style U1 fill:#e94560,stroke:#fff,color:#fff
style U2 fill:#e94560,stroke:#fff,color:#fff
style SS1 fill:#4CAF50,stroke:#fff,color:#fff
style SS2 fill:#4CAF50,stroke:#fff,color:#fff
style CS1 fill:#ff9800,stroke:#fff,color:#fff
style CS2 fill:#ff9800,stroke:#fff,color:#fff
style BD1 fill:#2c3e50,stroke:#fff,color:#fff
style BD2 fill:#2c3e50,stroke:#fff,color:#fff
| Kiểu | Mô tả | Use case thực tế |
|---|---|---|
| Unary | 1 request → 1 response (giống REST) | CRUD operations, authentication, đọc/ghi đơn lẻ |
| Server Streaming | 1 request → N responses liên tục | Real-time feed, download file lớn, push notifications |
| Client Streaming | N requests → 1 response tổng hợp | Upload file chunked, batch insert, telemetry data |
| Bidirectional | N requests ↔ M responses đồng thời | Chat, game state sync, collaborative editing |
5. Triển khai gRPC trên .NET 10
5.1. Tạo gRPC Server
Trên .NET 10, gRPC server được tích hợp sẵn trong ASP.NET Core — chỉ cần thêm NuGet package và register service:
// Program.cs — gRPC Server
var builder = WebApplication.CreateBuilder(args);
// Thêm gRPC services
builder.Services.AddGrpc(options =>
{
options.MaxReceiveMessageSize = 16 * 1024 * 1024; // 16MB
options.MaxSendMessageSize = 16 * 1024 * 1024;
options.EnableDetailedErrors = builder.Environment.IsDevelopment();
});
// Health checks cho gRPC
builder.Services.AddGrpcHealthChecks()
.AddCheck("database", () => HealthCheckResult.Healthy());
// gRPC Reflection (cho dev tools như grpcurl)
builder.Services.AddGrpcReflection();
var app = builder.Build();
// Map gRPC services
app.MapGrpcService<ProductServiceImpl>();
app.MapGrpcHealthChecksService();
if (app.Environment.IsDevelopment())
{
app.MapGrpcReflectionService();
}
app.Run();5.2. Implement Service
// Services/ProductServiceImpl.cs
public class ProductServiceImpl : ProductService.ProductServiceBase
{
private readonly IProductRepository _repo;
private readonly ILogger<ProductServiceImpl> _logger;
public ProductServiceImpl(
IProductRepository repo,
ILogger<ProductServiceImpl> logger)
{
_repo = repo;
_logger = logger;
}
// Unary RPC
public override async Task<Product> GetProduct(
GetProductRequest request,
ServerCallContext context)
{
var product = await _repo.GetByIdAsync(
request.Id, context.CancellationToken);
if (product is null)
{
throw new RpcException(new Status(
StatusCode.NotFound,
$"Product {request.Id} not found"));
}
return MapToProto(product);
}
// Server Streaming — gửi từng product qua stream
public override async Task ListProducts(
ProductFilter request,
IServerStreamWriter<Product> responseStream,
ServerCallContext context)
{
var products = _repo.GetFilteredAsync(request);
await foreach (var product in products
.WithCancellation(context.CancellationToken))
{
await responseStream.WriteAsync(MapToProto(product));
}
}
// Client Streaming — nhận batch từ client
public override async Task<BulkCreateResponse> BulkCreateProducts(
IAsyncStreamReader<Product> requestStream,
ServerCallContext context)
{
var created = 0;
var errors = new List<string>();
await foreach (var proto in requestStream
.ReadAllAsync(context.CancellationToken))
{
try
{
await _repo.CreateAsync(MapFromProto(proto));
created++;
}
catch (Exception ex)
{
errors.Add($"Product '{proto.Name}': {ex.Message}");
}
}
return new BulkCreateResponse
{
CreatedCount = created,
Errors = { errors }
};
}
// Bidirectional Streaming — sync inventory realtime
public override async Task SyncInventory(
IAsyncStreamReader<InventoryUpdate> requestStream,
IServerStreamWriter<InventoryStatus> responseStream,
ServerCallContext context)
{
await foreach (var update in requestStream
.ReadAllAsync(context.CancellationToken))
{
var newStock = await _repo.UpdateStockAsync(
update.ProductId, update.QuantityChange);
await responseStream.WriteAsync(new InventoryStatus
{
ProductId = update.ProductId,
CurrentStock = newStock,
LowStockAlert = newStock < 10
});
}
}
}5.3. Tạo gRPC Client
// Client registration trong DI container
builder.Services.AddGrpcClient<ProductService.ProductServiceClient>(options =>
{
options.Address = new Uri("https://product-service:5001");
})
.ConfigurePrimaryHttpMessageHandler(() => new SocketsHttpHandler
{
PooledConnectionIdleTimeout = Timeout.InfiniteTimeSpan,
KeepAlivePingDelay = TimeSpan.FromSeconds(60),
KeepAlivePingTimeout = TimeSpan.FromSeconds(30),
EnableMultipleHttp2Connections = true
})
.AddInterceptor<ClientLoggingInterceptor>();
// Sử dụng client trong service khác
public class OrderService
{
private readonly ProductService.ProductServiceClient _productClient;
public OrderService(ProductService.ProductServiceClient productClient)
{
_productClient = productClient;
}
public async Task<OrderResult> CreateOrder(OrderRequest order)
{
// Unary call
var product = await _productClient.GetProductAsync(
new GetProductRequest { Id = order.ProductId });
// Server streaming — đọc danh sách products
var products = new List<Product>();
using var stream = _productClient.ListProducts(
new ProductFilter { Category = ProductCategory.Electronics });
await foreach (var p in stream.ResponseStream.ReadAllAsync())
{
products.Add(p);
}
return new OrderResult { /* ... */ };
}
}6. Interceptors — Cross-cutting concerns
Interceptors trong gRPC tương tự middleware trong ASP.NET Core — cho phép bạn xen vào pipeline xử lý request/response để thêm logging, authentication, metrics, retry logic mà không ảnh hưởng business logic.
flowchart LR
A["Client Request"] --> B["Auth
Interceptor"]
B --> C["Logging
Interceptor"]
C --> D["Metrics
Interceptor"]
D --> E["Service
Handler"]
E --> D
D --> C
C --> B
B --> F["Client Response"]
style A fill:#f8f9fa,stroke:#e94560,color:#2c3e50
style B fill:#e94560,stroke:#fff,color:#fff
style C fill:#4CAF50,stroke:#fff,color:#fff
style D fill:#ff9800,stroke:#fff,color:#fff
style E fill:#2c3e50,stroke:#fff,color:#fff
style F fill:#f8f9fa,stroke:#e94560,color:#2c3e50
6.1. Server Interceptor — Logging & Metrics
public class ServerLoggingInterceptor : Interceptor
{
private readonly ILogger<ServerLoggingInterceptor> _logger;
public ServerLoggingInterceptor(
ILogger<ServerLoggingInterceptor> logger) => _logger = logger;
public override async Task<TResponse> UnaryServerHandler<TRequest, TResponse>(
TRequest request,
ServerCallContext context,
UnaryServerMethod<TRequest, TResponse> continuation)
{
var sw = Stopwatch.StartNew();
var method = context.Method;
try
{
var response = await continuation(request, context);
sw.Stop();
_logger.LogInformation(
"gRPC {Method} completed in {ElapsedMs}ms",
method, sw.ElapsedMilliseconds);
return response;
}
catch (RpcException ex)
{
sw.Stop();
_logger.LogError(ex,
"gRPC {Method} failed with {StatusCode} in {ElapsedMs}ms",
method, ex.StatusCode, sw.ElapsedMilliseconds);
throw;
}
}
}
// Register trong Program.cs
builder.Services.AddGrpc(options =>
{
options.Interceptors.Add<ServerLoggingInterceptor>();
});6.2. Client Interceptor — Retry & Deadline
public class ClientRetryInterceptor : Interceptor
{
public override AsyncUnaryCall<TResponse> AsyncUnaryCall<TRequest, TResponse>(
TRequest request,
ClientInterceptorContext<TRequest, TResponse> context,
AsyncUnaryCallContinuation<TRequest, TResponse> continuation)
{
// Thêm deadline nếu chưa có
if (context.Options.Deadline == null)
{
var options = context.Options
.WithDeadline(DateTime.UtcNow.AddSeconds(30));
context = new ClientInterceptorContext<TRequest, TResponse>(
context.Method, context.Host, options);
}
return continuation(request, context);
}
}
// Hoặc dùng built-in retry policy (khuyến nghị)
builder.Services.AddGrpcClient<ProductService.ProductServiceClient>(o =>
{
o.Address = new Uri("https://product-service:5001");
})
.ConfigureChannel(o =>
{
o.ServiceConfig = new ServiceConfig
{
MethodConfigs =
{
new MethodConfig
{
Names = { MethodName.Default },
RetryPolicy = new RetryPolicy
{
MaxAttempts = 3,
InitialBackoff = TimeSpan.FromMilliseconds(500),
MaxBackoff = TimeSpan.FromSeconds(5),
BackoffMultiplier = 2,
RetryableStatusCodes =
{
StatusCode.Unavailable,
StatusCode.DeadlineExceeded
}
}
}
}
};
});7. Health Checks và gRPC Reflection
Trong môi trường production với Kubernetes hoặc load balancer, health checks là bắt buộc. gRPC có chuẩn health checking protocol riêng (grpc.health.v1.Health), và .NET 10 tích hợp sẵn với ASP.NET Core Health Checks system.
// Cấu hình Health Checks
builder.Services.AddGrpcHealthChecks()
.AddAsyncCheck("database", async ct =>
{
try
{
await using var conn = new SqlConnection(connectionString);
await conn.OpenAsync(ct);
return HealthCheckResult.Healthy();
}
catch (Exception ex)
{
return HealthCheckResult.Unhealthy(ex.Message);
}
})
.AddCheck<RedisHealthCheck>("redis");
// Map endpoint
app.MapGrpcHealthChecksService();
// Kubernetes liveness/readiness probe dùng grpc_health_probe:
// livenessProbe:
// grpc:
// port: 5001
// initialDelaySeconds: 5
// periodSeconds: 10gRPC Reflection — Dev tool không thể thiếu
gRPC Reflection cho phép client khám phá các service và method có sẵn trên server mà không cần file .proto. Kết hợp với grpcurl hoặc grpcui, bạn có thể test gRPC service giống như dùng Postman cho REST. Chỉ nên bật ở môi trường development.
# Cài grpcurl
# brew install grpcurl (macOS) hoặc scoop install grpcurl (Windows)
# List tất cả services
grpcurl -plaintext localhost:5001 list
# Describe một service
grpcurl -plaintext localhost:5001 describe ecommerce.ProductService
# Gọi Unary RPC
grpcurl -plaintext -d '{"id": 42}' \
localhost:5001 ecommerce.ProductService/GetProduct
# Server streaming
grpcurl -plaintext -d '{"category": "ELECTRONICS", "page_size": 10}' \
localhost:5001 ecommerce.ProductService/ListProducts8. Schema Evolution — Tiến hóa API an toàn
Một trong những thế mạnh lớn nhất của Protobuf là khả năng tiến hóa schema mà không break client/server đang chạy. Đây là yếu tố cực kỳ quan trọng trong microservices khi các service được deploy independent.
flowchart TD
A["v1: Product có 5 fields"] --> B{"Thêm field mới?"}
B -->|"✅ An toàn"| C["v2: Thêm optional field
với field number mới"]
B -->|"⚠️ Cẩn thận"| D["Đổi type field
cần compatible types"]
B -->|"❌ Nguy hiểm"| E["Xóa field hoặc
reuse field number"]
C --> F["Old client bỏ qua field mới
New client đọc được field mới"]
E --> G["Binary corruption
Data loss"]
style A fill:#f8f9fa,stroke:#e94560,color:#2c3e50
style B fill:#2c3e50,stroke:#fff,color:#fff
style C fill:#4CAF50,stroke:#fff,color:#fff
style D fill:#ff9800,stroke:#fff,color:#fff
style E fill:#e94560,stroke:#fff,color:#fff
style F fill:#f8f9fa,stroke:#4CAF50,color:#2c3e50
style G fill:#f8f9fa,stroke:#e94560,color:#2c3e50
Quy tắc vàng cho Schema Evolution
| Hành động | An toàn? | Giải thích |
|---|---|---|
| Thêm optional field mới | ✅ An toàn | Client cũ bỏ qua, client mới đọc được |
| Thêm repeated field mới | ✅ An toàn | Default là empty list |
| Đổi tên field | ✅ An toàn | Binary encoding dùng field number, không dùng tên |
| Xóa field + reserved number | ⚠️ Cẩn thận | Phải dùng reserved để khóa field number |
| Đổi int32 → int64 | ⚠️ Tương thích | int32 value vẫn đọc được trong int64 |
| Reuse field number đã xóa | ❌ KHÔNG | Gây corrupt data khi client cũ gửi data |
| Đổi string → int32 | ❌ KHÔNG | Incompatible wire types |
// Ví dụ schema evolution an toàn
message Product {
int32 id = 1;
string name = 2;
// Field 3 đã bị xóa — KHÓA lại
reserved 3;
reserved "old_description";
double price = 4;
ProductCategory category = 5;
repeated string tags = 6;
optional string image_url = 7;
// v2: Thêm fields mới với số mới
optional double discount_percent = 8;
optional string brand = 9;
// v3: Thêm sub-message
optional ProductDimensions dimensions = 10;
}
message ProductDimensions {
double weight_kg = 1;
double width_cm = 2;
double height_cm = 3;
double depth_cm = 4;
}Dùng Proto-Break trong CI/CD
Tích hợp tool buf breaking hoặc Proto-Break vào CI pipeline để tự động phát hiện breaking changes trước khi merge. Một breaking change lọt vào production có thể gây cascade failure trên toàn hệ thống microservices.
9. Kiến trúc gRPC trong hệ thống Microservices
Dưới đây là kiến trúc điển hình khi tích hợp gRPC vào hệ thống microservices trên .NET 10:
flowchart TB
subgraph External["External Clients"]
Web["🌐 Web App
(Vue.js)"]
Mobile["📱 Mobile App"]
end
subgraph Gateway["API Gateway Layer"]
GW["API Gateway
(YARP / Envoy)"]
end
subgraph Internal["Internal Services (gRPC)"]
PS["Product Service
gRPC + .NET 10"]
OS["Order Service
gRPC + .NET 10"]
IS["Inventory Service
gRPC + .NET 10"]
NS["Notification Service
gRPC + .NET 10"]
end
subgraph Infra["Infrastructure"]
DB1[("PostgreSQL")]
DB2[("PostgreSQL")]
MQ["Message Broker"]
end
Web -->|"REST / GraphQL"| GW
Mobile -->|"REST / GraphQL"| GW
GW -->|"gRPC"| PS
GW -->|"gRPC"| OS
OS -->|"gRPC"| PS
OS -->|"gRPC"| IS
OS -->|"gRPC"| NS
IS -->|"Bidirectional
Streaming"| PS
PS --> DB1
OS --> DB2
IS --> DB1
NS --> MQ
style Web fill:#f8f9fa,stroke:#e94560,color:#2c3e50
style Mobile fill:#f8f9fa,stroke:#e94560,color:#2c3e50
style GW fill:#e94560,stroke:#fff,color:#fff
style PS fill:#2c3e50,stroke:#fff,color:#fff
style OS fill:#2c3e50,stroke:#fff,color:#fff
style IS fill:#2c3e50,stroke:#fff,color:#fff
style NS fill:#2c3e50,stroke:#fff,color:#fff
style DB1 fill:#f8f9fa,stroke:#4CAF50,color:#2c3e50
style DB2 fill:#f8f9fa,stroke:#4CAF50,color:#2c3e50
style MQ fill:#f8f9fa,stroke:#ff9800,color:#2c3e50
10. Performance Tuning và Best Practices
10.1. Connection Management
// Tối ưu connection pool
builder.Services.AddGrpcClient<ProductService.ProductServiceClient>(o =>
{
o.Address = new Uri("https://product-service:5001");
})
.ConfigurePrimaryHttpMessageHandler(() => new SocketsHttpHandler
{
// Giữ connection alive — tránh TCP handshake lặp lại
PooledConnectionIdleTimeout = Timeout.InfiniteTimeSpan,
// HTTP/2 keep-alive ping
KeepAlivePingDelay = TimeSpan.FromSeconds(60),
KeepAlivePingTimeout = TimeSpan.FromSeconds(30),
// Cho phép nhiều HTTP/2 connection khi cần
EnableMultipleHttp2Connections = true,
// Connection lifetime — rotate để load balancing
PooledConnectionLifetime = TimeSpan.FromMinutes(5)
});10.2. Compression
// Server-side: enable Gzip compression
builder.Services.AddGrpc(options =>
{
options.ResponseCompressionAlgorithm = "gzip";
options.ResponseCompressionLevel = CompressionLevel.Optimal;
options.CompressionProviders = new List<ICompressionProvider>
{
new GzipCompressionProvider(CompressionLevel.Optimal)
};
});
// Client-side: gửi request với compression
var callOptions = new CallOptions(
writeOptions: new WriteOptions(WriteFlags.NoCompress)); // disable
// hoặc enable bằng channel option
var channel = GrpcChannel.ForAddress("https://server:5001",
new GrpcChannelOptions
{
CompressionProviders = new[]
{
new GzipCompressionProvider(CompressionLevel.Fastest)
}
});10.3. Deadline & Cancellation
Luôn set deadline cho mọi gRPC call
Không set deadline = chờ vô hạn. Trong microservices, một service bị treo sẽ kéo theo toàn bộ call chain bị block. Quy tắc: mỗi RPC call phải có deadline, và deadline phải được propagate qua các service downstream.
// Set deadline khi gọi
var deadline = DateTime.UtcNow.AddSeconds(5);
var product = await client.GetProductAsync(
new GetProductRequest { Id = 42 },
deadline: deadline);
// Propagate deadline trong server
public override async Task<OrderResult> CreateOrder(
CreateOrderRequest request,
ServerCallContext context)
{
// Dùng deadline từ caller, không tạo deadline mới dài hơn
var remainingTime = context.Deadline - DateTime.UtcNow;
var childDeadline = DateTime.UtcNow.Add(remainingTime * 0.8);
var product = await _productClient.GetProductAsync(
new GetProductRequest { Id = request.ProductId },
deadline: childDeadline);
// ...
}10.4. Checklist Best Practices
| Practice | Mô tả | Tầm quan trọng |
|---|---|---|
| Luôn set deadline | Mọi RPC call cần timeout rõ ràng | 🔴 Bắt buộc |
| Dùng CancellationToken | Truyền token qua toàn bộ async chain | 🔴 Bắt buộc |
| Retry policy | Retry cho Unavailable, DeadlineExceeded | 🔴 Bắt buộc |
| Health checks | Implement grpc.health.v1 cho K8s probes | 🔴 Bắt buộc |
| Interceptors cho logging | Log method, duration, status code | 🟡 Khuyến nghị |
| Compression | Gzip cho payload > 1KB | 🟡 Khuyến nghị |
| Connection pooling | Reuse connection, enable multiplexing | 🟡 Khuyến nghị |
| Buf/Proto-Break CI | Detect breaking schema changes | 🟡 Khuyến nghị |
| gRPC-Web proxy | Nếu cần browser access qua Envoy | 🟢 Tùy chọn |
| Reflection chỉ ở dev | Tắt reflection ở production | 🟢 Tùy chọn |
11. gRPC-Web — Khi cần gọi từ Browser
gRPC chuẩn không hoạt động trực tiếp trên browser vì browser không expose HTTP/2 framing ở tầng ứng dụng. gRPC-Web giải quyết vấn đề này bằng cách encode gRPC frames trong HTTP/1.1 hoặc HTTP/2 request thông thường.
// Server: enable gRPC-Web
builder.Services.AddGrpc();
var app = builder.Build();
app.UseGrpcWeb(); // Middleware chuyển đổi gRPC-Web ↔ gRPC
app.MapGrpcService<ProductServiceImpl>()
.EnableGrpcWeb(); // Enable cho từng service
// Client JavaScript/TypeScript (với @grpc/grpc-js hoặc connect-web)
// Hoặc dùng Envoy proxy để translate gRPC-Web → gRPCConnect Protocol — Alternative hiện đại
Nếu bạn cần browser compatibility tốt hơn, hãy xem xét Connect (connectrpc.com) — một protocol tương thích gRPC nhưng cũng hỗ trợ JSON over HTTP/1.1, giúp debug dễ hơn và hoạt động với mọi HTTP client mà không cần proxy đặc biệt.
12. Khi nào KHÔNG nên dùng gRPC?
gRPC không phải silver bullet. Dưới đây là những trường hợp bạn nên cân nhắc protocol khác:
- Public API cho third-party: REST với OpenAPI documentation dễ tiếp cận hơn nhiều — developer chỉ cần curl hoặc Postman.
- Browser-to-server đơn giản: Nếu không cần streaming, REST/GraphQL đơn giản hơn gRPC-Web + proxy.
- Team nhỏ, ít service: Overhead của Protobuf toolchain không worth it nếu chỉ có 2-3 services.
- Cần HTTP caching: gRPC không tận dụng được HTTP cache (CDN, reverse proxy) vì dùng POST cho mọi request.
- Payload chủ yếu là text/human-readable: Nếu cần inspect request/response thường xuyên, JSON dễ debug hơn binary.
Kết luận
gRPC trên .NET 10 mang lại sự kết hợp hoàn hảo giữa hiệu năng (binary serialization, HTTP/2 multiplexing), type safety (Protobuf contract), và developer experience (code generation, interceptors, health checks tích hợp sẵn). Với internal microservices communication — nơi mà latency thấp và throughput cao là yêu cầu then chốt — gRPC là lựa chọn tối ưu nhất hiện tại.
Bắt đầu bằng cách định nghĩa .proto file cho service quan trọng nhất, triển khai Unary RPC trước, sau đó mở rộng sang streaming khi có nhu cầu thực tế. Nhớ luôn set deadline, implement health checks, và thiết lập schema evolution rules ngay từ đầu — đó là nền tảng để hệ thống gRPC của bạn scale một cách bền vững.
Nguồn tham khảo
- Microsoft Learn — gRPC services with ASP.NET Core
- gRPC Official Documentation — Guides
- GitHub — grpc-dotnet repository
- Microsoft Learn — gRPC Interceptors on .NET
- Microsoft Learn — gRPC Health Checks in ASP.NET Core
- Microsoft Learn — Versioning gRPC services
- Calmops — gRPC vs REST Complete Comparison 2026
- Microservices with gRPC and Protocol Buffers for Scalability 2026
Disclaimer: The opinions expressed in this blog are solely my own and do not reflect the views or opinions of my employer or any affiliated organizations. The content provided is for informational and educational purposes only and should not be taken as professional advice. While I strive to provide accurate and up-to-date information, I make no warranties or guarantees about the completeness, reliability, or accuracy of the content. Readers are encouraged to verify the information and seek independent advice as needed. I disclaim any liability for decisions or actions taken based on the content of this blog.