Ba khái niệm về sự cố đáng để biết
Chào mừng
Các hệ thống phân tán gặp sự cố theo các mô hình nhất định. Một khi bạn nắm được các mô hình này, mọi báo cáo phân tích sự cố sẽ trở thành một bài tập nhận diện thay vì một bí ẩn.
Ba khái niệm bao phủ phần lớn những điều quan trọng trong phân tích sự cố môi trường sản xuất:
Điểm lỗi duy nhất (SPOF): một thành phần mà sự cố của nó sẽ kéo sập một hệ thống lớn hơn. Thường bị ẩn đi: máy chủ DNS mà mọi người đều phụ thuộc; chứng chỉ mà mọi thứ đều làm mới dựa vào; hoặc master cơ sở dữ liệu duy nhất.
Sự cố lan truyền (Cascading failure): sự cố của một thành phần kích hoạt sự cố của thành phần khác, và quá trình này tiếp diễn. Một cơ sở dữ liệu chậm gây ra hết thời gian chờ (timeout) ở tầng API, điều này dẫn đến các yêu cầu thử lại (retry), làm tăng tải cho cơ sở dữ liệu, và gây ra thêm nhiều timeout hơn. Mối nguy hiểm lan rộng.
Bán kính ảnh hưởng (Blast radius): mức độ bao trùm của hệ thống bị ngừng hoạt động khi một phần tử gặp sự cố. Các lựa chọn kiến trúc sẽ giới hạn hoặc không giới hạn bán kính này. Một điểm lỗi đơn (SPOF) có bán kính ảnh hưởng không giới hạn. Một dịch vụ được cách ly (bulkhead) có bán kính ảnh hưởng có giới hạn.
Vào cuối bài học này, bạn sẽ:
- Xác định các điểm lỗi đơn (SPOF) trong kiến trúc bằng cách kiểm tra trực quan
- Nhận diện các mẫu sự cố lan truyền: bầy đàn sấm (thundering herd), bão thử lại (retry storm), hàng đợi cái chết (queue of death)
- Đọc một dòng thời gian thực tế & phân biệt giữa yếu tố kích hoạt và khiếm khuyết tiềm ẩn mà yếu tố kích hoạt đã bộc lộ
- Viết các mục hành động không gán lỗi (blameless action items) nhắm vào hệ thống thay vì con người, bao gồm phòng ngừa / phát hiện / phục hồi
- Suy luận về các ngăn cách ly (bulkheads) & công tắc ngắt mạch (circuit breakers) như các công cụ giới hạn bán kính ảnh hưởng
Tìm điểm hỏng hóc đơn lẻ
Kiểm tra kiến trúc phân lớp
Hãy xem xét một kiến trúc web nhỏ:
- DNS: api.example.com -> một địa chỉ IP nameserver duy nhất 203.0.113.10 do một nhà cung cấp DNS duy nhất quản lý
- CDN: một nhà cung cấp CDN duy nhất đặt phía trước api.example.com
- Ingress: hai máy reverse proxy nằm phía sau một bộ cân bằng tải
- Backend: sáu bản sao API nằm trong hai vùng khả dụng (ba bản sao cho mỗi vùng)
- Cơ sở dữ liệu: một primary và một read replica, nằm trong cùng một vùng khả dụng
- Cache: cụm Redis, ba nút được phân bố trên cùng hai vùng khả dụng (availability zones)
Câu hỏi: thành phần nào là điểm hỏng đơn (SPOF)? Gợi ý: SPOF không phải lúc nào cũng là kiểu 'một máy duy nhất' rõ ràng. Một cụm ba máy đều nằm trong cùng một vùng khả dụng là một SPOF đối với sự cố của vùng đó.
Ba Mẫu Chuỗi Điển Hình
Sự cố lan truyền qua các phụ thuộc
Mẫu 1: Bầy đàn sấm sét (Thundering herd). Một tài nguyên chung (bộ nhớ đệm, khóa, cơ sở dữ liệu) gặp sự cố hoặc khởi động lại. Mọi client đã phụ thuộc vào nó đều thử lại (retry) đồng thời. Làn sóng này áp đảo bất kỳ thành phần nào vừa được khôi phục; các yêu cầu thử lại chồng chất nhanh hơn tốc độ hệ thống có thể hấp thụ; quá trình phục hồi không bao giờ hoàn tất.
Mẫu 2: Bão thử lại (Retry storm). Một dịch vụ hạ nguồn bị chậm. Các trình gọi thượng nguồn, thay vì báo lỗi, sẽ thử lại. Các lần thử lại này nhân lên thành tải ban đầu. Dịch vụ chậm trở nên chậm hơn nữa, kích hoạt thêm nhiều lần thử lại. Cuối cùng, tải vượt quá cả khả năng xử lý của phiên bản dịch vụ hoạt động bình thường.
Mẫu 3: Hàng đợi tử thần (Queue of death). Một hàng đợi xử lý không có cơ chế áp suất ngược (backpressure) nhận dữ liệu nhanh hơn tốc độ xử lý. Hàng đợi tăng trưởng không giới hạn. Bộ nhớ cạn kiệt; trình tiêu thụ (consumer) bị sập; khởi động lại; phát hiện hàng đợi vẫn còn lớn hơn; lại sập.
Dây chung: một nhiễu loạn ban đầu nhỏ kích hoạt một vòng lặp phản hồi dương. Phản ứng của chính hệ thống khuếch đại sự cố thay vì triệt tiêu nó.
Các Cơ Chế Giảm Chấn
Hồi quy mũ với nhiễu (jitter). Các client thử lại sẽ chờ lâu hơn mỗi lần, kèm theo độ trễ ngẫu nhiên. Điều này ngăn chặn các đợt thử lại đồng bộ hóa.
Circuit breaker (Cầu chì). Người gọi (caller) theo dõi tỷ lệ lỗi của hệ thống hạ nguồn (downstream). Khi vượt quá ngưỡng, người gọi ngừng gọi trong một thời gian làm mát (cooldown) và ngay lập tức báo lỗi cho các yêu cầu của chính nó. Điều này ngăn chặn việc lãng phí tài nguyên và cho phép hệ thống hạ nguồn phục hồi.
Bulkhead (Khoang cách ly). Cách ly tài nguyên theo từng phụ thuộc. Dùng nhóm kết nối A cho cơ sở dữ liệu, nhóm kết nối B riêng cho bộ nhớ đệm (cache). Cơ sở dữ liệu chậm không thể làm cạn kiệt tất cả các kết nối; các cuộc gọi đến cache vẫn tiếp tục.
Load shedding (Giảm tải). Khi quá tải, từ chối các yêu cầu ngay tại biên (edge) thay vì chấp nhận chúng và xử lý chậm chạp. Một mã 429 trong 1 ms tốt hơn một mã 500 trong 30 giây.
Backpressure (Áp suất ngược). Làm chậm các nhà sản xuất (producers) khi người tiêu thụ (consumers) không theo kịp. Hàng đợi trở nên có giới hạn; người gửi bị chặn; nguồn gốc ban đầu của khối lượng công việc cảm nhận được sự ma sát này.
Chẩn đoán Suy giảm Dây chuyền
Một nhóm API sụp đổ trong quá trình chuyển đổi dự phòng (failover) cơ sở dữ liệu thường kỳ. Dòng thời gian:
- 14:00:00 — Người vận hành thăng cấp cơ sở dữ liệu dự phòng (standby). Thời gian không khả dụng dự kiến: ~10 giây.
- 14:00:08 — Máy chủ chính (primary) không khả dụng. Các yêu cầu từ tầng API bắt đầu thất bại với lỗi kết nối cơ sở dữ liệu.
- 14:00:08 — Tầng API thử lại (cấu hình mặc định: 5 lần thử lại, không có backoff, cách nhau 100ms).
- 14:00:11 — Máy chủ dự phòng được thăng cấp, bắt đầu chấp nhận các kết nối mới.
- 14:00:11 — Tầng API mở hàng nghìn kết nối cơ sở dữ liệu mới đồng thời (mọi bản sao × mọi yêu cầu đồng thời × mọi lần thử lại).
- 14:00:13 — Hết hạn mức kết nối (connection pool) của máy chủ chính mới; các kết nối mới bị từ chối.
- 14:00:13-14:05:00 — Các bản sao của tầng API cạn kiệt hạn mức kết nối, ném ngoại lệ, bị sập, khởi động lại, và lặp lại.
- 14:05:00 — Người vận hành thủ công dừng lưu lượng của tầng API; cơ sở dữ liệu ổn định.
- 14:10:00 — quá trình khôi phục lưu lượng truy cập dần hoàn tất. Tổng thời gian gián đoạn: ~10 phút (so với dự kiến ~10 giây).
DNS SERVFAIL: Hai khiếm khuyết chồng chéo
Một bài học thực tế
Dưới đây là phiên bản đã được ẩn danh của một sự cố thực tế. Tên nhà cung cấp đã được thay đổi, địa chỉ IP đã được ẩn danh; nhưng hình thái, dòng thời gian & các bài học rút ra là có thật.
Tóm tắt
Trang web example.com đã trả về SERVFAIL từ tất cả các trình phân giải DNS công khai trong khoảng 3-4 giờ. Tất cả 46 zone khác trên cùng một máy chủ DNS chính (master) không bị ảnh hưởng. Nguyên nhân gốc rễ: hai khiếm khuyết chồng chéo.
1. Nhà cung cấp A (một nhà cung cấp DNS phụ) đã thêm một địa chỉ IP đồng bộ nội bộ mới không có trong danh sách cho phép allow-axfr-ips của máy chủ chính.
2. Zone example.com có một xung đột CNAME vi phạm RFC tồn tại từ nhiều năm trước (demo.example.com có cả bản ghi CNAME & MX/TXT tại cùng một nhãn) khiến Nhà cung cấp A từ chối zone này khi thực hiện AXFR mới.
Dòng thời gian (UTC)
- ~15:00 — Nhà cung cấp A thêm IP đồng bộ mới 198.51.100.42 vào hạ tầng của họ
- 15:02 — thông báo AXFR-out denied đầu tiên cho 198.51.100.42 xuất hiện trong nhật ký DNS chính (không có cảnh báo cho tín hiệu này)
- ~18:00 — cửa sổ hết hạn SOA đã đạt; Nhà cung cấp A loại bỏ vùng example.com khỏi bộ nhớ đệm
- ~18:30 — SERVFAIL được phát hiện từ bên ngoài
- ~19:45 — nguyên nhân gốc rễ được xác định
- 20:00 — 198.51.100.42 được thêm vào allow-axfr-ips; máy chủ chính được khởi động lại
- 20:05 — NOTIFY được gửi; AXFR được khởi tạo; vùng VẪN SERVFAIL (xung đột CNAME)
- 20:07 — check-zone hiển thị 1 lỗi: xung đột CNAME trên demo.example.com
- 20:09 — CNAME đã được thay thế bằng bản ghi A; kiểm tra zone sạch (0 lỗi)
- 20:10 — NOTIFY đã được gửi; AXFR hoàn tất; Nhà cung cấp A bắt đầu cung cấp zone
- 20:11 — dig @8.8.8.8 example.com A trả về IP chính xác — ĐÃ GIẢI QUYẾT
Tại sao chỉ có example.com?
Tất cả 47 zone đều chia sẻ cùng một DNS primary. Việc chặn IP của AXFR đã ảnh hưởng đến tất cả các zone. Nhưng chỉ example.com có xung đột CNAME, & chỉ example.com cần một AXFR mới vào thời điểm lệnh từ chối được thực thi. Các zone khác đã được làm mới trước khi bị chặn hoặc chưa cần làm mới.
Lỗi tiềm ẩn
Xung đột CNAME tại demo.example.com đã tồn tại trong nhiều năm. Nó hoạt động được vì primary cung cấp zone từ cơ sở dữ liệu của nó (thoải mái với các vi phạm RFC) & Nhà cung cấp A đang cung cấp từ dữ liệu cache cũ từ trước khi vi phạm được đưa vào. Khi Nhà cung cấp A xóa cache của mình & cần dữ liệu mới, vi phạm đã lộ ra.
Kích hoạt
Nhà cung cấp A đã âm thầm thêm một địa chỉ IP đồng bộ hóa mới. Danh sách cho phép (allowlist) của máy chủ chính không bao gồm địa chỉ này. Yêu cầu AXFR bị từ chối. Ba giờ sau (khi hết hạn SOA), Nhà cung cấp A đã xóa vùng (zone). Khiếm khuyết tiềm ẩn đã bộc lộ khi hệ thống cố gắng khôi phục.
Viết các mục hành động không gán lỗi
Không gán lỗi: Hướng đến Hệ thống, không phải Con người
Một mục hành động không gán lỗi chỉ ra điều gì đó mà hệ thống nên làm khác đi, chứ không phải điều gì đó mà một con người nên làm khác đi. 'Đào tạo cho người vận hành' là có gán lỗi. 'Thêm một kiểm tra tự động để phát hiện điều này trước khi triển khai' là không gán lỗi.
Các mục hành động không gán lỗi tốt thường được phân nhóm thành ba chiều:
- Phòng ngừa: khiến điều xấu trở nên khó xảy ra hơn hoặc không thể xảy ra
- Phát hiện: nhận ra sự cố sớm hơn nếu nó xảy ra
- Khôi phục: hạn chế thiệt hại khi sự cố xảy ra
Mỗi mục cần nêu rõ (1) thay đổi hệ thống cụ thể, (2) đội ngũ chịu trách nhiệm, và (3) chiều kích mà nó phục vụ.
Các khoang ngăn chìm không kéo theo con tàu
Mượn từ kỹ thuật hàng hải
Tàu chở hàng có các vách ngăn kín nước: những bức tường dọc chia thân tàu thành các khoang riêng biệt. Một khoang có thể bị ngập mà không làm chìm cả con tàu; một khoang khác có thể hỏng mà không ảnh hưởng đến phần còn lại.
Các hệ thống phân tán mượn cùng một thuật ngữ & cùng một ý tưởng.
Mô hình vách ngăn (Bulkhead pattern): cách ly tài nguyên theo từng phụ thuộc. Một dịch vụ gọi ba API hạ nguồn sẽ sử dụng ba nhóm kết nối (connection pool) riêng biệt, ba nhóm luồng (thread pool) riêng biệt, và ba ngân sách thử lại (retry budget) riêng biệt. Một phụ thuộc hạ nguồn chậm hoặc lỗi không thể tiêu thụ tài nguyên được cấp phát cho hai phụ thuộc còn lại.
Không có vách ngăn: một phụ thuộc chậm làm cạn kiệt nhóm luồng dùng chung; các cuộc gọi đến các phụ thuộc khác bị chặn chờ luồng; toàn bộ dịch vụ trở nên không phản hồi.
Có vách ngăn: một phụ thuộc chậm làm cạn kiệt nhóm của chính nó; các cuộc gọi đến nó thất bại nhanh (fail fast); các cuộc gọi đến các phụ thuộc khác tiếp tục hoạt động bình thường; phạm vi ảnh hưởng (blast radius) được giới hạn trong phạm vi của phụ thuộc bị lỗi.
Cầu ngắt mạch (Circuit Breakers)
Mô hình ngắt mạch (Circuit breaker pattern): một lớp bao bọc có trạng thái (stateful wrapper) xung quanh một phụ thuộc hạ nguồn (downstream dependency) để theo dõi tỷ lệ thất bại. Ba trạng thái:
- Đóng (bình thường): các cuộc gọi được truyền qua. Các lần thất bại được đếm.
- Mở (bị ngắt): khi vượt qua ngưỡng thất bại (ví dụ, 50% thất bại trong 30 giây gần nhất), mạch sẽ mở ra. Các cuộc gọi sẽ thất bại ngay lập tức mà không thử gọi đến phụ thuộc. Điều này giúp người gọi tránh lãng phí công sức; đồng thời giúp phụ thuộc tránh nhận tải khi nó đang không khỏe mạnh.
- Nửa mở (đang kiểm tra): sau một khoảng thời gian làm mát (cooldown), mạch cho phép một phần nhỏ các cuộc gọi đi qua. Nếu chúng thành công, mạch sẽ đóng lại về trạng thái bình thường. Nếu chúng thất bại, mạch sẽ mở lại cho một chu kỳ làm mát tiếp theo.
Insight quan trọng: ngắt mạch ngăn chặn sự lãng phí công sức trong các khoảng thời gian đã biết là không khỏe mạnh, & cho phép phụ thuộc hạ nguồn có cơ hội phục hồi mà không bị tải liên tục.
Bulkheads giới hạn bán kính nổ (blast radius). Ngắt mạch ngăn chặn vụ nổ tự duy trì.
Giới hạn bán kính nổ
Dịch vụ API của bạn gọi bốn dịch vụ hạ nguồn: Dịch vụ Người dùng, Dịch vụ Gợi ý, Dịch vụ Thông báo, & API Thanh toán của bên thứ ba. Đội ngũ đã nghe tin 'Dịch vụ Gợi ý đã có chút bất ổn' & muốn đảm bảo rằng khi nó thất bại, phần còn lại của hệ thống vẫn hoạt động khỏe mạnh.
Hôm nay, dịch vụ này sử dụng một nhóm thread (thread pool) dùng chung duy nhất gồm 200 thread & một nhóm kết nối HTTP (HTTP connection pool) dùng chung duy nhất. Cả bốn hệ thống hạ nguồn (downstreams) đều cạnh tranh để sử dụng các tài nguyên này. Không có bộ ngắt mạch (circuit breaker) nào được triển khai.
Thiết kế Phiên Đánh Giá Chế Độ Hỏng Hỏng
Tổng hợp
Bạn đã học cách phát hiện các điểm hỏng đơn (SPOF) bằng kiểm tra trực quan, nhận diện các mô hình hỏng hổng dây chuyền, phân biệt giữa tác nhân kích hoạt và khiếm khuyết tiềm ẩn khi đọc báo cáo hậu sự cố, viết các mục hành động không đổ lỗi (blameless) xuyên suốt phòng ngừa / phát hiện / phục hồi, & giới hạn bán kính ảnh hưởng (blast radius) bằng các vách ngăn (bulkheads) + bộ ngắt mạch (circuit breakers) + suy giảm chức năng có kiểm soát (graceful degradation).
Áp dụng cả năm kỹ năng.
Đội ngũ của bạn đang ra mắt một dịch vụ mới search.example.com phụ thuộc vào ba dịch vụ hạ nguồn: một chỉ mục tìm kiếm chính (index.example.com), một dịch vụ phân tích (analytics.example.com), & một dịch vụ gợi ý (recs.example.com). Đội ngũ muốn bạn dẫn dắt một 'phiên đánh giá chế độ hỏng hỏng' (failure-mode review) trước khi ra mắt.
Khóa học này sẽ tiếp tục đi đâu
Khóa học này sẽ tiếp tục đi đâu
Bây giờ bạn có thể phát hiện một điểm hỏng hóc đơn lẻ (SPOF), nhận diện một sự lan truyền (cascade), đọc một báo cáo hậu sự cố (postmortem) một cách hiệu quả, viết các mục tiêu hành động không đổ lỗi (blameless action items), & giới hạn phạm vi ảnh hưởng (blast radius) bằng thiết kế.
Bài học cuối cùng trong khóa học này (cs_distsys_observability_and_capacity) dạy bạn đo lường những gì để phát hiện vấn đề đang xảy ra trước khi người dùng nhận ra. Các kiểm tra sức khỏe (health checks), các điểm cuối phiên bản (version endpoints), bốn tín hiệu vàng (four golden signals) ở tầng proxy, & cách các quyết định về dung lượng dự phòng (surge capacity) liên kết ngược lại với dữ liệu quan sát được.
Bài học đồng hành: geometry_of_failure_modes_and_blast_radius suy ra độ trung tâm giữa (betweenness centrality) (nút đồ thị nào là nút thắt) & cắt nhỏ nhất (min-cut) (giới hạn của phạm vi ảnh hưởng).
Làm tốt lắm. Tiếp tục tiến lên.