High Availability: SPOF, failover, DR và quan sát hệ thống
Tiếp theo: High Availability cho shop-app.
shop-app giờ có gateway, LB, nhiều instance, Redis, DB. Bài này hỏi một câu duy nhất cho mọi box: "Box này chết thì sao?"
1. Bài toán
Clients
│
API Gateway
│
Load Balancer
/ | \
A1 A2 A3 A1 💀 → không sao, LB chuyển sang A2/A3
\ | /
Redis
│
Database 💀 → A1, A2, A3 đều lỗi. 100 instance cũng vô dụng.
2. Vấn đề: Single Point of Failure
SPOF là thành phần mà khi nó chết thì cả hệ thống, hoặc một chức năng quan trọng, ngừng hoạt động. 1 LB, 1 gateway, 1 Redis, 1 DB, 1 Kafka broker đều có thể là SPOF. Và còn SPOF ẩn: 3 instance nhưng cùng nằm trên một máy vật lý → máy chết là cả 3 chết.
3. Giải pháp
Redundancy: có thành phần dự phòng (A1/A2/A3, Primary + Replica, Gateway ×3).
Failover: chuyển sang thành phần dự phòng khi cần. Có DB2 mà app vẫn trỏ vào DB1 đã chết thì DB2 vô ích. Cần: phát hiện DB1 chết → promote DB2 → chuyển traffic.
Redundancy = có đồ dự phòng
Failover = biết chuyển sang đồ dự phòng
Active-passive vs active-active:
Active-passive: Primary (nhận traffic) ── replicate ──► Standby (chờ)
Primary chết → promote Standby (mất vài giây–phút)
Active-active: Node A ◄── traffic ──► Node B (cùng phục vụ)
A chết → B gánh hết (phải đủ capacity dư, khó với write)
Health check: process còn sống chưa chắc phục vụ được (connection pool cạn, cache chưa load). Tách 2 loại:
- Liveness: "Có sống không?" Sai → restart.
- Readiness: "Có nhận traffic được không?" Sai → LB/K8s ngừng gửi request, không restart.
Failure domain / Availability Zone: rải instance ra nhiều AZ trong region; Zone A chết, B và C vẫn phục vụ.
Disaster Recovery: cả region chết → backup/restore, replication sang region B. Hai con số:
- RTO (time): bao lâu hệ thống phải sống lại? Ví dụ 30 phút.
- RPO (data): chấp nhận mất bao nhiêu dữ liệu? Backup 09:55, sự cố 10:00 → mất 5 phút data → RPO 5 phút.
4. Code từng bước: health check trong Spring Boot
Bước 1: dependency spring-boot-starter-actuator (và micrometer-registry-prometheus cho metrics).
Bước 2: config probe. Readiness gồm DB; liveness không gồm DB (DB chết mà restart hết app thì chỉ làm tệ hơn).
management:
endpoint:
health:
probes:
enabled: true
group:
readiness:
include: readinessState, db, redis
liveness:
include: livenessState
endpoints:
web:
exposure:
include: health, prometheus
Bước 3: Kubernetes dùng probe.
livenessProbe:
httpGet: { path: /actuator/health/liveness, port: 8080 }
periodSeconds: 10
readinessProbe:
httpGet: { path: /actuator/health/readiness, port: 8080 }
periodSeconds: 5
topologySpreadConstraints: # rải pod qua nhiều zone
- maxSkew: 1
topologyKey: topology.kubernetes.io/zone
whenUnsatisfiable: ScheduleAnyway
labelSelector: { matchLabels: { app: order-service } }
Bước 4: test.
curl localhost:8080/actuator/health/readiness # {"status":"UP"}
# tắt DB
curl localhost:8080/actuator/health/readiness # {"status":"OUT_OF_SERVICE"} → LB bỏ instance
curl localhost:8080/actuator/health/liveness # vẫn UP → không bị restart vô ích
5. Cách hoạt động: biết hệ thống đang ra sao (observability)
HA không chỉ là dự phòng; phải phát hiện được lỗi. User báo "checkout mất 8 giây". Log mỗi service đều có, nhưng 10,000 req/s thì log nào của request đó?
METRICS "Có gì đó sai" Payment error rate 0.1% → 15% 🔥
↓
TRACES "Sai ở đâu" Order 50ms → Payment 8s → Bank 7.8s 🔥
↓
LOGS "Chính xác chuyện gì" [traceId=abc123] Bank returned 503, timeout
- Metrics: số được aggregate theo thời gian. Tối thiểu: request rate, error rate, latency (+ CPU, memory, thread pool, DB pool, Kafka lag).
- Percentile: 99 request 100 ms + 1 request 10 s → average trông ổn, nhưng user ở tail rất khổ. p95 = 500 ms nghĩa ~95% request ≤ 500 ms. 1 triệu request/ngày thì 1% vẫn là 10,000 request → p99 đáng quan tâm.
- Traces: trace = cả request, span = từng bước (có parent). Trace ID được propagate qua HTTP header và message header Kafka. Chi tiết: Distributed tracing.
- Alerting:
error_rate > 5% trong 5 phút,p95 > 2s,DB pool > 90%. Alert phải actionable; alert CPU > 70%, 71%, 72%... sinh alert fatigue, rồi mọi người bỏ qua cả alert thật. Ưu tiên alert theo triệu chứng user thấy (SLO), xem 15.2.
6. So sánh
| Khái niệm | Mục tiêu | Ví dụ |
|---|---|---|
| High Availability | Giảm downtime, có thể gián đoạn ngắn | Primary chết, 10 giây failover rồi chạy lại |
| Fault Tolerance | Lỗi xảy ra mà gần như không gián đoạn | Node1 chết, Node2/Node3 gánh, user không thấy |
| Disaster Recovery | Phục hồi sau thảm họa lớn (cả region) | Restore từ backup sang region B, theo RTO/RPO |
| Active-passive | Active-active | |
|---|---|---|
| Failover | Có khoảng gián đoạn | Gần như tức thì |
| Chi phí | Standby ngồi không | Dùng hết tài nguyên, phải dư capacity |
| Độ khó | Dễ hơn, hợp DB write | Khó với write (xung đột dữ liệu) |
| Health check | Monitoring | |
|---|---|---|
| Hỏi | Instance này nhận traffic được không? | Hệ thống đang chạy thế nào? |
| Ai dùng | LB, Kubernetes | Con người, dashboard, alert |
7. Khi nào nên / không nên
- Mỗi box: hỏi "chết thì sao?"; câu trả lời "cả hệ thống chết" = SPOF cần xử lý.
- Multi-AZ gần như mặc định cho production; multi-region chỉ khi RTO/RPO và business đòi hỏi, vì đắt và phức tạp (replication xuyên region, consistency).
- Đừng để liveness phụ thuộc dependency ngoài; sẽ restart dây chuyền.
- Backup mà chưa từng thử restore thì chưa phải DR.
High availability với fault tolerance khác nhau thật không? Hay chỉ là hai cách gọi của cùng một thứ?
Hai từ hay được dùng gần nhau, nhưng để phỏng vấn nên phân biệt theo mục tiêu:
HA : failure → gián đoạn ngắn → failover → chạy lại
(minimize downtime)
Fault tolerance : failure → hệ thống tiếp tục chạy, user hầu như không thấy
(no meaningful interruption)
Ví dụ: PostgreSQL primary chết, mất 10–30 giây để promote replica → HA. Ba app instance stateless sau LB, một cái chết, request tiếp theo vào cái còn lại → gần với fault tolerance. Không nên nói mọi hệ thống HA đều fault tolerant; fault tolerance thường đắt hơn nhiều.
Load balancer đã có health check rồi, vậy mình còn cần monitoring và alert làm gì nữa?
Cần, vì hai thứ trả lời hai câu hỏi khác nhau. Health check tự động loại một instance hỏng. Nhưng nếu cả Payment chậm vì Bank chậm, mọi instance vẫn "healthy" mà p95 checkout tăng từ 300 ms lên 8 s. Health check không thấy; metrics và alert mới thấy.
Bank chậm
→ Tomcat threads 190/200 🔥 (metrics)
→ p95 checkout 8s 🔥 (alert)
→ circuit breaker OPEN (metrics)
→ trace abc123: Bank 7.8s (trace)
→ log: Bank timeout after 2s (log)
Observability giúp bạn nhìn thấy cascading failure đang hình thành trước khi cả hệ thống sập.
Phỏng vấn hỏi "làm sao tăng availability" và "monitor microservices thế nào" thì trả lời sao?
Q1. How would you improve the availability of a system?
🇻🇳 Tiếng Việt:
Đầu tiên em tìm các single point of failure. Với thành phần quan trọng, em thêm redundancy và cơ chế failover: app stateless chạy nhiều instance sau load balancer có health check liveness và readiness, database có replica và failover tự động. Em rải instance qua nhiều availability zone để một máy hay một zone chết không kéo sập hệ thống. Với thảm họa lớn hơn, em dùng backup và phục hồi cross-region, đặt RTO và RPO theo yêu cầu business.
🇬🇧 English:
First I identify single points of failure. For critical components I add redundancy and failover: stateless application instances behind a load balancer with liveness and readiness checks, and a database with replicas and automatic failover. I spread instances across availability zones so a single machine or zone failure doesn't take the system down. For larger disasters I use backups and cross-region recovery, with RTO and RPO defined by business requirements.
Q2. RTO và RPO là gì?
🇻🇳 Tiếng Việt:
RTO là thời gian tối đa chấp nhận để hệ thống phục hồi sau sự cố. RPO là lượng dữ liệu tối đa chấp nhận mất, tính bằng thời gian, ví dụ backup mỗi 5 phút thì RPO khoảng 5 phút. RTO và RPO càng nhỏ thì giải pháp càng đắt, như replication liên tục hay active-active.
🇬🇧 English:
RTO is the maximum acceptable time to restore the service after an incident. RPO is the maximum acceptable data loss measured in time; for example, backups every 5 minutes give an RPO of about 5 minutes. The smaller the RTO and RPO, the more expensive the solution, such as continuous replication or active-active.
Q3. How would you monitor a microservices system?
🇻🇳 Tiếng Việt:
Em dùng metrics, logs và distributed tracing. Metrics theo dõi request rate, error rate, latency theo percentile p95, p99, cùng tài nguyên như thread pool, DB pool, Kafka lag, có dashboard và alert theo SLO, tránh alert vụn gây alert fatigue. Logs có cấu trúc, chứa trace ID. Tracing propagate trace ID qua HTTP và message để thấy request chậm ở service nào.
🇬🇧 English:
I use metrics, logs and distributed tracing. Metrics cover request rate, error rate and latency percentiles such as p95 and p99, plus resources like thread pools, DB pools and Kafka lag, with dashboards and SLO-based alerts, avoiding noisy alerts that cause alert fatigue. Logs are structured and include the trace ID. Tracing propagates the trace ID across HTTP calls and messages so we can see which service makes a request slow.
Cách nhớ ngắn gọn
- Mỗi box: "chết thì sao?" → SPOF → redundancy + failover.
- Liveness = có sống; readiness = nhận traffic được. Rải qua nhiều AZ.
- HA: gián đoạn ngắn; FT: gần như không; DR: cả region. RTO = thời gian, RPO = dữ liệu.
- Metrics: có gì sai; traces: sai ở đâu; logs: chuyện gì. Nhìn p95/p99, không chỉ average.
Tự kiểm tra
Có 2 DB nhưng app vẫn lỗi khi DB1 chết. Thiếu gì?
Thiếu failover: phát hiện lỗi, promote DB2, chuyển kết nối sang DB2. Redundancy thôi chưa đủ.
Vì sao không đưa DB vào liveness probe?
DB chết thì mọi pod fail liveness và bị restart liên tục, không giúp gì mà còn tệ hơn. DB nên thuộc readiness.
Backup 09:55, sự cố 10:00, phục hồi xong 10:30. RTO và RPO thực tế?
RTO ≈ 30 phút (thời gian phục hồi), RPO ≈ 5 phút (dữ liệu mất).
Vì sao nhìn p99 thay vì average latency?
Average che giấu tail latency; 1% request rất chậm vẫn là hàng nghìn user mỗi ngày.
Alert fatigue là gì, tránh thế nào?
Quá nhiều alert vô nghĩa khiến người trực bỏ qua alert. Chỉ alert khi actionable, theo triệu chứng user thấy (SLO), có ngưỡng thời gian.