1. Giới thiệu
Trong thế giới hiện đại của kiến trúc hướng dịch vụ (SOA), microservices và giải pháp cloud, một yêu cầu từ người dùng có thể "bay" qua hàng chục service khác nhau trước khi trả về kết quả. Mỗi node trên "hành trình" này ghi log riêng, nhưng làm sao biết lỗi hoặc độ trễ ở một chỗ có thực sự liên quan tới chính yêu cầu đó?
Phân tán tracing giống như hệ thống tracking cho gói hàng trong logistics: bạn thấy gói đi qua những điểm nào, nơi nào bị trì hoãn và nơi xảy ra sự cố. Trong lập trình — đó là khả năng theo dõi "chuyến đi" của một yêu cầu qua tất cả service, biết nó tốn bao nhiêu thời gian ở đâu và chỗ nào xảy ra lỗi hoặc độ trễ.
Tại sao cần tracing?
- Xác định lỗi: Nếu có vấn đề, bạn thấy ngay nó ở đâu: service nào, bước nào và thậm chí phương thức cụ thể nào đã được gọi.
- Tìm "điểm nghẽn": Tracing cho thấy service nào chạy chậm và bước nào mất nhiều thời gian nhất.
- Phân tích hành vi người dùng: Hiểu cách khách hàng thực sự dùng service của bạn và nơi nào nên ưu tiên tối ưu.
Cách hoạt động?
Mỗi yêu cầu được gán một identifier duy nhất cho trace (TraceId), identifier này "đi theo" yêu cầu xuyên suốt stack của các service: từ frontend tới backend, từ API tới DB rồi quay lại. Ở mỗi bước tạo ra các "span" (spans) ghi lại thời gian thực thi và sự kiện bổ sung. Kết quả là bạn có một cây (graph) các thao tác với thời lượng và mối liên hệ giữa chúng.
Các thực thể và thuật ngữ chính của phân tán tracing
| Thuật ngữ | Mô tả |
|---|---|
| Trace | Đường đi logic của một yêu cầu qua nhiều service. Duy nhất theo TraceId. |
| Span | Một thao tác hoặc thao tác con trong trace. Mỗi span có identifier duy nhất. |
| TraceId | Identifier duy nhất cho toàn bộ trace (toàn bộ yêu cầu). |
| SpanId | Identifier duy nhất cho span hiện tại (thao tác). |
| Parent SpanId | Id của span cha (nếu span này là một phần của thao tác khác). |
| Attributes | Tập metadata do người dùng đặt cho span: tên method, tham số, trạng thái, v.v. |
| Events/Logs | Sự kiện quan trọng liên quan tới span (ví dụ lỗi, hoàn thành). |
| Context Propagation | Truyền thông tin về trace giữa các service và luồng. |
2. OpenTelemetry: chuẩn mở cho observability
Tóm tắt về OpenTelemetry
OpenTelemetry — chuẩn mở và bộ công cụ để thu thập telemetry (tracing, metrics, logs) từ các ứng dụng khác nhau. Nó được Cloud Native Computing Foundation (CNCF) bảo trợ và đã trở thành tiêu chuẩn de-facto trong thế giới ứng dụng đám mây và phân tán.
OpenTelemetry (viết tắt OTel) làm được:
- Tự động thu thập và gửi traces, metrics và logs;
- Hỗ trợ các ngôn ngữ chính, bao gồm C#, Java, Python và nhiều ngôn ngữ khác;
- Gửi dữ liệu tới các hệ thống observability — Jaeger, Zipkin, Azure Monitor, Grafana, v.v.;
- Mở rộng bằng plugin và cấu hình cho bất kỳ stack nào.
Tại sao chọn OpenTelemetry?
- Không phụ thuộc vendor: OTel là chuẩn, không phải sản phẩm của một công ty cụ thể.
- Tương thích: Hỗ trợ các định dạng trace chính, dễ tích hợp với APM systems.
- Tự động hóa: Phần lớn công việc (ví dụ tracing HTTP requests) OTel có thể làm tự động với ít cấu hình.
- Khả năng scale: OTel hoạt động tốt ở hệ thống nhỏ lẫn rất lớn.
Kiến trúc OpenTelemetry: giải thích ngắn gọn
Để khỏi rối, ta trực quan hóa luồng dữ liệu trace điển hình trong OTel.
flowchart LR
subgraph Ứng dụng
A[OpenTelemetry SDK]
end
A -->|Traces, metrics, logs| B[OpenTelemetry Collector]
B -->|Export tới| C[Jaeger/Zipkin/Grafana/Azure/Application Insights v.v.]
- OpenTelemetry SDK: Nhúng trực tiếp vào ứng dụng của bạn (ví dụ qua NuGet packages cho .NET).
- OTel Collector: Service tập trung riêng (thường deploy bằng Docker), nhận trace data từ tất cả app và export tới nơi bạn chỉ định.
- Frontend quan sát: Hệ thống nơi bạn thấy biểu đồ đẹp, cây trace, lọc và phân tích trace.
3. Bắt đầu nhanh với tracing trên C#/.NET
Thêm tracing phân tán cơ bản vào ứng dụng học tập. Các bước chính khá đơn giản.
Install NuGet packages
dotnet add package OpenTelemetry
dotnet add package OpenTelemetry.Exporter.Console
OpenTelemetry — implementation SDK cơ bản;
OpenTelemetry.Exporter.Console — exporter đưa traces ra console (có thể thay bằng Jaeger, Zipkin, v.v.).
Cấu hình tối thiểu cho tracing
Trong file Program.cs (cho console app):
using OpenTelemetry;
using OpenTelemetry.Trace;
class Program
{
static void Main(string[] args)
{
// Cấu hình tracing
using var tracerProvider = Sdk.CreateTracerProviderBuilder()
.AddSource("DemoApp") // Chỉ định tên source
.AddConsoleExporter() // Export traces ra console
.Build();
var source = new System.Diagnostics.ActivitySource("DemoApp");
// Bắt đầu một trace mới (root span)
using (var activity = source.StartActivity("Hoạt động chính"))
{
DoWork();
}
}
static void DoWork()
{
// Một số logic ứng dụng...
System.Threading.Thread.Sleep(300);
}
}
Chuyện gì đang xảy ra?
- Tạo tracer provider, đăng ký source ("DemoApp") và thêm console exporter AddConsoleExporter().
- Khởi tạo một "activity" (span) qua ActivitySource ở root của chương trình.
- Bên trong thực hiện công việc hữu ích mà bạn muốn trace.
Kết quả:
Activity.Id: 0b69e3e97ca5f14d
Activity.Operation: Hoạt động chính
Duration: 00:00:00.3001082
...
Auto-instrumentation cho HTTP và database
OpenTelemetry hỗ trợ instrumentation — tự động trace cho các thư viện phổ biến. Ví dụ, các gọi qua HttpClient và truy vấn DB (ADO.NET) có thể được thu thập mà không cần sửa code của bạn.
using var tracerProvider = Sdk.CreateTracerProviderBuilder()
.AddHttpClientInstrumentation() // HTTP requests
.AddSqlClientInstrumentation() // SQL requests (nếu dùng)
.AddConsoleExporter()
.Build();
Bây giờ tất cả gọi qua HttpClient và thao tác với SQL sẽ tự động xuất hiện trong traces.
4. Trực quan hóa tracing
In ra console chỉ là bước đầu. Để có lợi ích thực sự, bạn cần frontend trực quan.
Jaeger
Jaeger là một trong những hệ thống open-source phổ biến để trực quan hóa traces.
- Triển khai Jaeger (ví dụ bằng Docker).
- Thay cho AddConsoleExporter() dùng:
.AddJaegerExporter(options => { options.AgentHost = "localhost"; options.AgentPort = 6831; }) - Bây giờ tất cả traces sẽ hiển thị trong Jaeger UI — có thể lọc theo TraceId, xem biểu đồ thời gian chi tiết.
Hướng dẫn đầy đủ: OpenTelemetry Jaeger Exporter cho .NET
5. Những lưu ý hữu ích
So sánh các cách tiếp cận
Không dùng OTel thì bạn copy TraceId vào logs bằng tay, hy vọng không quên truyền nó tiếp và vật lộn khi phân tích incident. Với OTel, chuỗi được dựng tự động và trực quan hóa chỉ với vài cú click.
| Cách | Nỗ lực | Độ tin cậy | Khả năng scale | Tương thích |
|---|---|---|---|---|
| Ghi log TraceId thủ công | Cao | Thấp | Cần bảo trì liên tục | Chỉ các app của bạn |
| OpenTelemetry | Tối thiểu (sau khi triển khai) | Đảm bảo | Rộng (mọi ngôn ngữ, stack) | Tương thích với hầu hết APM và hệ thống log |
Ví dụ kiến trúc
[Web-client] --> [API-service] --> [Service-authorize] --> [Service-database]
Khi người dùng bấm nút, request đi qua các service này. Với OTel bạn sẽ "kéo sợi chỉ" (TraceId) qua từng service, tự động gom logic thành một trace duy nhất.
- Trong mỗi service, SDK OTel tự nhận TraceId từ HTTP headers và tiếp tục chuỗi.
- Kết quả là mở một trace — và thấy nơi đã tiêu tốn millisecond, chuyện gì diễn ra và nơi xuất hiện lỗi.
Ứng dụng trong dự án thực và phỏng vấn
- Hệ thống microservices và ứng dụng phân tán (fintech, marketplace, SaaS,...).
- DevOps/SRE: nhanh chóng định vị vấn đề.
- Cải thiện SLA và độ phản hồi của dịch vụ.
- Profiling và tối ưu hóa.
- Trong phỏng vấn — thể hiện thực hành kỹ thuật mature.
6. Lỗi phổ biến và đặc trưng khi triển khai
Quên truyền TraceContext. Nếu giữa các service không truyền TraceId (ví dụ quên set các HTTP headers cần thiết), traces sẽ bị đứt — xuất hiện như các "điểm" riêng lẻ và toàn bộ lợi ích của observability biến mất.
Code không được instrumented. Nếu thư viện hoặc framework bạn dùng không hỗ trợ instrumentation, một số thao tác sẽ rơi ra khỏi trace (nhưng bạn luôn có thể tạo span thủ công qua ActivitySource và StartActivity()).
Quá nhiều dữ liệu. Thu thập trace cho mọi request rất tốn kém, đặc biệt ở production lớn. Thường bật tracing cho một phần request (sample rate) hoặc theo trigger (lỗi, request chậm).
Hiệu năng. Việc thêm OTel ảnh hưởng tối thiểu tới performance nếu dùng export bất đồng bộ, nhưng với traffic cực lớn cần giám sát overhead và điều chỉnh sampling rate.
GO TO FULL VERSION