Backend

System Design — Bài 19: Thiết kế giao đồ ăn: geo query, vị trí và dispatch an toàn

SSite Admin
10 tháng 10, 2026 6 phút đọc 0 lượt xem

Vị trí tài xế có thể thay đổi liên tục, còn một đơn hàng phải có đúng người đang giữ quyền nhận. Hai loại dữ liệu này cần mức bảo đảm khác nhau. Ta dùng bài toán giao đồ ăn để thiết kế cả đường cập nhật vị trí tốc độ cao và chuyển trạng thái đơn có kiểm soát.

Khóa học System Design: mục lục 21 bài — bạn đang đọc bài 19.

Các con số dưới đây là giả định phục vụ học thiết kế, không phải số liệu hay kiến trúc nội bộ của sản phẩm được nhắc tên. Dung lượng dùng đơn vị thập phân; capacity thực tế phải đo theo workload và mục tiêu chất lượng.

Yêu cầu & ước lượng

Phạm vi bài gồm tìm tài xế phù hợp, theo dõi vị trí, quản lý vòng đời đơn và ước tính thời gian giao. Cần hỏi thêm vị trí được phép cũ bao lâu, mức chính xác mong muốn và điều gì xảy ra khi tài xế mất mạng.

Giả sử 200.000 tài xế online, mỗi bốn giây gửi vị trí một lần, ta có 50.000 cập nhật mỗi giây trung bình. Chưa tính retry, burst hoặc người dùng xem bản đồ. Đây là tín hiệu phải benchmark và thiết kế đường ingest riêng; không thể chỉ từ con số này kết luận mọi database đơn đều không chịu nổi.

Dữ liệu vị trí mới có thể thay thế vị trí cũ cho tìm kiếm tức thời. Trạng thái nhận đơn thì không thể tùy tiện ghi đè, vì có bất biến một đơn không được đồng thời giao cho hai tài xế. Khác biệt nghiệp vụ này dẫn tới hai cách quản lý dữ liệu.

Dữ liệu geo-spatial

Geohash mã hóa vị trí thành các ô với độ chi tiết khác nhau. Tiền tố chung thường giúp nhóm khu vực, nhưng hai điểm rất gần ở hai bên biên ô có thể có tiền tố khác. Vì vậy tìm trong ô hiện tại thôi có thể bỏ sót tài xế ngay bên cạnh.

Quy trình là tìm các ô ứng viên, gồm ô lân cận cần thiết, rồi tính và lọc khoảng cách thực. Quadtree chia nhỏ vùng đông dữ liệu để thích ứng với mật độ. Một công cụ như Redis GEO có thể hỗ trợ truy vấn bán kính, nhưng vẫn cần quản lý freshness và tình trạng sẵn sàng của tài xế.

Gần theo đường chim bay chưa chắc giao nhanh nhất vì đường một chiều, sông hoặc cầu. Geo query tạo danh sách ứng viên; xếp hạng cuối có thể cần thời gian di chuyển, năng lực nhận đơn và các ràng buộc nghiệp vụ.

Hai luồng dữ liệu vị trí

Luồng nóng giữ vị trí mới nhất để tìm và hiển thị tài xế. Có thể ghi đè với điều kiện sequence hoặc timestamp phù hợp để một gói tới muộn không kéo tài xế quay về vị trí cũ. Cần loại tài xế quá lâu không cập nhật khỏi tập ứng viên.

Luồng lịch sử đi qua pipeline lưu bền để phân tích hành trình, đối soát hoặc xử lý khiếu nại. Nó có thể chấp nhận độ trễ khác đường tìm tài xế, nhưng phải xác định mức mất dữ liệu cho phép và chính sách retention.

Redis GEO không tự làm mọi member hết hạn riêng chỉ vì key có TTL; cần cách dọn hoặc lọc theo last_seen phù hợp. Với 50.000 cập nhật mỗi giây, phải đo payload, hot city, replication và failover. Không dùng một mốc throughput quảng bá để bảo đảm hệ thống của mình sẽ đạt.

Dispatch và vòng đời đơn

