Các loại monitoring trong thiết kế hệ thống
Xem thửTình huống: dashboard xanh, nhưng khách hàng nói app chậm
Thứ Hai, 9 giờ sáng. Bộ phận chăm sóc khách hàng của GDev nhắn vào kênh trực: "Nhiều khách báo màn hình đặt hàng quay rất lâu, có người phải bấm lại". Bạn mở dashboard của gdev-api. Mọi ô đều xanh: dịch vụ đang chạy, CPU 23%, RAM 41%, health check trả về 200.
Hai người trong đội tranh luận. Một người nói: "Hệ thống khỏe, khách dùng mạng yếu thôi". Người kia nói: "Khách báo thật, sao có thể xanh được". Cả hai đều đang nhìn đúng dữ liệu, nhưng dữ liệu đó không trả lời câu hỏi "người dùng có đang bị ảnh hưởng không".
Bài này xây lại cách chọn thứ để quan sát. Bạn sẽ tự dựng đúng sự cố trên trong máy mình, thấy health check xanh trong khi một phần người dùng chờ 2 giây, rồi rút ra nên đo gì và nên báo động theo cái gì.
Sau bài này bạn làm được các việc sau:
- Phân biệt metrics, logs, traces bằng câu hỏi mà mỗi loại trả lời, và biết bắt đầu điều tra từ đâu.
- Giải thích vì sao health check xanh và giá trị trung bình đều có thể nói dối, rồi dùng bốn tín hiệu vàng (four golden signals) để thay thế.
- Chọn nên báo động theo triệu chứng hay theo nguyên nhân, và nhận ra dấu hiệu của mệt mỏi vì cảnh báo (alert fatigue).
Bạn cần có sẵn: terminal Linux hoặc macOS với python3 (3.8 trở lên), curl và awk. Bài này chưa cần Docker. Hai bài tiếp theo của chương mới đưa Prometheus vào.
Ba mô hình cần nhớ
- Quan sát từ ngoài và từ trong: black-box monitoring kiểm tra hành vi như người dùng thấy, white-box monitoring đọc số liệu mà chính hệ thống phát ra. Mỗi loại thấy một nửa sự thật.
- Ba loại tín hiệu, ba câu hỏi: metrics trả lời "bao nhiêu, nhanh hay chậm, có xu hướng gì", logs trả lời "chuyện gì xảy ra với từng sự kiện", traces trả lời "một request mất thời gian ở đâu".
- Báo động theo triệu chứng, tra cứu theo nguyên nhân: người trực chỉ nên bị đánh thức vì thứ người dùng cảm nhận được. CPU, đĩa, số kết nối là chỗ để tìm lý do sau khi đã có triệu chứng.
Chuẩn bị môi trường lab
Lab dùng một máy chủ HTTP giả lập gdev-api viết bằng thư viện chuẩn của Python, chạy ở cổng 18090 trên máy bạn. Nó mô phỏng một lỗi quen thuộc: đường dẫn /healthz luôn nhanh, còn /orders thỉnh thoảng bị chậm vì một truy vấn chậm (slow query). Cứ 10 request tới /orders thì có 1 request mất 2 giây, và cứ 50 request thì có 1 request trả lỗi 500. Máy chủ ghi mỗi request thành một dòng gồm đường dẫn, mã trạng thái và thời gian xử lý (giây).
Lưu đoạn mã sau thành slow_api.py:
import itertools
import sys
import time
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
counter = itertools.count(1)
class Handler(BaseHTTPRequestHandler):
def do_GET(self):
started = time.perf_counter()
status = 200
if self.path == "/orders":
n = next(counter)
if n % 10 == 0:
time.sleep(2.0)
else:
time.sleep(0.05)
if n % 50 == 0:
status = 500
self.send_response(status)
self.send_header("Content-Length", "2")
self.end_headers()
self.wfile.write(b"ok")
took = time.perf_counter() - started
print("%s %s %.3f" % (self.path, status, took), file=sys.stderr, flush=True)
def log_message(self, *args):
pass
ThreadingHTTPServer(("127.0.0.1", 18090), Handler).serve_forever()
Chạy ở terminal thứ nhất, nhật ký truy cập đi vào file access.log:
python3 slow_api.py 2> access.log
Terminal thứ hai là nơi bạn làm mọi việc còn lại. Đặt LC_ALL=C một lần, vì nếu máy bạn dùng dấu phẩy làm dấu thập phân thì awk sẽ đọc 0.051 thành 0:
export LC_ALL=C
Lab: nhìn hệ thống bằng hai cách
Cách thứ nhất là black-box: một "người dùng giả" gọi /healthz, đúng thứ mà nhiều hệ thống dùng làm health check.
curl -s -o /dev/null -w "healthz %{http_code} %{time_total}s\n" http://127.0.0.1:18090/healthz
Kết quả minh họa (thời gian của bạn sẽ khác chút):
healthz 200 0.003250s
Mã 200 trong 3 mili giây. Nếu dashboard chỉ dựa vào đây, nó xanh. Bây giờ mô phỏng 100 lần khách đặt hàng, lần lượt từng request:
for i in $(seq 1 100); do curl -s -o /dev/null http://127.0.0.1:18090/orders; done
Lệnh chạy khoảng 20 giây vì có 10 request chậm 2 giây. Cách thứ hai là white-box: đọc số liệu mà chính ứng dụng ghi lại. Tính tỷ lệ lỗi và thời gian trung bình:
awk '$1=="/orders"{n++; if($2>=500)e++; s+=$3} END{printf "requests=%d errors=%d error_ratio=%.3f mean=%.3fs\n", n,e,e/n,s/n}' access.log
Rồi tính các phân vị (percentile) của thời gian xử lý, tức là giá trị mà một phần trăm nhất định của request nhanh hơn hoặc bằng:
grep '^/orders' access.log | awk '{print $3}' | sort -n | awk '{a[NR]=$1} END{printf "p50=%.3fs p90=%.3fs p99=%.3fs max=%.3fs\n", a[int(NR*0.5)], a[int(NR*0.9)], a[int(NR*0.99)], a[NR]}'
Kết quả minh họa (đây là kết quả chạy thật khi viết bài; số của bạn gần giống vì độ trễ nhỏ dao động theo máy):
requests=100 errors=2 error_ratio=0.020 mean=0.246s
p50=0.050s p90=0.054s p99=2.003s max=2.006s
Đọc kết quả từng dòng:
mean=0.246s: trung bình gần một phần tư giây. Trên biểu đồ vẽ trung bình, con số này trông chấp nhận được và không ai bị đánh thức.p50=0.050svàp90=0.054s: 90% request nhanh. Đây là lý do nhiều người dùng không thấy gì bất thường.p99=2.003s: 1% chậm nhất mất tới 2 giây. Vì cứ 10 request có 1 request chậm,p90vừa đủ nằm ngay sát vùng chậm và vẫn trông đẹp. Chỉ cần thêm một chút request chậm nữa làp90cũng nhảy theo. Phân vị cao cho thấy phần người dùng bị thiệt.error_ratio=0.020: 2% request lỗi. Hai lỗi này cũng là hai request chậm 2 giây, nên người bị lỗi còn phải chờ trước khi nhận lỗi.
Kết luận của lab: health check xanh, CPU thấp, trung bình ổn, mà cứ 10 khách thì 1 khách chờ 2 giây. Đội ở phần tình huống có cả hai quan điểm đúng, vì họ đang đo sai thứ.
Điều tra từ đâu: metrics, logs, traces
Trước khi xem sơ đồ, hãy tự trả lời: ở lab trên, bạn muốn biết "có bao nhiêu request chậm, và có tăng không" hay "request chậm nhất đã đi qua những bước nào"? Hai câu hỏi này dùng hai loại dữ liệu khác nhau.
Đang tải sơ đồ…
Cách đọc sơ đồ:
- Điều tra gần như luôn bắt đầu bằng metrics, vì metrics là số liệu tổng hợp, rẻ để lưu, và trả lời nhanh câu hỏi "triệu chứng có thật không, nặng cỡ nào, bắt đầu từ lúc nào". Ở lab, câu trả lời là
p99bằng 2 giây trên/orders. - Nếu triệu chứng có thật, bạn cần biết thời gian đi đâu. Traces (dấu vết) ghi lại hành trình của một request đi qua các dịch vụ, mỗi bước là một đoạn có thời lượng. Một trace của request chậm có thể chỉ ra 1,9 giây nằm ở truy vấn database.
- Cuối cùng logs cho chi tiết của đúng sự kiện đó: câu SQL, mã lỗi,
request_id. Logs đắt hơn vì lưu từng sự kiện, nên người ta chỉ mở sau khi đã thu hẹp phạm vi. - Vòng cuối quay về metrics để xác nhận đã khỏi. Nhánh "no" nhắc rằng đôi khi hệ thống đúng là khỏe và vấn đề nằm ở phía khách.
Bảng dưới tóm tắt ba loại tín hiệu. Chương này chỉ làm metrics với Prometheus; logs và traces được nhắc ở đây để bạn biết khi nào cần chúng.
| Tín hiệu | Câu hỏi trả lời | Dạng dữ liệu | Điểm mạnh | Điểm yếu | Ví dụ trong lab |
|---|---|---|---|---|---|
| Metrics | Bao nhiêu, nhanh hay chậm, xu hướng ra sao | Con số theo thời gian | Rẻ, dễ cảnh báo, vẽ biểu đồ | Không biết request nào hoặc người dùng nào | p99=2.003s, error_ratio=0.020 |
| Logs | Chuyện gì xảy ra với sự kiện này | Dòng văn bản theo sự kiện | Chi tiết đầy đủ, tìm theo từ khóa | Tốn dung lượng, khó thấy xu hướng | Một dòng /orders 500 2.001 |
| Traces | Một request mất thời gian ở bước nào | Cây các đoạn thời gian | Chỉ đúng chỗ chậm qua nhiều dịch vụ | Cần gắn mã vào ứng dụng, thường chỉ lấy mẫu | Request chậm: 1,9 giây ở database (giả định) |
Khái niệm observability (khả năng quan sát) hay bị dùng như từ đồng nghĩa với ba loại tín hiệu này. Cách hiểu thực dụng: monitoring là thu các tín hiệu đã chọn trước để phát hiện những lỗi đã biết, còn observability là đủ dữ liệu giàu ngữ cảnh để hỏi những câu hỏi chưa nghĩ ra trước đó mà không phải sửa mã rồi triển khai lại. Đây là cách phân biệt để dễ nhớ, không phải một chuẩn kỹ thuật, và ranh giới giữa hai khái niệm trên thực tế khá mờ.
Đo cái gì: bốn tín hiệu vàng, RED và USE
Biết loại tín hiệu chưa đủ, bạn còn phải chọn đo cái gì. Theo chương về giám sát hệ thống phân tán trong sách Google SRE, mỗi dịch vụ nên có tối thiểu bốn tín hiệu vàng:
| Tín hiệu | Ý nghĩa | Trong lab gdev-api |
|---|---|---|
| Latency (độ trễ) | Thời gian xử lý một request; nên tách riêng request thành công và request lỗi | p99=2.003s trên /orders |
| Traffic (lưu lượng) | Mức nhu cầu đặt lên hệ thống | 100 request tới /orders trong khoảng 20 giây |
| Errors (lỗi) | Tỷ lệ request thất bại | error_ratio=0.020 |
| Saturation (độ bão hòa) | Hệ thống "đầy" tới mức nào, tài nguyên nào hạn chế nhất | Lab chưa đo; ở thực tế đó là độ dài hàng đợi hay số kết nối database đang dùng |
Hai cách đặt tên ngắn hơn đến từ hai người khác nhau. RED (Rate, Errors, Duration) do Tom Wilkie đề xuất cho dịch vụ nhận request: tốc độ request, tỷ lệ lỗi, thời lượng xử lý. Về cơ bản nó là bốn tín hiệu vàng bỏ saturation. USE (Utilization, Saturation, Errors) do Brendan Gregg đề xuất cho tài nguyên: với mỗi CPU, đĩa, card mạng hay thread pool, hãy kiểm tra mức dùng, hàng đợi chờ, và số lỗi.
Quy tắc chọn rất ngắn: thứ có người dùng gọi tới thì dùng RED, thứ là tài nguyên bị dùng chung thì dùng USE.
| Đối tượng | Phương pháp | Câu hỏi đầu tiên |
|---|---|---|
gdev-api (nhận request HTTP) | RED | Có bao nhiêu request, bao nhiêu lỗi, p99 là bao nhiêu |
Node chạy gdev-api | USE | CPU bận bao nhiêu, hàng đợi chạy có dồn không |
| Pool kết nối database | USE | Bao nhiêu kết nối đang dùng, bao nhiêu request đang chờ |
Vì sao luôn nói tới phân vị p99 mà không dừng ở trung bình? Trung bình trộn lẫn mọi request, nên 90 request nhanh kéo con số xuống và che 10 request chậm. Phân vị cao đo đúng trải nghiệm của nhóm người dùng tệ nhất. Chọn phân vị nào là quyết định sản phẩm: p99 thường dùng cho API quan trọng, và bài Prometheus sẽ cho bạn cách tính nó từ histogram.
Black-box và white-box trong thực tế
Sách Google SRE định nghĩa black-box monitoring là kiểm tra hành vi nhìn thấy từ bên ngoài như một người dùng, còn white-box monitoring dựa vào số liệu do nội bộ hệ thống phát ra. Cả hai cần có:
| Black-box | White-box | |
|---|---|---|
| Nhìn từ đâu | Từ ngoài, như người dùng | Từ trong, qua bộ đếm và log của ứng dụng |
| Bắt tốt | Lỗi đang diễn ra ngay lúc này, lỗi mạng, DNS, chứng chỉ hết hạn | Lỗi sắp xảy ra, lỗi bị che bởi retry, chi tiết theo đường dẫn |
| Bỏ sót | Lỗi chỉ ảnh hưởng một phần request, như lab này | Mọi thứ nằm ngoài ứng dụng: nếu DNS hỏng, ứng dụng không biết có request nào đã không tới |
| Ví dụ | Blackbox exporter gọi https://api.gdev.local/healthz | Counter gdev_http_requests_total do ứng dụng đếm |
Ở lab, /healthz là black-box kém: nó kiểm tra một đường dẫn không đại diện cho đường đặt hàng. Black-box tốt thì gọi đúng hành trình người dùng, và vẫn cần white-box để thấy phần lỗi một phần. Bài Blackbox Exporter ở cuối chương nói tiếp về phía black-box.
Báo động: theo triệu chứng, tra cứu theo nguyên nhân
Có thêm số liệu thì cũng có nhiều thứ để báo động hơn. Sách Google SRE nhấn mạnh phải phân biệt hai câu hỏi: cái gì đang hỏng (triệu chứng) và vì sao (nguyên nhân). Triệu chứng nên dùng để gọi người trực, còn nguyên nhân dùng để điều tra. Tài liệu hướng dẫn cảnh báo của Prometheus cũng dặn như vậy: cảnh báo ở tầng cao nhất có thể, ít cảnh báo nhất có thể.
Thử phân loại sáu ứng viên cảnh báo cho gdev-api. Hãy tự quyết định trước khi đọc cột cuối:
| Ứng viên | Loại | Người dùng có chắc chắn bị ảnh hưởng không | Nên làm gì |
|---|---|---|---|
Tỷ lệ lỗi /orders trên 1% trong 5 phút | Triệu chứng | Có | Báo động gọi người trực |
p99 của /orders trên 1 giây trong 10 phút | Triệu chứng | Có | Báo động gọi người trực |
| CPU của node trên 70% | Nguyên nhân | Không chắc, nhiều node chạy 80% vẫn ổn | Chỉ hiện trên dashboard |
| Đĩa đầy 71% | Nguyên nhân | Chưa | Tạo ticket; chỉ gọi người nếu dự báo đầy trong vài giờ |
| Pod khởi động lại 1 lần | Nguyên nhân | Thường không | Ghi nhận, báo khi lặp lại nhiều lần |
| Prometheus ngừng thu số liệu | Giám sát hỏng | Mất khả năng nhìn | Báo động riêng, vì mọi cảnh báo khác sẽ im lặng theo |
Lưu ý hàng cuối. Khi hệ thống giám sát hỏng, các cảnh báo kia không nổ, và màn hình xanh trở nên nguy hiểm. Tài liệu Prometheus gọi đây là metamonitoring: phải có cảnh báo cho chính hệ thống giám sát.
Cảnh báo theo nguyên nhân không vô ích. Nó rẻ để viết và đôi khi báo sớm hơn triệu chứng. Vấn đề là mỗi cảnh báo như vậy sinh ra những lần nổ mà người dùng không hề bị ảnh hưởng. Sau vài tuần, người trực học cách bỏ qua chuông. Hiện tượng này gọi là alert fatigue (mệt mỏi vì cảnh báo): khi cảnh báo thật xuất hiện, nó bị xử lý như cảnh báo giả. Theo sách Google SRE, mỗi lần gọi người phải khẩn cấp và cần phán đoán của con người, chứ không phải một thao tác máy móc lặp lại.
Một phép tính giả định cho thấy mức độ: một quy tắc CPU dao động nổ 5 lần mỗi ngày cho một người trực trong tuần sẽ tạo 35 lần gọi trong 7 ngày, trong khi sự cố thật của gdev-api có thể chỉ xảy ra một hai lần mỗi tháng. Con số này là giả định để minh họa, không phải số đo.
Xem lại sự cố mở đầu theo trục thời gian. Trước khi bấm, dự đoán: nếu đội chỉ có cảnh báo theo CPU, cảnh báo có nổ khi gdev-api có 10% request chậm 2 giây không?
Đang tải Animation…
Biến thành mục tiêu: SLI và SLO
Sau lab, đội GDev chọn các chỉ số cụ thể để đo sức khỏe gdev-api. Một SLI (service level indicator) là một phép đo trải nghiệm người dùng, và SLO (service level objective) là mục tiêu đặt cho phép đo đó. Ba SLI hợp lý cho /orders:
| SLI | Cách tính từ dữ liệu của lab | SLO ví dụ |
|---|---|---|
| Tỷ lệ thành công | Số request có mã dưới 500 chia tổng số request | 99,5% trong 30 ngày |
| Độ trễ | Phân vị p99 của thời gian xử lý | Dưới 1 giây |
| Tỷ lệ request quá chậm | Số request chậm hơn 1 giây chia tổng số | Dưới 1% |
Với dữ liệu lab (2 lỗi trên 100 request, 10 request chậm hơn 1 giây), cả ba SLO ví dụ đều bị vi phạm: tỷ lệ thành công 98% chưa đạt 99,5%, p99 bằng 2 giây vượt 1 giây, và 10% request quá chậm vượt xa 1%. Các mục tiêu ở bảng chỉ là ví dụ để luyện tập. Mục tiêu thật do sản phẩm và chi phí quyết định, không phải do công cụ.
Bài Prometheus tiếp theo cho bạn công cụ để tính đúng các phép đo này từ dữ liệu thật của gdev-api, thay vì tính tay bằng awk.
Lỗi thường gặp
| Triệu chứng | Nguyên nhân hay gặp | Cách tránh |
|---|---|---|
| Dashboard xanh nhưng khách báo chậm | Chỉ có health check hoặc chỉ có CPU và RAM | Thêm tỷ lệ lỗi và phân vị độ trễ theo từng đường dẫn |
| Biểu đồ độ trễ đẹp, khách vẫn phàn nàn | Vẽ trung bình thay vì phân vị cao | Vẽ p50 và p99 cạnh nhau |
| Chuông nổ cả đêm, người trực bỏ qua | Cảnh báo theo nguyên nhân hoặc ngưỡng quá nhạy | Chuyển sang cảnh báo triệu chứng, thêm thời gian giữ ngưỡng |
| Biết chậm mà không biết ở đâu | Chỉ có metrics tổng hợp | Thêm traces hoặc ít nhất là log có request_id |
| Không ai biết khi hệ thống giám sát hỏng | Không có cảnh báo cho chính Prometheus | Cảnh báo riêng cho việc mất số liệu |
| Mọi thứ đều có label người dùng | Muốn tra cứu từng người trong metrics | Dùng logs và traces cho từng người; metrics chỉ giữ nhãn có ít giá trị |
Dòng cuối là chủ đề của bài Prometheus tiếp theo: nhãn quá nhiều giá trị làm số chuỗi thời gian nổ tung.
Tự kiểm tra
Hãy trả lời trước khi đọc đáp án ngay bên dưới mỗi câu.
Câu 1. /healthz trả 200 trong 3 mili giây nhưng 10% request /orders mất 2 giây. Black-box check nào bắt được lỗi này?
Đáp án: Check gọi đúng đường /orders và đo thời gian, tức là mô phỏng hành trình người dùng. Check chỉ gọi /healthz không đi qua truy vấn chậm nên không thấy gì.
Câu 2. Thời gian trung bình 0,246 giây và p90 0,054 giây đều ổn. Số liệu nào cho thấy vấn đề, và vì sao trung bình che mất nó?
Đáp án: p99 bằng 2,003 giây. Trung bình trộn 90 request nhanh với 10 request chậm nên kéo con số xuống, còn phân vị cao đo đúng nhóm bị thiệt.
Câu 3. Đội muốn thêm cảnh báo "disk 71%" gọi người trực lúc 3 giờ sáng. Bạn đề xuất gì?
Đáp án: Chuyển thành ticket hoặc dashboard. Đĩa 71% chưa ảnh hưởng người dùng và người trực không có hành động rõ ràng. Chỉ nên gọi người khi dự báo cho thấy đĩa đầy trong vài giờ.
Nếu chỉ nhớ 3 điều
- Health check xanh và trung bình đẹp không chứng minh người dùng ổn; hãy đo tỷ lệ lỗi và phân vị cao theo từng đường dẫn.
- Metrics để phát hiện và định lượng, traces để tìm bước chậm, logs để đọc chi tiết một sự kiện.
- Gọi người trực theo triệu chứng người dùng cảm nhận, dùng nguyên nhân để điều tra, và luôn có cảnh báo cho chính hệ thống giám sát.
Bài tiếp theo là Prometheus: cách một server thu, lưu và truy vấn đúng những số liệu RED này.
Tài liệu chính thức: chương Monitoring Distributed Systems của sách Google SRE, USE Method của Brendan Gregg, RED Method của Tom Wilkie, hướng dẫn cảnh báo của Prometheus và tổng quan Prometheus.