Chương 0. Tấm Bản Đồ Chung
Kiến trúc hệ thống rất dễ làm người học bị ngợp. Chỉ cần nhìn vào một hệ thống hiện đại, ta đã thấy hàng loạt khái niệm: DNS, CDN, reverse proxy, load balancer, API Gateway, database, cache, queue, worker, Redis, Kafka, Docker, Kubernetes, observability, tracing, SLO, circuit breaker, rate limit, service mesh.
Nếu học từng công nghệ rời rạc, ta rất dễ rơi vào cảm giác "biết nhiều tên nhưng không biết dùng khi nào". Mục tiêu của chương này là dựng một tấm bản đồ chung: mỗi thành phần nằm ở đâu, giải quyết vấn đề gì, và khi hệ thống gặp một loại vấn đề thì nên nhìn vào vùng nào của bản đồ.
Cuốn sách này không xem kiến trúc là cuộc đua dùng công nghệ mới. Kiến trúc tốt là cách kiểm soát độ phức tạp để hệ thống chạy được, đo được, debug được, mở rộng được, và vẫn đủ đơn giản để con người vận hành.
---
0.1. Vì sao cần một bản đồ trước khi học chi tiết?
Một người mới học kiến trúc thường hỏi theo kiểu:
- Có nên dùng microservices không?
- Redis khác Kafka thế nào?
- Dùng FastAPI hay Django?
- Khi nào cần Kubernetes?
- CDN, proxy, load balancer, API Gateway khác nhau ra sao?
- Message queue có phải thứ quan trọng nhất trong microservices không?
Những câu hỏi này đều đúng, nhưng nếu trả lời riêng lẻ thì rất dễ lạc. Ví dụ, Kafka không thể được hiểu đúng nếu chưa hiểu queue, Pub/Sub, event log, replay và data pipeline. Kubernetes cũng không nên học như một "công nghệ deploy", mà phải đặt trong bài toán orchestration, scaling, self-healing và vận hành nhiều container.
Bản đồ giúp ta biết:
- Thành phần này thuộc tầng nào của hệ thống.
- Nó giải quyết vấn đề gì.
- Nó thay thế hay bổ sung cho thành phần nào.
- Khi nào nó là cần thiết.
- Khi nào nó là quá mức.
Một hệ thống tốt thường không phải hệ thống dùng nhiều công nghệ nhất, mà là hệ thống dùng vừa đủ công nghệ cho áp lực hiện tại, đồng thời không tự khóa đường tiến hóa sau này.
---
0.2. Hệ thống hiện đại gồm những tầng nào?
Có thể nhìn một hệ thống web hiện đại qua các tầng sau.
Tầng người dùng
Đây là nơi request bắt đầu:
- Browser
- Mobile app
- Desktop app
- Third-party client
- Bot hoặc automated client
Tầng này quan tâm đến trải nghiệm người dùng: trang có tải nhanh không, thao tác có phản hồi không, realtime có mượt không, lỗi có được hiển thị dễ hiểu không.
Tầng biên
Đây là lớp đứng giữa internet và hệ thống backend:
- DNS
- CDN
- Firewall
- WAF
- Reverse proxy
- Load balancer
Tầng biên giúp hệ thống nhận request an toàn và hiệu quả hơn. Nó có thể xử lý TLS, chặn request độc hại, cache file tĩnh, phân phối request đến nhiều server, hoặc bảo vệ origin server khỏi traffic quá lớn.
Tầng giao tiếp
Đây là cách các thành phần nói chuyện với nhau:
- HTTP API
- REST
- GraphQL
- gRPC
- Webhook
- Polling
- SSE
- WebSocket
- Message queue
- Pub/Sub
- Event streaming
Chọn sai cách giao tiếp là một trong những nguyên nhân phổ biến làm hệ thống chậm hoặc khó debug. Một request cần trả lời ngay có thể dùng API. Một tác vụ dài nên đi qua queue. Một cập nhật realtime có thể dùng WebSocket hoặc SSE. Một dòng sự kiện cần lưu lại và replay có thể cần Kafka hoặc hệ event streaming tương tự.
Tầng ứng dụng
Đây là nơi chứa logic nghiệp vụ:
- Monolith
- Modular monolith
- Microservices
- Backend service
- API service
- Internal service
Đây là tầng nhiều người hay tranh luận nhất, nhưng cũng dễ bị hiểu sai nhất. Microservices không làm hệ thống tự động tốt hơn. Nó chỉ có ích khi ta thật sự cần tách deploy, tách scale, tách failure boundary, tách team hoặc tách quyền sở hữu dữ liệu.
Tầng xử lý nền
Không phải việc gì cũng nên xử lý trong request người dùng. Các việc lâu hoặc dễ lỗi nên được đưa ra nền:
- Queue
- Worker
- Scheduler
- Cron job
- Workflow engine
- Batch job
Ví dụ: gửi email, xử lý video, import file lớn, generate report, gọi API AI mất lâu, đồng bộ dữ liệu sang hệ thống khác.
Tầng này giúp request trả nhanh hơn, hệ thống chịu tải tốt hơn, và lỗi tạm thời có thể được retry.
Tầng dữ liệu
Đây là nơi hệ thống lưu và truy xuất dữ liệu:
- Relational database
- NoSQL database
- Cache
- Search engine
- Object storage
- Data warehouse
- Data lake
Không nên xem mọi nơi lưu dữ liệu là "database giống nhau". PostgreSQL/MySQL phù hợp dữ liệu nghiệp vụ cần đúng. Redis phù hợp cache, counter, lock hoặc queue nhẹ. Elasticsearch/OpenSearch phù hợp tìm kiếm. Object storage phù hợp file lớn. Warehouse phù hợp phân tích.
Tầng vận hành
Đây là cách hệ thống được chạy và triển khai:
- Linux server
- Docker
- Docker Compose
- CI/CD
- Kubernetes
- Cloud service
- Managed database
- Managed queue
Tầng vận hành quyết định hệ thống có dễ deploy, rollback, scale, restart và phục hồi sau lỗi hay không.
Tầng quan sát
Không đo thì chỉ đoán. Tầng quan sát gồm:
- Logs
- Metrics
- Traces
- Alerts
- Dashboards
Nếu không có quan sát, mọi cuộc tranh luận kiến trúc sẽ dễ biến thành cảm tính: "tôi nghĩ database chậm", "tôi nghĩ framework này yếu", "tôi nghĩ phải tách service". Một hệ thống tốt phải cho ta thấy nó đang nghẽn ở đâu.
Tầng bảo vệ
Tầng này giữ hệ thống an toàn:
- Authentication
- Authorization
- Secret management
- Audit log
- Rate limiting
- Network boundary
- Encryption
Bảo mật không chỉ là đăng nhập. Nó còn là ai được xem dữ liệu nào, service nào được gọi service nào, API key nằm ở đâu, file upload có nguy hiểm không, request lạ có bị giới hạn không.
---
0.3. Một request đi qua bản đồ như thế nào?
Hãy hình dung một request đơn giản: người dùng mở một trang trong ứng dụng.
Luồng có thể diễn ra như sau:
1. Browser gửi request. 2. DNS phân giải domain thành địa chỉ hệ thống. 3. CDN có thể trả file tĩnh ngay nếu đã cache. 4. Request đi qua firewall/WAF để lọc traffic nguy hiểm. 5. Reverse proxy hoặc load balancer nhận request. 6. Request được chuyển đến backend phù hợp. 7. Backend xác thực người dùng. 8. Backend đọc cache nếu có. 9. Nếu cache không có, backend query database. 10. Backend có thể gọi service khác nếu cần. 11. Backend trả response. 12. Logs, metrics, traces ghi lại toàn bộ đường đi.
Nếu request này kích hoạt một việc lâu, ví dụ xử lý file hoặc gọi API ngoài mất nhiều thời gian, backend không nên đứng chờ trong request chính. Nó nên:
1. Lưu trạng thái ban đầu vào database. 2. Đẩy job vào queue. 3. Trả về cho người dùng rằng hệ thống đã nhận việc. 4. Worker xử lý job ở nền. 5. Khi xong, worker lưu kết quả. 6. Frontend biết kết quả qua polling, SSE, WebSocket hoặc notification.
Điểm quan trọng: cùng là "xử lý một hành động", nhưng hệ thống có thể chọn trả lời ngay hoặc xử lý sau. Kiến trúc tốt nằm ở chỗ biết phân biệt hai loại việc này.
---
0.4. Ba loại việc trong hệ thống
Phần lớn quyết định kiến trúc trở nên dễ hơn nếu ta phân loại việc trong hệ thống thành ba nhóm.
Việc cần trả lời ngay
Ví dụ:
- Đăng nhập
- Lấy thông tin user
- Xem danh sách sản phẩm
- Mở trang chi tiết
- Kiểm tra quyền truy cập
Loại việc này thường dùng API trực tiếp. Nó cần latency thấp, timeout rõ ràng, và không nên phụ thuộc vào tác vụ lâu.
Việc có thể xử lý sau
Ví dụ:
- Gửi email
- Tạo báo cáo PDF
- Xử lý video
- Transcribe audio
- Import dữ liệu lớn
- Gọi API AI mất lâu
Loại việc này nên dùng queue và worker. Người dùng không nhất thiết phải chờ kết quả ngay trong HTTP request.
Việc cần phát cho nhiều nơi biết
Ví dụ:
- Đơn hàng đã thanh toán
- User vừa đăng ký
- File đã xử lý xong
- Bài chấm đã hoàn tất
- Có event hành vi cần đưa vào analytics
Loại việc này có thể dùng event, Pub/Sub hoặc event streaming. Một event có thể được nhiều consumer xử lý: gửi notification, cập nhật dashboard, ghi analytics, đồng bộ search index.
Sai lầm phổ biến là dùng một kiểu giao tiếp cho mọi loại việc. Ví dụ, bắt HTTP request chờ một việc 90 giây, hoặc dùng Pub/Sub không bền cho job quan trọng, hoặc đưa Kafka vào chỉ để gửi vài email.
---
0.5. Bản đồ chọn công cụ nhanh
Đây là bản đồ ngắn để định hướng ban đầu.
Cần trả lời ngay
Dùng:
- HTTP API
- REST
- GraphQL
- gRPC nếu cần contract chặt và performance cao
Tránh:
- Chờ tác vụ dài trong request
- Retry vô hạn
- Không có timeout
Việc lâu
Dùng:
- Message queue
- Worker
- Background job
- Workflow engine nếu luồng nhiều bước và phức tạp
Tránh:
- Xử lý trong web request
- Không lưu trạng thái job
- Retry nhưng không idempotent
Cần realtime
Dùng:
- Polling nếu đơn giản và tải thấp
- SSE nếu server chỉ cần đẩy một chiều
- WebSocket nếu cần hai chiều realtime
Tránh:
- WebSocket cho mọi thứ chỉ vì nghe hiện đại
- Không có chiến lược scale connection
Nhiều bên cùng cần biết một sự kiện
Dùng:
- Pub/Sub
- Event bus
- Message broker có durable subscription nếu event quan trọng
Tránh:
- Import trực tiếp module của nhau quá sâu
- Gọi dây chuyền nhiều service trong cùng một request
Cần lưu lịch sử sự kiện và replay
Dùng:
- Kafka
- Event streaming platform
Tránh:
- Dùng Kafka quá sớm khi chỉ cần queue
- Không hiểu partition, offset, retention, consumer group
Cần đọc nhanh
Dùng:
- Cache
- CDN
- Read replica trong một số trường hợp
Tránh:
- Cache dữ liệu mà không biết khi nào xóa
- Xem cache là source of truth
Cần tìm kiếm tốt
Dùng:
- Search engine như Elasticsearch/OpenSearch
Tránh:
- Ép database làm full-text search phức tạp khi dữ liệu lớn
- Quên rằng search index có thể chậm đồng bộ
Cần giao file lớn
Dùng:
- Object storage
- CDN
- Presigned URL
Tránh:
- Đẩy file lớn qua web server nếu không cần
- Lưu file lớn trong database
---
0.6. Bản đồ tiến hóa kiến trúc
Kiến trúc nên tiến hóa theo áp lực thật. Không nên bắt đầu bằng kiến trúc của công ty có hàng trăm triệu người dùng nếu sản phẩm còn chưa có người dùng đầu tiên.
Giai đoạn 1: Monolith rõ ràng
Đặc điểm:
- Một backend chính
- Một database chính
- Deploy đơn giản
- Ít service
- Ít hạ tầng
Mục tiêu:
- Ra sản phẩm nhanh
- Hiểu domain
- Tìm người dùng thật
- Giữ code đủ sạch để không tự phá mình
Giai đoạn 2: Modular monolith
Đặc điểm:
- Vẫn deploy chung
- Code chia module rõ hơn
- Có service layer
- Có job queue
- Có cache ở vài điểm cần thiết
- Có logs/metrics cơ bản
Mục tiêu:
- Kiểm soát độ phức tạp
- Tách logic theo nghiệp vụ
- Chuẩn bị khả năng tách service sau này
Giai đoạn 3: Tách service có chọn lọc
Đặc điểm:
- Một vài phần được tách ra vì có lý do rõ ràng
- Ví dụ: xử lý AI, xử lý video, notification, search
- Có message queue hoặc API contract rõ
- Có monitoring tốt hơn
Mục tiêu:
- Tách đúng chỗ có áp lực thật
- Không biến hệ thống thành distributed monolith
Giai đoạn 4: Event-driven và data pipeline
Đặc điểm:
- Nhiều sự kiện nghiệp vụ quan trọng
- Analytics bắt đầu quan trọng
- Search, notification, recommendation, dashboard cần dữ liệu riêng
- Có thể cần event streaming
Mục tiêu:
- Tách hệ thống vận hành khỏi hệ thống phân tích
- Cho nhiều consumer xử lý cùng một event
- Hỗ trợ replay/backfill khi cần
Giai đoạn 5: Platform hóa
Đặc điểm:
- Nhiều service
- Nhiều team
- Hạ tầng managed hoặc Kubernetes
- Observability nghiêm túc
- SLO/SLA rõ ràng
- Security và compliance cao hơn
Mục tiêu:
- Vận hành hệ thống lớn ổn định
- Tự động hóa deploy, scale, rollback
- Giảm chi phí phối hợp giữa các team
Nguyên tắc: không nhảy giai đoạn vì thích công nghệ. Hãy để áp lực thật kéo kiến trúc đi lên.
---
0.7. Bản đồ đọc lỗi hệ thống
Khi hệ thống có vấn đề, không nên đoán ngay "phải đổi framework" hay "phải dùng microservices". Hãy đọc triệu chứng.
Chậm một request
Nên xem:
- Trace của request
- Query database
- External API
- Cache hit/miss
- Serialization
- Network timeout
Chậm toàn hệ thống
Nên xem:
- CPU
- RAM
- Database connection pool
- Load balancer
- Worker saturation
- Queue backlog
- External provider latency
Job chờ lâu
Nên xem:
- Queue length
- Queue age
- Worker concurrency
- Worker error rate
- Provider latency
- Rate limit
- Retry count
Dữ liệu sai
Nên xem:
- Transaction boundary
- Race condition
- Duplicate retry
- Idempotency
- Event ordering
- Cache invalidation
- Manual data fix
Service chết dây chuyền
Nên xem:
- Timeout
- Retry storm
- Circuit breaker
- Bulkhead
- Connection pool
- Dependency health
- Cascading failure
Khó debug
Nên xem:
- Structured logs
- Correlation ID
- Distributed tracing
- Metrics theo từng service
- Dashboard cho queue/database/API
- Log có bị thiếu context không
Một người làm hệ thống giỏi không chỉ biết xây, mà còn biết đọc tín hiệu khi hệ thống đau.
---
0.8. Những nguyên tắc xuyên suốt cuốn sách
Đơn giản trước, phức tạp sau
Nếu monolith giải quyết được, chưa cần microservices. Nếu PostgreSQL giải quyết được, chưa cần NoSQL. Nếu Redis/RabbitMQ đủ, chưa cần Kafka. Nếu Docker Compose/PaaS đủ, chưa cần Kubernetes.
Đơn giản không có nghĩa là sơ sài. Đơn giản là ít thành phần nhất nhưng vẫn đúng yêu cầu.
Đo trước, tối ưu sau
Không có số liệu, mọi quyết định dễ thành cảm tính. Trước khi đổi kiến trúc, hãy đo latency, throughput, concurrency, queue age, error rate, CPU, RAM, database query, provider latency.
Tách logic trước, tách service sau
Nhiều hệ thống thất bại vì tách server quá sớm trong khi code vẫn dính chặt. Hãy chia module, service layer, domain boundary trước. Khi cần tách runtime, việc tách sẽ ít đau hơn.
Timeout mọi thứ có thể chờ
Mọi call ra ngoài đều phải có timeout: database, API ngoài, service khác, queue operation, file operation. Không có timeout là để một lỗi nhỏ giữ tài nguyên vô hạn.
Retry phải đi cùng idempotency
Retry giúp hệ thống phục hồi lỗi tạm thời, nhưng retry sai có thể tạo đơn hàng trùng, gửi email trùng, cộng tiền trùng, ghi điểm trùng. Muốn retry an toàn phải thiết kế idempotency.
Queue giúp chống nghẽn nhưng làm hệ thống bất đồng bộ hơn
Queue rất mạnh, nhưng nó đổi bài toán từ "xử lý ngay" thành "xử lý sau". Điều này kéo theo job state, retry, dead letter, monitoring, eventual consistency và UI trạng thái chờ.
Cache giúp nhanh hơn nhưng có thể làm sai dữ liệu
Cache không phải miễn phí. Dữ liệu cache có thể cũ, sai, khó invalidate. Chỉ cache khi biết rõ dữ liệu nào được phép cũ và cũ trong bao lâu.
Microservices tăng tự do nhưng cũng tăng chi phí vận hành
Microservices giúp tách deploy, tách scale, tách lỗi và tách team. Nhưng nó thêm network, distributed data, observability, security, contract testing và vận hành phức tạp. Nếu chưa cần những lợi ích đó, đừng vội trả cái giá đó.
Không có công nghệ tốt nhất
Chỉ có lựa chọn phù hợp nhất với vấn đề, giai đoạn, team, tải, ngân sách và khả năng vận hành.
---
0.9. Kết luận của chương
Tấm bản đồ chung không giúp ta biết hết mọi thứ ngay lập tức, nhưng nó giúp ta không học lạc. Khi gặp một công nghệ mới, ta sẽ hỏi:
- Nó nằm ở tầng nào?
- Nó giải quyết vấn đề gì?
- Nó thay thế hay bổ sung cho thứ gì?
- Nó có làm hệ thống đơn giản hơn không?
- Nếu dùng sai, nó gây ra loại đau nào?
Đó là cách học kiến trúc thực dụng: không chạy theo tên công nghệ, mà học cách nhìn hệ thống như một tập hợp các vấn đề, áp lực và trade-off.