Request routing — tìm đúng node giữ dữ liệu
Chia dữ liệu ra nhiều node xong, client làm sao biết hỏi node nào cho một khoá? Ba cách route, dịch vụ coordination, và giữ mapping đúng khi partition di chuyển.
TL;DR: Chia dữ liệu ra nhiều node xong, câu hỏi cuối là định tuyến: client muốn khoá K, làm sao biết K nằm ở node nào — nhất là khi partition di chuyển lúc rebalancing? Có ba cách: (1) client tự biết mapping, gọi thẳng node đúng; (2) một routing tier (proxy) đứng trước, chuyển tiếp; (3) client gọi node bất kỳ, node đó tự chuyển tiếp. Cả ba đều cần một nguồn sự thật về mapping partition→node luôn cập nhật khi cụm co giãn — thường do dịch vụ coordination (ZooKeeper/etcd) giữ. Điểm khó không phải "route thế nào" mà "ai biết mapping mới nhất" và đồng bộ nó ra sao khi cụm co giãn.
Đây là mảnh cuối của partitioning: sau khi quyết chia thế nào (bài 1-2) và index thế nào (bài 3), phải trả lời tìm lại ở đâu. Và câu trả lời kéo thẳng vào một chủ đề lớn của track — làm sao nhiều thành phần thống nhất một thông tin (mapping) đang thay đổi.
Bạn đã thấy Kubernetes giải đúng bài toán này ở tầng mạng: Service cho một tên ổn định, kube-proxy định tuyến tới pod đang sống dù pod đổi IP. Request routing ở đây là cùng ý tưởng cho dữ liệu: một lớp gián tiếp che việc "partition nào ở node nào" khỏi client, và tự cập nhật khi mapping đổi.
1. Analogy — gọi tổng đài, hỏi trực tiếp, hay được chuyển máy
Bạn cần gặp đúng người phụ trách hồ sơ của mình trong một toà nhà lớn, mà phòng ban hay đổi tầng:
- Bạn tự có sơ đồ tầng mới nhất (client tự biết): đi thẳng đúng phòng. Nhanh, nhưng bạn phải luôn có bản sơ đồ cập nhật — tầng đổi mà bạn không biết là đi nhầm.
- Có quầy lễ tân (routing tier): bạn hỏi lễ tân, họ chỉ/đưa tới đúng phòng. Bạn không cần biết sơ đồ; chỉ lễ tân cần cập nhật.
- Hỏi bất kỳ ai gặp, họ chuyển tiếp (node tự forward): gặp ai cũng được, họ biết đường và dẫn bạn tới đúng phòng. Tiện cho bạn, nhưng mỗi nhân viên phải biết sơ đồ.
Điểm chung: dù cách nào, phải có một sơ đồ tầng đúng và ai đó giữ nó cập nhật khi phòng ban chuyển tầng. Đó mới là phần khó.
| Toà nhà | Request routing |
|---|---|
| Sơ đồ tầng | Mapping partition → node |
| Tự có sơ đồ, đi thẳng | Client tự biết mapping |
| Hỏi lễ tân | Routing tier / proxy |
| Hỏi bất kỳ ai, được chuyển | Node bất kỳ tự forward |
| Phòng ban đổi tầng | Partition di chuyển (rebalancing) |
| Ai giữ sơ đồ cập nhật | Dịch vụ coordination |
2. Ba cách định tuyến