Dispatch có thể cấp một lease giữ quyền nhận đơn trong thời hạn. Nếu không xác nhận kịp, hệ thống cho phép thử ứng viên khác. Nhưng TTL hết không tự giải quyết mọi race: tài xế cũ có thể gửi xác nhận muộn.

Do đó request nhận đơn cần mang reservation hoặc version và server kiểm tra nó vẫn là quyền hợp lệ. Chuyển trạng thái phải là cập nhật có điều kiện, chẳng hạn chỉ từ offered đúng phiên sang accepted. Client retry với cùng định danh phải trả lại kết quả cũ thay vì tạo lần gán mới.

State machine mô tả các bước hợp lệ từ tìm tài xế tới nhận, lấy hàng và hoàn tất. Luồng hết hạn cần worker phục hồi và idempotency. ETA, giá theo tải và hot cell là các hướng đào sâu riêng, không nên nhét toàn bộ logic vào một khóa phân tán duy nhất.

Hai luồng dữ liệu và một điểm xác nhận nhận đơn

200.000 tài xế / 4 giây = 50.000 update/s trung bình

Location ingest -> latest location + last_seen
                -> pipeline lịch sử -> kho phân tích

Geo query: ô hiện tại + ô lân cận -> lọc khoảng cách
         -> lọc freshness và trạng thái sẵn sàng

Nhận đơn:
UPDATE offer SET state = 'accepted'
WHERE id = :id AND version = :version
  AND state = 'offered' AND expires_at > now();

Đoạn cập nhật là minh họa, cần kiểm tra số dòng thay đổi và ràng buộc quyền gán đơn ở nguồn sự thật trong cùng transaction. Lease đã hết thì ACK cũ không được thắng offer mới. Vị trí gần theo đường chim bay chưa chắc cho ETA tốt nhất.

Thuật ngữ cần nhớ

  • TTL — Time To Live: Thời gian sống còn hiệu lực của dữ liệu, bản ghi tên miền hoặc lease. Hết hạn không tự thực hiện nghiệp vụ như hoàn tồn kho; cần luồng xử lý tương ứng.

  • ETA — Estimated Time of Arrival: Thời gian đến dự kiến, ví dụ khi tài xế tới điểm lấy/giao hàng; thường là kết quả ước lượng có thể thay đổi theo vị trí và giao thông.

  • ACK — Acknowledgment: Tín hiệu xác nhận. Phải nói rõ xác nhận đã nhận vào bộ nhớ, đã lưu bền hay đã xử lý nghiệp vụ; các mốc này cho bảo đảm khác nhau.

Bài tập tự thực hành

Tài xế xác nhận đơn sau khi lease của mình hết hạn.

Gợi ý kiểm tra lời giải

Kiểm tra reservation/version tại nguồn sự thật; từ chối ack cũ; không gán trùng.

Tự kiểm tra sau bài học

  • Gói vị trí đến muộn không ghi đè vị trí mới.

  • Geo query không bỏ sót tài xế sát biên ô.

  • Request nhận đơn kiểm tra reservation/version và chống retry trùng.

Tiếp tục lộ trình

Bài trước — Thiết kế ứng dụng chat: WebSocket, ordering và reconnect

Bài tiếp — Thiết kế ví điện tử: ledger, idempotency và đối soát

Xem toàn bộ khóa học System Design 21 bài

S

Site Admin

Engineer and writer. Building things with TypeScript and distributed systems.

Bình luận (0)

Bạn cần đăng nhập bằng Google để bình luận.

Hãy là người bình luận đầu tiên.

Bài viết liên quan

K

Học System Design qua 21 bài: yêu cầu, capacity, database, cache, hệ phân tán và 6 bài thực hành, kèm bài tập, sơ đồ luồng và mục lục đầy đủ.

10 thg 10, 20266 phút2
S

Học cách đi từ vấn đề đến kiến trúc, phân biệt architecture với design và bảo vệ lựa chọn bằng yêu cầu, số liệu và đánh đổi.

10 thg 10, 20266 phút2
S

Đặt câu hỏi về người dùng, thao tác, tải, độ trễ và tính đúng đắn trước khi chọn database hay vẽ sơ đồ kiến trúc.

10 thg 10, 20266 phút1