Tuyệt vời! Dưới đây là bản nháp bài blog post bằng tiếng Việt, tập trung vào việc thêm khả năng chống chịu cho ứng dụng .NET với Polly, tuân thủ các yêu cầu bạn đã đặt ra.
Chào mừng các bạn quay trở lại với chuỗi bài viết về Lộ Trình học ASP.NET Core 2025! Trong các bài trước, chúng ta đã cùng nhau đi qua nhiều khía cạnh quan trọng, từ nền tảng C#, tìm hiểu Hệ Sinh Thái .NET, quản lý mã nguồn với Git, làm quen với HTTP/HTTPS, cho đến việc làm việc với dữ liệu qua SQL và Cơ sở dữ liệu quan hệ hay Entity Framework Core, xây dựng RESTful API, và nhiều hơn nữa.
Khi xây dựng các ứng dụng hiện đại, đặc biệt là các hệ thống phân tán (microservices) hoặc ứng dụng phụ thuộc vào các dịch vụ bên ngoài (third-party APIs, databases, message queues…), chúng ta phải đối mặt với một thực tế không thể tránh khỏi: thất bại.
Mạng có thể chập chờn, dịch vụ khác có thể tạm thời quá tải, cơ sở dữ liệu có thể gặp vấn đề kết nối trong giây lát, hoặc một API bên ngoài có thể trả về lỗi 5xx không mong muốn. Nếu ứng dụng của chúng ta không được chuẩn bị để xử lý những tình huống này một cách khéo léo, nó sẽ dễ dàng bị sập, gây ra trải nghiệm tồi tệ cho người dùng và ảnh hưởng đến toàn bộ hệ thống.
Đây chính là lúc khái niệm “khả năng chống chịu” (resilience) trở nên cực kỳ quan trọng. Một ứng dụng có khả năng chống chịu tốt không có nghĩa là nó không bao giờ gặp lỗi, mà là nó có thể xử lý các lỗi tạm thời một cách graceful, tự phục hồi và tiếp tục hoạt động ổn định nhất có thể.
Trong bài viết này, chúng ta sẽ tìm hiểu về Polly – một thư viện .NET mạnh mẽ giúp chúng ta dễ dàng thêm khả năng chống chịu và xử lý lỗi tạm thời (transient fault handling) vào ứng dụng của mình.
Mục lục
Khả Năng Chống Chịu Là Gì Và Tại Sao Chúng Ta Cần Nó?
Trong bối cảnh phát triển phần mềm, khả năng chống chịu (resilience) là khả năng của một hệ thống đối phó với các lỗi (failures) và vẫn tiếp tục cung cấp dịch vụ ở một mức độ chấp nhận được. Các lỗi này có thể là:
- Lỗi tạm thời (Transient faults): Những lỗi chỉ xảy ra trong một thời gian ngắn và có thể tự khắc phục nếu thử lại sau một khoảng thời gian. Ví dụ: mất kết nối mạng trong giây lát, server bị quá tải trong chốc lát, deadlock cơ sở dữ liệu ngắn hạn.
- Lỗi kéo dài (Persistent faults): Những lỗi không thể tự khắc phục bằng cách thử lại ngay lập tức. Ví dụ: dịch vụ ngoại bộ bị sập hoàn toàn, lỗi logic trong code, sai cấu hình.
Trong thế giới cloud, microservices và kết nối API, ứng dụng của chúng ta thường xuyên gọi đến các dịch vụ khác qua mạng. Mạng không phải lúc nào cũng hoàn hảo, các dịch vụ khác có thể có vấn đề. Nếu mỗi lần gọi dịch vụ bị lỗi tạm thời mà ứng dụng của chúng ta sập hoặc trả về lỗi cho người dùng, thì hệ thống sẽ rất mong manh.
Ví dụ, khi bạn gọi một API ngoại bộ để lấy dữ liệu, hoặc tương tác với cơ sở dữ liệu, một lỗi kết nối tạm thời có thể xảy ra. Thay vì để request thất bại, chúng ta có thể thử lại sau vài giây. Nếu thử lại thành công, người dùng sẽ không hề nhận biết được vấn đề gì đã xảy ra. Đó chính là khả năng chống chịu.
Tuy nhiên, việc thử lại một cách “mù quáng” cũng có thể gây hại. Nếu dịch vụ đích đang thực sự bị sập (lỗi kéo dài), việc liên tục thử lại chỉ làm tăng thêm áp lực lên dịch vụ đó và lãng phí tài nguyên của chính ứng dụng chúng ta. Lúc này, chúng ta cần một chiến lược khác.
Giới Thiệu về Polly
Polly là một thư viện mã nguồn mở (.NET) cung cấp một bộ sưu tập các chính sách (policies) để xử lý các trường hợp lỗi tạm thời và tăng cường khả năng chống chịu. Polly giúp chúng ta định nghĩa cách ứng dụng nên hành xử khi gặp lỗi, ví dụ như:
- Thử lại hoạt động sau khi gặp lỗi.
- Ngắt mạch (circuit break) để dừng việc gửi yêu cầu tới một dịch vụ đang bị lỗi liên tục.
- Giới hạn thời gian chờ (timeout) cho một hoạt động.
- Giới hạn số lượng yêu cầu đồng thời (bulkhead).
- Cung cấp giá trị dự phòng (fallback) khi hoạt động chính thất bại.
Sức mạnh của Polly nằm ở việc nó trừu tượng hóa logic xử lý lỗi phức tạp, cho phép chúng ta định nghĩa các chính sách này một cách rõ ràng và áp dụng chúng một cách linh hoạt cho bất kỳ đoạn code nào có khả năng thất bại (ví dụ: gọi API, truy vấn database, gọi message queue…).
Polly hoạt động dựa trên nguyên tắc “Policy Wrap”: bạn định nghĩa một hoặc nhiều chính sách, sau đó “bao bọc” đoạn code có khả năng thất bại bằng chính sách đó. Khi code thực thi và gặp lỗi được chính sách định nghĩa, Polly sẽ can thiệp và xử lý theo logic của chính sách.
Các Chính Sách Polly Phổ Biến
Polly cung cấp nhiều loại chính sách khác nhau, mỗi loại phục vụ một mục đích cụ thể. Dưới đây là những chính sách thường dùng nhất:
Chính Sách Retry (Thử Lại)
Chính sách Retry là cơ chế đơn giản nhất và thường được áp dụng cho các lỗi tạm thời. Nó sẽ tự động thử lại hoạt động bị lỗi một số lần nhất định hoặc cho đến khi thành công.
Có nhiều biến thể của Retry:
- Retry cố định (Fixed Interval Retry): Thử lại sau một khoảng thời gian chờ cố định giữa các lần thử.
- Retry tuyến tính (Linear Retry): Tăng khoảng thời gian chờ giữa các lần thử theo một hằng số.
- Retry lũy thừa với Jitter (Exponential Backoff with Jitter): Tăng khoảng thời gian chờ theo cấp số nhân (ví dụ: 2s, 4s, 8s…) và thêm một yếu tố ngẫu nhiên (jitter). Jitter giúp tránh “cơn bão thử lại” (retry storm) khi nhiều instance cùng gặp lỗi và thử lại đồng thời vào cùng một thời điểm. Đây là chiến lược Retry được khuyến khích nhất.
Ví dụ cấu hình Retry với exponential backoff và jitter:
using Polly;
using Polly.Retry;
using System.Net.Http;
using System;
using System.Threading.Tasks;
// Định nghĩa chính sách Retry:
// - Xử lý HttpRequestException hoặc bất kỳ exception nào
// - Hoặc xử lý các HttpStatusCode từ 500 trở lên (Internal Server Error, Service Unavailable, Gateway Timeout)
// - Thử lại tối đa 5 lần
// - Thời gian chờ giữa các lần thử tăng theo cấp số nhân (2^retryAttempt) và thêm jitter ngẫu nhiên
RetryPolicy<HttpResponseMessage> retryPolicy = Policy
.Handle<HttpRequestException>()
.OrResult(response => (int)response.StatusCode >= 500) // Handle 5xx status codes
.WaitAndRetryAsync(
retryCount: 5,
sleepDurationProvider: retryAttempt =>
TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)) + TimeSpan.FromMilliseconds(new Random().Next(0, 1000)), // Exponential backoff + Jitter
onRetry: (outcome, timespan, retryAttempt, context) =>
{
// Log thông tin về lần thử lại (sử dụng ILogger thực tế trong ứng dụng)
Console.WriteLine($"Retry {retryAttempt} after {timespan.TotalSeconds:N1}s due to {outcome.Exception?.Message ?? outcome.Result?.StatusCode.ToString()}. Context: {context.OperationKey}");
});
// Cách sử dụng chính sách Retry
public async Task<string> GetDataAsync(HttpClient httpClient)
{
// Thực thi hoạt động trong phạm vi chính sách Retry
HttpResponseMessage response = await retryPolicy.ExecuteAsync(() =>
httpClient.GetAsync("https://api.example.com/data"));
response.EnsureSuccessStatusCode(); // Ném exception nếu mã trạng thái không thành công
return await response.Content.ReadAsStringAsync();
}
Chính Sách Circuit Breaker (Ngắt Mạch)
Chính sách Circuit Breaker được sử dụng để ngăn chặn ứng dụng liên tục cố gắng gọi đến một dịch vụ đã được xác định là đang gặp sự cố nghiêm trọng. Giống như cầu dao điện, khi phát hiện quá nhiều lỗi liên tục, Circuit Breaker sẽ “ngắt mạch” và chuyển sang trạng thái Open. Trong trạng thái Open, mọi yêu cầu tiếp theo sẽ bị Polly chặn lại ngay lập tức mà không cần gọi đến dịch vụ đích, cho phép dịch vụ đích có thời gian phục hồi và ngăn chặn “cơn bão” yêu cầu làm trầm trọng thêm tình hình.
Circuit Breaker có 3 trạng thái chính:
- Closed: Hoạt động bình thường, cho phép các yêu cầu đi qua. Nếu số lượng lỗi liên tiếp vượt quá ngưỡng cấu hình, nó sẽ chuyển sang trạng thái Open.
- Open: Ngắt mạch, chặn tất cả các yêu cầu và ném ra ngoại lệ
CircuitBreakerOpenException ngay lập tức. Sau một khoảng thời gian cấu hình (durationOfBreak), nó sẽ tự động chuyển sang trạng thái Half-Open. - Half-Open: Cho phép một số lượng nhỏ yêu cầu đi qua để kiểm tra xem dịch vụ đích đã phục hồi chưa. Nếu các yêu cầu này thành công, nó chuyển về trạng thái Closed. Nếu chúng vẫn thất bại, nó quay trở lại trạng thái Open.
Chính sách Circuit Breaker thường được áp dụng cho các lỗi kéo dài hơn hoặc sau khi chính sách Retry đã thử hết số lần cho phép mà vẫn thất bại.
Ví dụ cấu hình Circuit Breaker:
using Polly;
using Polly.CircuitBreaker;
using System.Net.Http;
using System;
using System.Threading.Tasks;
// Định nghĩa chính sách Circuit Breaker:
// - Xử lý HttpRequestException
// - Nếu có 5 lỗi liên tiếp xảy ra...
// - ...thì ngắt mạch trong 30 giây
CircuitBreakerPolicy<HttpResponseMessage> circuitBreakerPolicy = Policy
.Handle<HttpRequestException>()
.CircuitBreakerAsync(
failureThreshold: 5, // Số lỗi liên tiếp để ngắt mạch
durationOfBreak: TimeSpan.FromSeconds(30), // Thời gian ngắt mạch
onBreak: (exception, breakDelay) =>
{
Console.WriteLine($"Circuit breaker opening for {breakDelay.TotalSeconds:N1}s due to {exception.Message}");
},
onReset: () =>
{
Console.WriteLine("Circuit breaker closing");
},
onHalfOpen: () =>
{
Console.WriteLine("Circuit breaker half-opening");
});
// Cách sử dụng chính sách Circuit Breaker
public async Task<string> GetDataCircuitBreakerAsync(HttpClient httpClient)
{
try
{
HttpResponseMessage response = await circuitBreakerPolicy.ExecuteAsync(() =>
httpClient.GetAsync("https://api.example.com/data-unstable"));
response.EnsureSuccessStatusCode();
return await response.Content.ReadAsStringAsync();
}
catch (CircuitBreakerOpenException cbex)
{
Console.WriteLine($"Request blocked by circuit breaker: {cbex.Message}");
// Có thể trả về lỗi cho người dùng hoặc dùng Fallback policy
throw; // Rethrow hoặc xử lý khác
}
}
Chính Sách Timeout (Hết Giờ)
Chính sách Timeout giúp giới hạn thời gian cho một hoạt động hoàn thành. Nếu hoạt động vượt quá thời gian quy định, Polly sẽ hủy bỏ nó và ném ra ngoại lệ
Polly hỗ trợ hai loại Timeout:
- Timeout lạc quan (Optimistic Timeout): Theo dõi CancellationToken để hủy bỏ hoạt động. Yêu cầu hoạt động được bọc phải tôn trọng CancellationToken.
- Timeout bi quan (Pessimistic Timeout): Tạo một thread riêng để hủy bỏ hoạt động nếu hết giờ, ngay cả khi hoạt động đó không tôn trọng CancellationToken. Thường có chi phí lớn hơn.
Timeout lạc quan được khuyến khích sử dụng khi có thể.
Ví dụ cấu hình Timeout:
using Polly;
using Polly.Timeout;
using System;
using System.Threading.Tasks;
// Định nghĩa chính sách Timeout:
// - Nếu hoạt động mất hơn 10 giây, hủy bỏ nó (Optimistic)
TimeoutPolicy timeoutPolicy = Policy.Timeout(TimeSpan.FromSeconds(10));
// Cách sử dụng chính sách Timeout
public async Task PerformQuickOperationAsync()
{
try
{
await timeoutPolicy.ExecuteAsync(async () =>
{
// Hoạt động có thể mất thời gian
await Task.Delay(5000); // Ví dụ: Hoạt động mất 5 giây (OK)
// await Task.Delay(15000); // Ví dụ: Hoạt động mất 15 giây (sẽ bị timeout)
Console.WriteLine("Operation completed within timeout.");
});
}
catch (TimeoutRejectedException)
{
Console.WriteLine("Operation timed out!");
// Xử lý khi hoạt động bị hết giờ
}
}
Các Chính Sách Khác
Polly còn cung cấp các chính sách hữu ích khác:
- Bulkhead Isolation: Giới hạn số lượng request đồng thời đến một tài nguyên cụ thể (ví dụ: một database, một API). Điều này giống như các khoang ngăn nước trên tàu thủy, giúp cô lập lỗi ở một phần hệ thống và ngăn nó lan ra làm sập toàn bộ. Ngăn chặn quá tải cho dịch vụ đích.
- Fallback: Cung cấp một giá trị hoặc hành động dự phòng khi hoạt động chính gặp lỗi. Ví dụ: trả về dữ liệu từ cache khi gọi API thất bại, hoặc trả về một giá trị mặc định.
- Rate Limit: Giới hạn số lượng request trong một khoảng thời gian nhất định, giúp tuân thủ giới hạn của các API ngoại bộ hoặc bảo vệ tài nguyên nội bộ khỏi bị tấn công từ chối dịch vụ (Denial of Service).
Tích Hợp Polly với HttpClientFactory trong ASP.NET Core
Trong các ứng dụng ASP.NET Core hiện đại, cách tốt nhất để quản lý và sử dụng
Bạn có thể cấu hình các chính sách Polly và gán chúng cho các named
Ví dụ cấu hình trong file
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using System;
using System.Net.Http;
using Polly;
using Polly.Extensions.Http;
var builder = Host.CreateDefaultBuilder(args); // Hoặc WebApplication.CreateBuilder(args)
builder.ConfigureServices((hostContext, services) =>
{
// Định nghĩa chính sách Retry HTTP:
// - Xử lý các lỗi network (HttpRequestException)
// - Hoặc các mã trạng thái 5xx, 408 (Request Timeout)
// - Thử lại 5 lần với exponential backoff và jitter
var retryPolicy = HttpPolicyExtensions
.HandleTransientHttpError() // Handles HttpRequestException, 5xx and 408
.WaitAndRetryAsync(5, retryAttempt =>
TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)) + TimeSpan.FromMilliseconds(new Random().Next(0, 500))
);
// Định nghĩa chính sách Circuit Breaker HTTP:
// - Xử lý các lỗi network hoặc mã trạng thái 5xx, 408
// - Nếu có 3 lỗi liên tiếp...
// - ...thì ngắt mạch trong 15 giây
var circuitBreakerPolicy = HttpPolicyExtensions
.HandleTransientHttpError()
.CircuitBreakerAsync(
handledEventsAllowedBeforeBreaking: 3,
durationOfBreak: TimeSpan.FromSeconds(15)
);
// Đăng ký HttpClient với HttpClientFactory và gán chính sách
services.AddHttpClient("MyExternalApi", client =>
{
client.BaseAddress = new Uri("https://api.external.com/");
client.Timeout = TimeSpan.FromSeconds(20); // Áp dụng Timeout ở đây
})
// Áp dụng chính sách Polly. Chính sách thứ nhất (Retry) sẽ được thực thi trước,
// nếu Retry không thành công hoặc gặp lỗi không tạm thời, chính sách thứ hai (Circuit Breaker) sẽ can thiệp.
// Thứ tự thêm chính sách vào AddPolicyHandler QUAN TRỌNG.
// Các chính sách được thêm VỀ SAU sẽ bao bọc các chính sách được thêm TRƯỚC.
// Retry thường nằm bên trong Circuit Breaker.
.AddPolicyHandler(retryPolicy)
.AddPolicyHandler(circuitBreakerPolicy); // Đây là cách sai thứ tự cho Retry & CB.
// Đúng thứ tự là: Circuit Breaker bên ngoài Retry.
// Hãy sửa lại:
// .AddPolicyHandler(circuitBreakerPolicy)
// .AddPolicyHandler(retryPolicy);
// Polices are applied in the order they are specified here.
// The policy added *last* wraps the policy added *first*.
// So, to have CB wrap Retry, Retry must be added first.
// Cấu hình lại với thứ tự đúng: Retry bên trong Circuit Breaker
services.AddHttpClient("MyExternalApiCorrectOrder", client =>
{
client.BaseAddress = new Uri("https://api.external.com/");
client.Timeout = TimeSpan.FromSeconds(20);
})
.AddPolicyHandler(retryPolicy) // Retry policy
.AddPolicyHandler(circuitBreakerPolicy); // Circuit Breaker policy wraps Retry
// Request -> Circuit Breaker -> Retry -> Outgoing HTTP Call
// Ví dụ với Timeout kết hợp Retry và Circuit Breaker
var totalTimeoutPolicy = Policy.TimeoutAsync<HttpResponseMessage>(TimeSpan.FromSeconds(30)); // Tổng thời gian cho toàn bộ quá trình (bao gồm cả retry)
services.AddHttpClient("ApiWithTimeoutRetryCircuitBreaker", client =>
{
client.BaseAddress = new Uri("https://api.external.com/");
// Client-level timeout không ảnh hưởng đến Polly Timeout Policy
})
.AddPolicyHandler(retryPolicy)
.AddPolicyHandler(circuitBreakerPolicy)
.AddPolicyHandler(totalTimeoutPolicy); // Timeout policy wraps everything
// Request -> Timeout -> Circuit Breaker -> Retry -> Outgoing HTTP Call
});
// ... Phần còn lại của Program.cs
</pre>
<p>Sau khi cấu hình, bạn chỉ cần inject <c>IHttpClientFactory</c> vào service hoặc controller của mình và tạo <c>HttpClient</c> theo tên đã đăng ký. Các chính sách Polly sẽ tự động được áp dụng mỗi khi bạn thực hiện một HTTP request bằng client đó.</p>
<pre><code class="language-csharp">
using System.Net.Http;
using System.Threading.Tasks;
using Microsoft.Extensions.HttpClientFactory; // Thay vì System.Net.Http.HttpClientFactory
public class ExternalApiService
{
private readonly HttpClient _httpClient;
// Sử dụng Constructor Injection để nhận IHttpClientFactory
public ExternalApiService(IHttpClientFactory httpClientFactory)
{
// Tạo client theo tên đã cấu hình
_httpClient = httpClientFactory.CreateClient("MyExternalApiCorrectOrder");
}
public async Task<string> GetExternalDataAsync()
{
// Gọi API. Polly policies sẽ tự động được áp dụng.
HttpResponseMessage response = await _httpClient.GetAsync("/resource");
response.EnsureSuccessStatusCode(); // Vẫn kiểm tra mã trạng thái thành công
return await response.Content.ReadAsStringAsync();
}
}
Kết Hợp Các Chính Sách (Policy Composition)
Một trong những tính năng mạnh mẽ của Polly là khả năng kết hợp nhiều chính sách lại với nhau theo một thứ tự nhất định. Điều này được gọi là Policy Composition hoặc Policy Wrap.
Ví dụ, bạn có thể muốn áp dụng chính sách Timeout *trước* chính sách Retry, và cả hai đều được bao bọc bởi chính sách Circuit Breaker. Thứ tự này quan trọng:
- Khi yêu cầu được gửi đi, nó đi qua chính sách ngoài cùng trước (Circuit Breaker).
- Circuit Breaker kiểm tra trạng thái. Nếu Open, nó chặn ngay. Nếu Closed hoặc Half-Open, nó cho phép yêu cầu đi vào chính sách tiếp theo (Timeout).
- Chính sách Timeout bắt đầu đếm giờ. Nếu hoạt động bọc nó (Retry policy + HTTP call) hoàn thành trong thời gian cho phép, OK. Nếu hết giờ, Timeout ngắt và ném lỗi.
- Nếu hoạt động bọc Timeout (tức là Circuit Breaker) cho phép, nó đi vào chính sách Retry.
- Chính sách Retry thực hiện HTTP call. Nếu gặp lỗi tạm thời, Retry sẽ thử lại theo logic của nó. Các lần thử lại này vẫn nằm trong phạm vi theo dõi của Timeout và Circuit Breaker.
Cú pháp để kết hợp chính sách là sử dụng
using Polly;
using Polly.Timeout;
using Polly.Retry;
using Polly.CircuitBreaker;
using System;
using System.Net.Http;
using System.Threading.Tasks;
// Định nghĩa các chính sách riêng lẻ
var retryPolicy = Policy
.Handle<HttpRequestException>()
.WaitAndRetryAsync(3, attempt => TimeSpan.FromSeconds(Math.Pow(2, attempt)));
var circuitBreakerPolicy = Policy
.Handle<HttpRequestException>()
.CircuitBreakerAsync(5, TimeSpan.FromSeconds(30));
var timeoutPolicy = Policy.TimeoutAsync(TimeSpan.FromSeconds(10)); // Timeout cho MỖI lần thử
// Kết hợp chính sách: Circuit Breaker -> Retry -> Timeout -> Hoạt động gốc
// Chính sách cuối cùng trong WrapAsync là chính sách bọc ngoài cùng
var combinedPolicy = Policy.WrapAsync(circuitBreakerPolicy, retryPolicy, timeoutPolicy); // SAI THỨ TỰ WRAP
// Cú pháp Wrap: Policy.WrapAsync(policy_ngoai_cung_2, policy_ngoai_cung_1, policy_ben_trong_nhat)
// Thứ tự thực thi: policy_ngoai_cung_2 -> policy_ngoai_cung_1 -> policy_ben_trong_nhat -> Hoạt động gốc
// Để có thứ tự Circuit Breaker -> Retry -> Timeout -> Hoạt động gốc, chúng ta cần wrap như sau:
// Policy.WrapAsync(Circuit Breaker, Retry bao bọc Timeout)
var retryAndTimeoutPolicy = Policy.WrapAsync(retryPolicy, timeoutPolicy);
var finalCombinedPolicy = Policy.WrapAsync(circuitBreakerPolicy, retryAndTimeoutPolicy);
// Cách sử dụng:
public async Task ExecuteCombinedPolicyAsync(HttpClient httpClient)
{
try
{
await finalCombinedPolicy.ExecuteAsync(async () =>
{
// Hoạt động có khả năng thất bại
await httpClient.GetAsync("https://api.example.com/unstable");
});
Console.WriteLine("Operation completed successfully with combined policy.");
}
catch (Exception ex)
{
Console.WriteLine($"Operation failed after applying combined policy: {ex.Message}");
// Xử lý các lỗi cuối cùng sau khi tất cả chính sách đã thử
}
}
// LƯU Ý QUAN TRỌNG KHI WRAP:
// - `Policy.WrapAsync(outerPolicy1, outerPolicy2, innerPolicy)`: Thứ tự thực thi là outerPolicy1 -> outerPolicy2 -> innerPolicy -> Hoạt động.
// - Trong HttpClientFactory, thứ tự thêm PolicyHandler quan trọng hơn.
// `AddPolicyHandler(policyA).AddPolicyHandler(policyB)` => policyB bọc policyA. Thứ tự thực thi: policyB -> policyA -> HTTP Call.
// Để Circuit Breaker bọc Retry, bạn thêm Retry trước rồi mới thêm Circuit Breaker.
// `AddPolicyHandler(retryPolicy).AddPolicyHandler(circuitBreakerPolicy)` => Đúng thứ tự trong HttpClientFactory.
Như bạn thấy,
Xử Lý Sự Kiện (Events) của Chính Sách
Việc hiểu rõ điều gì đang xảy ra với các chính sách chống chịu là rất quan trọng cho việc giám sát và debug. Polly cung cấp các event (sự kiện) mà bạn có thể đăng ký để nhận thông báo khi chính sách hoạt động.
onRetry : Khi chính sách Retry chuẩn bị thực hiện thử lại. Cung cấp thông tin về lỗi, thời gian chờ, lần thử hiện tại.onBreak : Khi Circuit Breaker chuyển sang trạng thái Open. Cung cấp thông tin về lỗi gây ra việc ngắt mạch, thời gian ngắt.onReset : Khi Circuit Breaker chuyển từ Half-Open về Closed.onHalfOpen : Khi Circuit Breaker chuyển từ Open về Half-Open.onTimeout : Khi hoạt động bị hết giờ.onFallback : Khi chính sách Fallback được kích hoạt.
Bạn nên sử dụng các event này để ghi log (log) thông tin chi tiết. Việc ghi log giúp bạn theo dõi tần suất các lỗi tạm thời xảy ra, Circuit Breaker có đang mở hay không, các yêu cầu có bị hết giờ không… Điều này cực kỳ hữu ích khi vận hành ứng dụng trong môi trường Production.
Bạn có thể tích hợp với các thư viện ghi log phổ biến trong .NET như Serilog hoặc NLog để ghi lại các sự kiện này.
Ví dụ thêm logging vào chính sách Retry (đã trình bày ở trên nhưng nhắc lại ở đây để nhấn mạnh tầm quan trọng):
// Ví dụ sử dụng ILogger từ Microsoft.Extensions.Logging
// Cần inject ILogger<YourService> vào service chứa logic gọi Polly
// ... trong ConfigureServices
services.AddHttpClient("LoggingApi", client => { /* ... */ })
.AddPolicyHandler(Policy
.HandleTransientHttpError()
.WaitAndRetryAsync(3, retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)),
onRetry: (outcome, timespan, retryAttempt, context) =>
{
// Lấy ILogger từ ServiceProvider (hoặc inject vào lớp chứa policy)
var logger = context.GetLogger(); // Requires Polly.Extensions.Logging
if (logger != null)
{
logger.LogWarning("Retry {RetryAttempt} of {PolicyKey} after {Delay}s due to {ExceptionType}",
retryAttempt, context.PolicyKey, timespan.TotalSeconds, outcome.Exception?.GetType().Name);
}
}));
// ... Để `context.GetLogger()` hoạt động, bạn cần thêm package Polly.Extensions.Logging
// và đăng ký logger trong context:
// services.AddHttpClient(...).AddPolicyHandler((services, request) =>
// {
// var logger = services.GetRequiredService<ILogger<YourService>>();
// var policy = Policy...
// return policy.WithPolicyKey("MySpecificPolicy").WithLogging(logger); // Use .WithLogging extension method
// });
Các Chính Sách Polly Phổ Biến
Dưới đây là bảng tóm tắt các chính sách thường gặp mà chúng ta đã thảo luận:
Chính sách | Mục đích | Khi nào sử dụng | Cần lưu ý |
---|---|---|---|
Retry (Thử Lại) | Tự động lặp lại hoạt động khi gặp lỗi tạm thời (transient fault). | Lỗi mạng tạm thời, dịch vụ bận trong giây lát, deadlock cơ sở dữ liệu ngắn hạn, lỗi 5xx/408 HTTP. | Cần có thời gian chờ giữa các lần thử (backoff), nên dùng exponential backoff + jitter. Giới hạn số lần thử lại. |
Circuit Breaker (Ngắt Mạch) | Ngăn chặn các yêu cầu tiếp theo đến một dịch vụ bị lỗi liên tục, cho dịch vụ thời gian phục hồi và bảo vệ chính ứng dụng khỏi chờ đợi/lãng phí tài nguyên. | Dịch vụ ngoại bộ đang gặp sự cố nghiêm trọng, timeout liên tục, lỗi không tạm thời. Thường bọc chính sách Retry. | Cấu hình ngưỡng lỗi và thời gian ngắt mạch phù hợp. Theo dõi trạng thái mạch. |
Timeout (Hết Giờ) | Giới hạn thời gian cho một hoạt động hoàn thành. | Ngăn chặn chờ đợi vô thời hạn, đảm bảo phản hồi nhanh. Áp dụng cho từng lần thử (Retry) hoặc cho toàn bộ quá trình. | Chọn Timeout lạc quan khi có thể. Đặt thời gian hợp lý để không ngắt quá sớm nhưng cũng không chờ quá lâu. |
Bulkhead (Ngăn Cách) | Hạn chế số lượng yêu cầu đồng thời đến một dịch vụ hoặc tài nguyên, ngăn chặn quá tải làm sập cả hệ thống. | Dịch vụ ngoại bộ có giới hạn kết nối, tài nguyên nội bộ bị hạn chế (connection pool, thread pool). | Xác định đúng giới hạn cho tài nguyên cần bảo vệ. |
Fallback (Dự Phòng) | Cung cấp một phản hồi thay thế hoặc thực hiện một hành động khác khi hoạt động chính bị lỗi (sau khi đã áp dụng các chính sách khác như Retry, Circuit Breaker…). | Hiển thị dữ liệu cache, trả về giá trị mặc định, thông báo cho người dùng một cách thân thiện, gọi một dịch vụ dự phòng. | Đảm bảo logic dự phòng đủ đơn giản và đáng tin cậy. |
Rate Limit (Giới Hạn Tốc Độ) | Giới hạn số lượng yêu cầu được thực hiện trong một khoảng thời gian nhất định. | Khi gọi API có giới hạn tần suất gọi, hoặc bảo vệ tài nguyên nội bộ khỏi bị gọi quá nhanh. | Cấu hình ngưỡng và khoảng thời gian giới hạn chính xác. |
Kết Nối với Lộ Trình .NET
Khả năng chống chịu là một kỹ năng thiết yếu khi bạn tiến xa hơn trên Lộ Trình .NET, đặc biệt là khi bạn bắt đầu xây dựng các hệ thống phức tạp hơn như microservices, tương tác với các API ngoại bộ, hoặc làm việc với các hệ thống Messaging. Bằng cách chủ động thêm các chính sách chống chịu với Polly, bạn đang xây dựng các ứng dụng không chỉ hoạt động khi mọi thứ suôn sẻ mà còn có thể vượt qua những trở ngại nhỏ mà không làm gián đoạn trải nghiệm người dùng.
Việc hiểu và áp dụng Polly cho các cuộc gọi HTTP ra ngoài (như đã học về HTTP và xây dựng API) hay tương tác với cơ sở dữ liệu qua EF Core là bước tiến quan trọng để xây dựng các hệ thống vững chắc và đáng tin cậy hơn.
Lời Kết
Trong bài viết này, chúng ta đã khám phá Polly, một công cụ không thể thiếu để xây dựng các ứng dụng .NET có khả năng chống chịu cao. Chúng ta đã tìm hiểu về các chính sách phổ biến như Retry, Circuit Breaker, Timeout, và cách tích hợp chúng một cách hiệu quả với HttpClientFactory trong ASP.NET Core.
Việc áp dụng Polly không chỉ giúp ứng dụng của bạn hoạt động ổn định hơn trước các sự cố tạm thời mà còn cải thiện trải nghiệm người dùng và giảm bớt gánh nặng vận hành. Hãy bắt đầu thêm Polly vào dự án .NET tiếp theo của bạn và cảm nhận sự khác biệt!
Chúc bạn thành công trên hành trình chinh phục Lộ Trình .NET. Hẹn gặp lại trong các bài viết tiếp theo!
“`