[Microservices] Trace ID: "Sợi chỉ đỏ" cứu rỗi thanh xuân khi debug hệ thống phân tán
Chào anh em, lại là mình đây.
Nếu anh em từng làm mấy con web nguyên khối (Monolithic), việc tìm lỗi (debug) nó khá là "chill". Khách hàng báo lỗi tạo đơn hàng? Anh em chỉ cần mở đúng một cái file laravel.log ra, Ctrl+F tìm đúng cái giờ đó là thấy ngay cục lỗi to đùng báo thiếu field hay sai logic.
Nhưng khi anh em nhảy sang ôm một hệ thống e-commerce với quy mô cực khủng, kiến trúc bị băm nát ra thành cả chục cái Microservices, thì câu chuyện nó chuyển sang chế độ "Nightmare" (Ác mộng).
Hãy tưởng tượng một luồng checkout diễn ra thế này:
- Khách bấm thanh toán -> Request chui vào API Gateway.
- Gateway đẩy sang Order Service (anh em viết bằng Node.js & TypeScript)
- Order Service xác nhận xong, ném một cái event vào Apache Kafka.
- Inventory Service (anh em viết bằng Golang cho nó gánh tải tốt) chui vào Kafka bốc event ra để trừ tồn kho.
- Lỗi cái rầm ở thằng Golang!
Khách hàng gọi lên tổng đài mắng vốn. Anh em lật đật mở Kibana (Elasticsearch) lên check log. Lúc này, đập vào mắt anh em là 10.000 dòng log đang nhảy múa loạn xạ của hàng trăm user đang checkout cùng lúc. Làm sao anh em biết được dòng log ERROR: Out of stock nào ở service Golang là của cái request xuất phát từ service Node.js của vị khách kia?
Không có manh mối nào cả! Anh em sẽ chìm nghỉm trong mớ log đó. Và đây là lúc Trace ID (hay Correlation ID) xuất hiện như một đấng cứu thế.
1. Trace ID rốt cuộc là cái quái gì?
Hiểu một cách dân dã, Trace ID là một cái "Căn cước công dân" hoặc một cái "Passport" của một Request.
Mỗi khi có một Request từ người dùng (User) đập vào hệ thống (thường là chạm vào cổng API Gateway đầu tiên), hệ thống sẽ lập tức sinh ra một chuỗi mã ngẫu nhiên, độc nhất vô nhị (thường dùng chuẩn UUID).
Ví dụ: trace_id = 550e8400-e29b-41d4-a716-446655440000.
Nhiệm vụ của hệ thống là phải nhét cái mã Trace ID này vào mọi ngóc ngách, mọi dòng log, và truyền nó đi theo Request đó xuyên suốt qua tất cả các Service.
2. Quy tắc luân chuyển: Truyền đuốc Olympic
Để cái Trace ID này có tác dụng, anh em phải tuân thủ kỷ luật sắt đá khi code: Tuyệt đối không được làm đứt dây chuyền.
- Qua HTTP Request (API Call): Khi thằng Node.js muốn gọi API sang thằng khác, nó bắt buộc phải nhét cái Trace ID này vào HTTP Header. Thường người ta sẽ dùng các key như
X-Request-ID,X-Correlation-IDhoặc traceparent (chuẩn của W3C). - Qua Message Queue (Kafka): Khi ném một message vào Kafka, đừng chỉ ném mỗi cái body (payload). Hãy nhét thêm cái Trace ID vào phần Kafka Headers (metadata). Nhờ đó, thằng Golang lúc consume (đọc) message ra, nó lấy được ngay cái Trace ID để gán vào context của nó.
3. Ma thuật xảy ra tại Centralized Logging (Log tập trung)
Khi tất cả các service (Node.js, Golang...) đều tuân thủ việc in log kèm theo Trace ID, anh em sẽ đẩy toàn bộ log của chúng nó về một cục Elasticsearch.
Bây giờ, thay vì mò kim đáy bể, quy trình fix bug của anh em sẽ thanh lịch như một vị thần:
- Thấy khách báo lỗi, anh em tìm xem lúc khách bấm nút, hệ thống sinh ra cái Trace ID nào (thường Front-end sẽ bắt được cái ID này từ Response Header của lỗi và hiển thị lên màn hình hoặc lưu lại).
- Anh em vác cái mã
550e8400... đó ném vào ô Search của Kibana. - Bùm! Một bức tranh hoàn hảo hiện ra. Kibana sẽ lọc đi 9.999 dòng log rác, chỉ hiển thị đúng 5 dòng log có chứa mã Trace ID đó, xếp theo đúng thứ tự thời gian:
[INFO] [NodeJS] Bắt đầu tạo đơn hàng...[INFO] [NodeJS] Đã ném event vào Kafka...[INFO] [Golang] Nhận được event từ Kafka...[ERROR] [Golang] Không tìm thấy sản phẩm trong Database!
Nguyên nhân lỗi lộ thiên ngay lập tức. Anh em fix bug trong 3 phút và thong thả đi pha cafe.
4. Đồ chơi đi kèm: OpenTelemetry & Jaeger
Nếu anh em nghĩ in log ra chữ đã là ngon, thì giới tinh hoa còn chơi một hệ cao hơn gọi là Distributed Tracing (Truy vết phân tán) với các tool như Jaeger hay Zipkin.
Bằng cách sử dụng các bộ thư viện tiêu chuẩn như OpenTelemetry, anh em không chỉ có log chữ, mà còn vẽ được cả một cái biểu đồ dạng Timeline (Gantt chart). Nhìn vào cái biểu đồ đó, anh em biết ngay request tốn 500ms ở Node.js, tốn 20ms nằm chờ trong Kafka, tốn 1000ms ở Golang để chọc vào Database. Thằng nào làm hệ thống chậm, nhìn cái biểu đồ phát là tóm cổ được ngay, khỏi đổ thừa cho nhau!
Chốt hạ
Làm Microservices mà không có Trace ID, nó giống như anh em đi điều tra một vụ án mạng mà hung thủ không để lại bất kỳ dấu vân tay hay camera an ninh nào. Hệ thống càng lớn, lượng log sinh ra mỗi giây càng khổng lồ, anh em sẽ càng tuyệt vọng.
Thiết lập Trace ID không khó về mặt code (viết cái Middleware ở Node.js và interceptor ở Golang bắt Header là xong), cái khó là sự kỷ luật của toàn bộ team Dev, không được phép quên luân chuyển cái ID đó giữa các hàm xử lý.
Hệ thống của anh em đã gắn Trace ID để theo vết chưa? Hay vẫn đang "bói" log bằng mắt thường? Để lại comment chém gió bên dưới nhé!
All rights reserved