Back to Explore
Nghệ thuật tối ưu hóa Metrics cho Nginx: Giải pháp thoát khỏi Cardinality Explosion

Nghệ thuật tối ưu hóa Metrics cho Nginx: Giải pháp thoát khỏi Cardinality Explosion

Khám phá cách chuyển đổi Nginx thành một hệ thống giám sát hiệu năng mạnh mẽ với OpenResty, giải quyết triệt để vấn đề cardinality explosion và tối ưu hóa tài nguyên hệ thống.

Website
Upvote this postSign in to upvote this article.

Bài viết được dịch và tổng hợp từ tin tức gốc. Bạn có thể đọc bài viết gốc bằng tiếng Anh tại đây.

Điểm tin nhanh:

  • Sử dụng OpenResty để biến Nginx thành một công cụ giám sát hiệu năng mạnh mẽ thay vì chỉ là reverse proxy.
  • Giải quyết triệt để vấn đề Cardinality Explosion bằng cách chuẩn hóa các nhãn (labels) như route, IP và host.
  • Tối ưu hóa lưu trữ Prometheus bằng cách kết hợp giữa Gauge và Histogram thay vì chỉ dựa vào các ước tính quantile.

Việc giám sát Nginx trong môi trường production thường trở thành một cơn ác mộng khi dữ liệu bắt đầu bùng nổ. Nếu bạn đang đối mặt với tình trạng Grafana bị quá tải do các chuỗi thời gian (time series) vô tận từ các đường dẫn URL chứa ID người dùng hoặc UUID, bạn không đơn độc. Đây là câu chuyện về cách chúng tôi đã biến Nginx thành một công cụ quan sát (observability) chuyên sâu mà không làm ảnh hưởng đến hiệu năng hệ thống.

Ảnh bìa bài viết

Đối mặt với Cardinality Explosion

Khi bắt đầu thêm các nhãn (labels) vào metrics, sai lầm phổ biến nhất là đưa trực tiếp ngx.var.request_uri vào hệ thống. Kết quả là mỗi URL duy nhất trở thành một time series riêng biệt. Trong một hệ thống quy mô lớn, điều này dẫn đến sự bùng nổ cardinality, khiến cơ sở dữ liệu Prometheus của bạn nhanh chóng cạn kiệt tài nguyên.

Để giải quyết vấn đề này, chúng ta cần chuyển đổi route từ dạng instance sang dạng shape. Thay vì lưu /orders/8231, chúng ta cần chuẩn hóa thành /orders/$param.

Kỹ thuật chuẩn hóa dữ liệu

Chúng ta sử dụng một hàm kiểm tra phân đoạn (segment) để lọc bỏ các giá trị biến đổi:

local function is_param_segment(seg)
    if #seg > cfg.sanitize_max_segment_len then return true end
    if cfg.sanitize_pure_numeric and seg:match("^%d+$") then return true end
    if cfg.sanitize_uuid and seg:match("^%x{8}-%x{4}-%x{4}-%x{4}-%x{12}$") then return true end
    return false
end

Việc thiết kế dữ liệu một cách khoa học ngay từ đầu là chìa khóa, giống như cách chúng ta đã thảo luận trong bài viết về Hình thái của dữ liệu: Tại sao cách bạn biểu diễn dữ liệu quyết định tư duy lập trình.

Chiến lược quản lý nhãn thông minh

Không chỉ route, mà cả Client IP và Host cũng cần được xử lý để tránh noise. Đối với IP, việc áp dụng subnet masking (ví dụ: /24 cho pod CIDRs và /16 cho public IPs) giúp giảm thiểu số lượng series mà vẫn giữ được độ chi tiết cần thiết.

Loại nhãn Chiến lược xử lý Mục tiêu
Route Thay thế ID/UUID bằng $param Giảm cardinality
Client IP Masking subnet (/24 hoặc /16) Bảo mật và tối ưu storage
Host Phân loại (localhost, ip, domain) Gom nhóm dữ liệu

Lưu ý: Mỗi nhãn bạn thêm vào là một quyết định về chi phí lưu trữ. Prometheus nhân các tổ hợp nhãn thay vì cộng dồn chúng.

Histogram hay Gauge?

Histogram cung cấp ước tính quantile, nhưng với các route ít traffic, dữ liệu này trở nên thiếu chính xác. Chúng tôi sử dụng Gauge để ghi lại thời gian phản hồi của request cuối cùng. Điều này mang lại sự công bằng cho các route ít được truy cập, trong khi Histogram vẫn làm tốt vai trò với các route có lưu lượng cao.

Để tránh tình trạng Gauge bị stale (cũ) mãi mãi, chúng ta cần một cơ chế tự làm sạch (self-cleaning) bằng cách sử dụng ngx.shared.dict và một timer quét định kỳ mỗi 5 giây để xóa các metric không còn hoạt động.

Hatam Abolghasemi

Mở rộng sang giám sát Egress

Sau khi đã làm chủ được traffic ingress, việc giám sát traffic egress (traffic đi ra) trở thành bước đi tiếp theo. Bằng cách tận dụng sidecar Nginx hiện có, chúng ta có thể theo dõi các cuộc gọi ra bên ngoài mà không cần can thiệp vào mã nguồn ứng dụng. Điều này tương tự như cách chúng ta Xây dựng CLI hiện đại: Tích hợp Health Check và Changelog tự động cho công cụ của bạn để tối ưu hóa vận hành.

Đánh giá & Lời khuyên Thực tiễn

Ưu điểm:

  • Giảm thiểu đáng kể chi phí lưu trữ Prometheus.
  • Không gây ảnh hưởng đến hiệu năng request (log_by_lua_block chạy sau khi response đã gửi).
  • Khả năng tùy biến cao với Lua.

Nhược điểm:

  • Đòi hỏi kiến thức về Lua và cấu trúc dữ liệu.
  • Cần bảo trì các bộ lọc regex khi cấu trúc URL thay đổi.

Lời khuyên: Nếu bạn đang gặp khó khăn trong việc quản lý log và metric, hãy cân nhắc việc Biến JSON Logs thành biểu đồ trong 2 phút mà không cần hệ thống Observability phức tạp trước khi xây dựng hệ thống tùy chỉnh phức tạp.

Câu hỏi thường gặp (FAQ)

Tại sao không dùng các công cụ giám sát có sẵn?

Các công cụ có sẵn thường không linh hoạt trong việc xử lý cardinality ở quy mô cực lớn hoặc yêu cầu chi phí license cao. Tự xây dựng trên OpenResty cho phép kiểm soát hoàn toàn.

Việc chạy Lua trong Nginx có làm chậm hệ thống không?

Không, vì chúng ta sử dụng log_by_lua_block, nó thực thi sau khi response đã được gửi tới client, hoàn toàn không gây block request.

Làm sao để tránh lỗi khi quét shared dict?

Luôn giới hạn số lượng key quét mỗi lần (expiry_scan_limit) để tránh làm treo worker process.

Kết luận

Việc tối ưu hóa metrics cho Nginx không chỉ là vấn đề kỹ thuật mà còn là bài toán về tư duy thiết kế hệ thống. Bằng cách kiểm soát chặt chẽ cardinality và áp dụng các chiến lược lưu trữ thông minh, bạn có thể biến Nginx thành một mắt xích quan trọng trong hệ thống observability của mình. Hãy bắt đầu refactor lại hệ thống giám sát của bạn ngay hôm nay và đừng quên theo dõi hi_dev để cập nhật những kiến trúc hệ thống hiện đại nhất.

Discussion (0)

You need to log in to post comments. Log In

No comments yet. Start the discussion!