Ba cách khác nhau ở ai giữ bản sao mapping và ai lo cập nhật nó khi partition di chuyển:
- (1) Client tự biết mapping. Mỗi client giữ (hoặc cache) bảng partition→node, tự tính partition của khoá rồi gọi thẳng node đúng — ít hop nhất, chỉ một chặng mạng tới node. Cái giá: mapping bị sao thành N bản (mỗi client một bản), nên khi nó đổi phải có đường đẩy cập nhật tới mọi client; sót một nhóm client là nhóm đó ôm bản cũ, gọi nhầm node. Toàn bộ độ phức tạp "biết mapping mới nhất" dồn về phía client — bên bạn ít kiểm soát nhất.
- (2) Routing tier (proxy). Một lớp proxy đứng trước nhận mọi request, tra mapping của riêng nó rồi chuyển tiếp tới node đúng. Client chỉ cần biết một địa chỉ (địa chỉ tier); mapping gom về một chỗ, chỉ tier phải cập nhật khi cụm co giãn. Cái giá: thêm một hop (client tới tier rồi tier tới node), và tier thành thành phần sống-còn phải scale + HA — tier chết là cả cụm mất lối vào. Thực tế chạy nhiều bản tier sau một load balancer, và các bản đó lại phải chia sẻ cùng một mapping.
- (3) Node tự forward. Client gọi bất kỳ node; nếu node đó không giữ khoá, nó chuyển tiếp nội bộ tới node đúng rồi trả kết quả về. Client cực đơn giản (gọi ai cũng được, không cần biết mapping). Cái giá: mọi node phải biết mapping toàn cục — thường qua gossip (mỗi node định kỳ trao đổi hiểu biết "ai giữ khoảng khoá nào" với vài node lân cận, dần dần cả cụm hội tụ về cùng bức tranh) — và một request gọi lệch node phải chịu thêm một hop mạng nội bộ.
| Cách | Mapping nằm ở | Ai cập nhật khi đổi | Số hop | Điểm yếu chính |
|---|---|---|---|---|
| Client tự biết | mọi client (cache) | phải đẩy tới mọi client | 1 (ít nhất) | N bản dễ lệch nhau |
| Routing tier | tập trung ở tier | chỉ tier | 2 (qua proxy) | tier phải HA + scale |
| Node tự forward | mọi node (gossip) | gossip lan dần | 1–2 (có thể lệch) | gossip hội tụ có độ trễ |
Mấu chốt: cả ba chỉ dời chỗ đặt độ phức tạp (client / một tier / mọi node) và đổi số hop, chứ không xoá được câu hỏi "ai giữ mapping mới nhất" — mục sau trả lời trực tiếp câu hỏi đó.
3. Ai giữ mapping — và vì sao đó là phần khó?
Cả ba cách đều dựa trên một mapping partition→node đúng. Khi cụm ổn định, mapping tĩnh — dễ. Cái khó là lúc rebalancing: partition di chuyển, và giờ nhiều thành phần (mọi client, hoặc mọi node, hoặc routing tier) phải cùng thấy thay đổi đó, nếu không request đi tới node cũ (không còn giữ khoá) → lỗi hoặc chuyển tiếp thừa.
Đây là một bài toán thống nhất một thông tin đang thay đổi giữa nhiều node — và nó xuất hiện khắp hệ phân tán. Giải pháp phổ biến: một dịch vụ coordination chuyên trách (ZooKeeper, etcd) giữ mapping như nguồn sự thật; các thành phần đăng ký theo dõi và được thông báo khi mapping đổi. Bản thân dịch vụ đó phải nhất quán mạnh (không được cho hai câu trả lời khác nhau về "ai giữ partition P") — nên nó dùng consensus bên trong.
Nói cách khác: routing đẩy độ khó về đúng câu hỏi trung tâm của cả track — làm sao nhiều node đồng ý một sự thật — mà Module 6 (consensus) sẽ mổ. Request routing là một ứng dụng cụ thể đầu tiên của consensus mà bạn gặp.
4. Pitfall — mỗi thành phần tự đoán mapping, không có nguồn sự thật
Cách hỏng phổ biến: client (hoặc node) cache bảng partition→node để đi thẳng cho nhanh, nhưng không có đường nhận biết khi mapping đổi.
❌ Client cache mapping luc khoi dong -> rebalancing chuyen partition P sang node moi
-> client van goi node cu -> node cu khong con giu P -> loi / tra sai
-> moi client cache mot phien ban khac nhau -> hanh vi loan
❌ Khong co nguon su that -> moi node tu "doan" ai giu gi -> khong dong bo
Hệ quả: sau mỗi lần co giãn, một phần request đi nhầm chỗ cho tới khi cache hết hạn — lỗi lúc-có-lúc-không, khó tái hiện.
Hướng đúng: luôn có một nguồn sự thật cho mapping (dịch vụ coordination nhất quán mạnh), và cơ chế thông báo/theo dõi để các thành phần cập nhật khi mapping đổi (watch/notify, hoặc phiên bản mapping kèm mỗi response để phát hiện cache cũ). Cache để nhanh thì được, nhưng phải có đường làm mới khi nguồn sự thật đổi — đừng để cache thành sự thật.
5. Tự thiết kế routing — đến lượt bạn
Bạn đã có ba cách route + hiểu phần khó nằm ở giữ mapping đồng bộ. Giờ tự thiết kế cho một tình huống cụ thể: đọc kỹ rồi tự quyết (a) chọn cách route nào, (b) đặt nguồn sự thật của mapping ở đâu, (c) biện minh — viết ra (trong đầu hoặc ra giấy) trước khi mở đáp án.
Kho key-value cho một game mobile. Khoảng 8 triệu client là app chạy trên điện thoại người dùng, mạng chập chờn, và bạn không kiểm soát được vòng đời chúng (không thể đẩy cập nhật tức thì tới mọi máy — người dùng cập nhật app khi nào tuỳ họ). Cụm 30 node giữ trạng thái người chơi và co giãn mạnh quanh giờ cao điểm/sự kiện — thêm/bớt node vài lần mỗi ngày, nên partition di chuyển thường xuyên. Yêu cầu: đọc/ghi độ trễ thấp, và không để client gọi nhầm node sau mỗi lần co giãn.
- Q1Với kho key-value game mobile trên: chọn cách route nào trong ba cách, đặt nguồn sự thật của mapping partition→node ở đâu, và vì sao? Biện minh dựa trên đặc điểm client (8 triệu app mobile không đẩy cập nhật được) và tần suất rebalancing.
6. 📚 Deep Dive
- Designing Data-Intensive Applications (Kleppmann) — Chương 6, mục "Request Routing" — ba cách route, vai trò của dịch vụ coordination (ZooKeeper) giữ mapping và thông báo thay đổi.
- Nhiều hệ dùng ZooKeeper/etcd làm nguồn mapping (và cả membership); một số (kiểu Cassandra) dùng gossip để mọi node cùng biết ai giữ khoảng khoá nào, cho phép cách (3) node tự forward mà không cần tier riêng.
Ghi chú: DDIA Ch6 nhấn: routing không khó ở "gửi đi đâu" mà ở "giữ mapping đồng bộ khi nó đổi" — chính là consensus.
7. Liên hệ các bài khác
- Bài 02 — Consistent hashing & rebalancing: partition di chuyển lúc rebalancing chính là lúc mapping đổi — nguồn của bài toán routing.
- Module 2 — Leaderless: store kiểu Dynamo dùng gossip để mọi node biết mapping → cho phép cách "node tự forward".
- Module 6 — Consensus: dịch vụ giữ mapping phải nhất quán mạnh → dùng consensus; routing là ứng dụng đầu tiên của nó.
- Service & kube-proxy (Networking): cùng bài toán định tuyến tới đích hay đổi, ở tầng mạng container.
8. Tóm tắt
- Request routing: client cần khoá K, làm sao tới node giữ K — khó vì mapping partition→node đổi khi rebalancing.
- Ba cách: client tự biết (ít hop, client phức tạp) · routing tier (client đơn giản, thêm hop) · node tự forward (client đơn giản, mọi node phải biết mapping).
- Cả ba cần một nguồn sự thật về mapping luôn cập nhật — thường do dịch vụ coordination (ZooKeeper/etcd) giữ và thông báo thay đổi.
- Phần khó thật sự = thống nhất mapping đang đổi giữa nhiều node → chính là consensus (Module 6).
- Anti-pattern: cache mapping mà không có đường làm mới khi nó đổi → request đi nhầm node sau mỗi lần co giãn.
9. Tự kiểm tra
- Q1Nêu ba cách request routing và điểm đánh đổi của mỗi cách (độ phức tạp đặt ở đâu, số hop).
- Q2Vì sao 'phần khó' của routing không phải cách gửi mà là giữ mapping đồng bộ? Điều gì xảy ra lúc rebalancing?
- Q3Vai trò của một dịch vụ coordination (ZooKeeper/etcd) trong routing là gì, và vì sao nó phải nhất quán mạnh?
- Q4Một hệ cache bảng mapping ở client để đi thẳng cho nhanh. Rủi ro gì khi rebalancing, và thiết kế đúng là gì?
- Q5Request routing được gọi là 'ứng dụng đầu tiên của consensus' trong track. Giải thích mối liên hệ đó.
Bài tiếp theo: Module 3 — Tổng kết & cheat sheet
Bài này đáng gửi cho bạn học cùng?
Copy link đã gắn nguồn — dán group, chat, hoặc LinkedIn.
Bài này có giúp bạn hiểu bản chất không?
Hỏi đáp về bài này
Chưa có câu hỏi
Có gì chưa rõ trong bài? Đặt câu hỏi đầu tiên — câu trả lời từ cộng đồng giúp bạn (và người sau).
Đặt câu hỏi đầu tiên