Xử lý lỗi tạm thời với gRPC retries (thử lại)
gRPC retries (thử lại) là tính năng cho phép gRPC client tự động thử lại các lệnh gọi thất bại. Bài viết này thảo luận cách cấu hình retry policy (chính sách thử lại) để xây dựng các ứng dụng gRPC có khả năng phục hồi và chịu lỗi trong .NET.
gRPC retries yêu cầu Grpc.Net.Client phiên bản 2.36.0 trở lên.
Xử lý lỗi tạm thời
Các lệnh gọi gRPC có thể bị gián đoạn bởi các lỗi tạm thời. Lỗi tạm thời bao gồm:
- Mất kết nối mạng thoáng qua.
- Dịch vụ không khả dụng tạm thời.
- Timeout (hết thời gian chờ) do tải server.
Khi một lệnh gọi gRPC bị gián đoạn, client ném ra RpcException với chi tiết về lỗi. Ứng dụng client phải bắt exception và chọn cách xử lý lỗi.
var client = new Greeter.GreeterClient(channel);
try
{
var response = await client.SayHelloAsync(
new HelloRequest { Name = ".NET" });
Console.WriteLine("From server: " + response.Message);
}
catch (RpcException ex)
{
// Viết logic để kiểm tra lỗi và thử lại
// nếu lỗi xuất phát từ lỗi tạm thời.
}Việc sao chép logic retry khắp ứng dụng rất dài dòng và dễ gây lỗi. May mắn thay, gRPC client của .NET có hỗ trợ tích hợp sẵn cho tính năng tự động retry.
Cấu hình gRPC retry policy
Retry policy được cấu hình một lần khi tạo gRPC channel:
var defaultMethodConfig = new MethodConfig
{
Names = { MethodName.Default },
RetryPolicy = new RetryPolicy
{
MaxAttempts = 5,
InitialBackoff = TimeSpan.FromSeconds(1),
MaxBackoff = TimeSpan.FromSeconds(5),
BackoffMultiplier = 1.5,
RetryableStatusCodes = { StatusCode.Unavailable }
}
};
var channel = GrpcChannel.ForAddress("https://localhost:5001", new GrpcChannelOptions
{
ServiceConfig = new ServiceConfig { MethodConfigs = { defaultMethodConfig } }
});Đoạn code trên:
- Tạo một
MethodConfig. Retry policy có thể được cấu hình theo từng method và các method được khớp bằng thuộc tínhNames. Method này được cấu hình vớiMethodName.Default, vì vậy nó được áp dụng cho tất cả các method gRPC được gọi bởi channel này. - Cấu hình một retry policy. Policy này hướng dẫn client tự động thử lại các lệnh gọi gRPC thất bại với status code
Unavailable. - Cấu hình channel đã tạo để sử dụng retry policy bằng cách đặt
GrpcChannelOptions.ServiceConfig.
Các gRPC client được tạo với channel sẽ tự động thử lại các lệnh gọi thất bại:
var client = new Greeter.GreeterClient(channel);
var response = await client.SayHelloAsync(
new HelloRequest { Name = ".NET" });
Console.WriteLine("From server: " + response.Message);Khi nào retry hợp lệ
Các lệnh gọi được thử lại khi:
- Status code lỗi khớp với một giá trị trong
RetryableStatusCodes. - Số lần thử trước đó nhỏ hơn
MaxAttempts. - Lệnh gọi chưa bị committed (cam kết).
- Deadline chưa bị vượt quá.
Một lệnh gọi gRPC trở thành committed trong hai trường hợp:
- Client nhận được response headers. Response headers được server gửi khi
ServerCallContext.WriteResponseHeadersAsyncđược gọi, hoặc khi message đầu tiên được ghi vào server response stream. - Message gửi đi của client (hoặc nhiều message nếu là streaming) đã vượt quá kích thước buffer tối đa của client.
MaxRetryBufferSizevàMaxRetryBufferPerCallSizeđược cấu hình trên channel.
Các lệnh gọi đã committed sẽ không retry, bất kể status code hay số lần thử trước đó.
Các lệnh gọi streaming
Các lệnh gọi streaming có thể được sử dụng với gRPC retries, nhưng có những điểm quan trọng cần lưu ý khi dùng chúng cùng nhau:
- Server streaming, bidirectional streaming (truyền hai chiều): Các RPC streaming trả về nhiều message từ server sẽ không retry sau khi message đầu tiên được nhận. Ứng dụng phải thêm logic bổ sung để khởi tạo lại thủ công các lệnh gọi server streaming và bidirectional streaming.
- Client streaming, bidirectional streaming: Các RPC streaming gửi nhiều message đến server sẽ không retry nếu các message gửi đi đã vượt quá kích thước buffer tối đa của client. Kích thước buffer tối đa có thể được tăng lên thông qua cấu hình.
Để biết thêm thông tin, xem phần Khi nào retry hợp lệ.
Độ trễ backoff khi retry
Độ trễ backoff (thời gian chờ) giữa các lần retry được cấu hình với InitialBackoff, MaxBackoff và BackoffMultiplier. Thông tin chi tiết về từng tùy chọn có trong phần gRPC retry options.
Độ trễ thực tế giữa các lần retry được ngẫu nhiên hóa. Một độ trễ ngẫu nhiên giữa 0 và backoff hiện tại xác định khi nào lần retry tiếp theo được thực hiện. Lưu ý rằng ngay cả khi exponential backoff (backoff lũy thừa) được cấu hình, làm tăng backoff hiện tại giữa các lần thử, độ trễ thực tế giữa các lần thử không phải lúc nào cũng lớn hơn. Độ trễ được ngẫu nhiên hóa để ngăn các lần retry từ nhiều lệnh gọi tập trung lại với nhau và có thể làm quá tải server.
Phát hiện retry bằng metadata
gRPC retries có thể được phát hiện thông qua metadata grpc-previous-rpc-attempts. Metadata grpc-previous-rpc-attempts:
- Được tự động thêm vào các lệnh gọi retry và gửi đến server.
- Giá trị biểu thị số lần retry trước đó.
- Giá trị luôn là số nguyên.
Hãy xem xét kịch bản retry sau:
- Client thực hiện lệnh gọi gRPC đến server.
- Server thất bại và trả về response với status code có thể retry.
- Client thử lại lệnh gọi gRPC. Vì có một lần thử trước đó, metadata
grpc-previous-rpc-attemptscó giá trị là1. Metadata được gửi đến server cùng với lần retry. - Server thành công và trả về OK.
- Client báo cáo thành công.
grpc-previous-rpc-attemptscó trong metadata response và có giá trị là1.
Metadata grpc-previous-rpc-attempts không có trong lệnh gọi gRPC ban đầu, là 1 cho lần retry đầu tiên, 2 cho lần retry thứ hai, v.v.
Các tùy chọn gRPC retry
Bảng sau mô tả các tùy chọn để cấu hình gRPC retry policy:
| Tùy chọn | Mô tả |
|---|---|
MaxAttempts | Số lần thử tối đa, bao gồm lần thử ban đầu. Giá trị này bị giới hạn bởi GrpcChannelOptions.MaxRetryAttempts mặc định là 5. Giá trị bắt buộc và phải lớn hơn 1. |
InitialBackoff | Độ trễ backoff ban đầu giữa các lần retry. Một độ trễ ngẫu nhiên giữa 0 và backoff hiện tại xác định khi nào lần retry tiếp theo được thực hiện. Sau mỗi lần thử, backoff hiện tại được nhân với BackoffMultiplier. Giá trị bắt buộc và phải lớn hơn 0. |
MaxBackoff | Backoff tối đa đặt giới hạn trên cho sự tăng trưởng exponential backoff. Giá trị bắt buộc và phải lớn hơn 0. |
BackoffMultiplier | Backoff sẽ được nhân với giá trị này sau mỗi lần retry và sẽ tăng theo cấp số nhân khi hệ số nhân lớn hơn 1. Giá trị bắt buộc và phải lớn hơn 0. |
RetryableStatusCodes | Tập hợp các status code. Một lệnh gọi gRPC thất bại với status code khớp sẽ tự động được thử lại. Để biết thêm thông tin về status code, xem Status codes and their use in gRPC. Cần ít nhất một status code có thể retry. |
Hedging (gửi song song)
Hedging là một chiến lược retry thay thế. Hedging cho phép gửi tích cực nhiều bản sao của một lệnh gọi gRPC mà không cần chờ phản hồi. Các lệnh gọi gRPC được hedged có thể được thực thi nhiều lần trên server và kết quả thành công đầu tiên sẽ được sử dụng. Điều quan trọng là hedging chỉ nên được bật cho các method an toàn khi thực thi nhiều lần mà không có tác động xấu.
Hedging có ưu điểm và nhược điểm khi so sánh với retries:
- Ưu điểm của hedging là có thể trả về kết quả thành công nhanh hơn. Nó cho phép nhiều lệnh gọi gRPC đồng thời và sẽ hoàn thành khi kết quả thành công đầu tiên có sẵn.
- Nhược điểm của hedging là nó có thể lãng phí. Nhiều lệnh gọi có thể được thực hiện và tất cả đều thành công. Chỉ kết quả đầu tiên được sử dụng, còn lại bị loại bỏ.
Cấu hình gRPC hedging policy
Hedging policy được cấu hình giống như retry policy. Lưu ý rằng hedging policy không thể kết hợp với retry policy.
var defaultMethodConfig = new MethodConfig
{
Names = { MethodName.Default },
HedgingPolicy = new HedgingPolicy
{
MaxAttempts = 5,
NonFatalStatusCodes = { StatusCode.Unavailable }
}
};
var channel = GrpcChannel.ForAddress("https://localhost:5001", new GrpcChannelOptions
{
ServiceConfig = new ServiceConfig { MethodConfigs = { defaultMethodConfig } }
});Các tùy chọn gRPC hedging
Bảng sau mô tả các tùy chọn để cấu hình gRPC hedging policy:
| Tùy chọn | Mô tả |
|---|---|
MaxAttempts | Hedging policy sẽ gửi tối đa số lệnh gọi này. MaxAttempts biểu thị tổng số tất cả các lần thử, bao gồm cả lần thử ban đầu. Giá trị này bị giới hạn bởi GrpcChannelOptions.MaxRetryAttempts mặc định là 5. Giá trị bắt buộc và phải từ 2 trở lên. |
HedgingDelay | Lệnh gọi đầu tiên được gửi ngay lập tức, các lệnh gọi hedging tiếp theo bị trì hoãn theo giá trị này. Khi độ trễ được đặt thành 0 hoặc null, tất cả các lệnh gọi hedged được gửi ngay lập tức. HedgingDelay là tùy chọn và mặc định là 0. Giá trị phải từ 0 trở lên. |
NonFatalStatusCodes | Tập hợp các status code cho biết các lệnh gọi hedge khác vẫn có thể thành công. Nếu một non-fatal status code được server trả về, các lệnh gọi hedged sẽ tiếp tục. Nếu không, các request đang chờ sẽ bị hủy và lỗi được trả về cho ứng dụng. Để biết thêm thông tin về status code, xem Status codes and their use in gRPC. |