Hệ thống Logistics và Giao nhận (Delivery) hiện đại phụ thuộc cực kỳ nhiều vào một năng lực cốt lõi: Tính toán khoảng cách và thời gian di chuyển (Distance Matrix) một cách nhanh chóng và chuẩn xác.
Làm thế nào Grab có thể phân cuốc cho hàng triệu tài xế mỗi giây? Bằng cách nào ShopeeXpress có thể tối ưu lộ trình giao hàng cho hàng chục ngàn shipper cùng lúc? Bí mật nằm ở kiến trúc của Hệ thống Định tuyến (Routing Engine) và Chỉ mục Không gian (Geospatial Indexing).
Trong series 8 phần này, chúng ta sẽ lặn sâu vào việc xây dựng một API Distance Matrix và Routing Engine hoàn chỉnh bằng Golang, tích hợp với Graphhopper, và được tăng tốc bởi Redis cùng hệ thống chỉ mục H3 của Uber. Series này được thiết kế theo hướng trực quan cao độ (highly visual), đi từ con số không (minh họa thuật toán bằng hình ảnh) cho đến kiến trúc ép tải (load testing architecture) ở quy mô lớn.
🗺️ Nội Dung Series (8 Phần)#
Hỏi Đáp: Các Câu Hỏi Thường Gặp (Q&A)#
Series này có phù hợp cho người mới bắt đầu không?Chắc chắn rồi. Series được xây dựng theo triết lý “Nền Tảng Trước Tiên” (Foundation First). Phần 1 và 2 giải thích tường tận các khái niệm qua hình ảnh minh họa và hướng dẫn từng bước cài đặt môi trường (tải dữ liệu bản đồ OSM, chạy Docker) để ai cũng có thể làm theo được.
Tại sao lại kết hợp Golang và Graphhopper?Golang cung cấp khả năng xử lý đồng thời (concurrency) tuyệt vời với mức ngốn tài nguyên cực thấp, biến nó thành lựa chọn lý tưởng cho API Gateway. Trong khi đó, Graphhopper (viết bằng Java) lại là một cỗ máy định tuyến mạnh mẽ vô song. Sự kết hợp này chắt lọc được tinh hoa của cả hai thế giới: Golang cân phần I/O và Caching, còn Graphhopper thầu trọn phần tính toán thuật toán sâu.
Source code của Demo Repo có được chia sẻ không?Có. Toàn bộ source code, cấu hình Docker Compose, các file dữ liệu OpenStreetMap mẫu, và script test K6/JMeter sẽ được công bố rộng rãi trên một kho lưu trữ (repository) GitHub đi kèm.
Bài Viết Liên Quan: Đem GraphHopper Lên Production#
Các bài hướng dẫn triển khai thực tế giúp mang kiến thức của series này áp dụng vào môi trường production thật:
Thử Thách Kỹ Thuật (The Engineering Challenge) Xây dựng một nền tảng logistics hiện đại (như giao đồ ăn, gọi xe, hay quản lý đội xe) đòi hỏi khả năng tính toán khoảng cách và Thời gian Dự kiến Đến nơi (ETA) ở một quy mô khổng lồ.
Bài toán $N^2$: Nếu bạn có 1,000 tài xế và 1,000 đơn hàng, việc tính toán khoảng cách giữa mọi tổ hợp (combination) có thể xảy ra sẽ đòi hỏi phải chạy tới 1,000,000 phép tính lộ trình riêng lẻ. Tốc độ: Những phép tính này bắt buộc phải diễn ra trong thời gian thực (dưới 50ms) để đảm bảo trải nghiệm người dùng mượt mà và ngăn không cho các thuật toán phân cuốc (dispatching) bị đứt gánh (timeout). Độ chính xác: Hệ thống phải tính tới các ràng buộc phũ phàng của thế giới thực như đường một chiều, biển cấm rẽ trái, và tình trạng kẹt xe thiên biến vạn hóa (dynamic traffic congestion). Các API điểm-tới-điểm thông thường (như mấy cái request Google Maps API cơ bản) thì quá chậm và quá đắt đỏ để có thể gánh vác việc sản xuất Ma trận Khoảng cách (Distance Matrix) hàng loạt. Bạn cần phải có một Hệ thống Định tuyến (Routing Engine) nội bộ được tối ưu hóa đến tận răng.
...
Ngồi đẻ ra một cái thuật toán chạy nhanh như gió mới chỉ đi được nửa quãng đường. Phép thử lửa thực sự (true test) của một Kỹ sư Principal nằm ở chỗ: làm sao vác nguyên một cái Hệ thống Định tuyến có trạng thái (stateful Routing Engine) to tổ chảng đắp lên Cloud mà không làm rớt mạng (downtime) lấy một giây nào mỗi lúc có đợt cập nhật bản đồ hay sập cơ sở hạ tầng.
...
Ép tải (Load testing) là trùm cuối (final boss) của Hệ thống Thiết kế (System Design). Một tay kỹ sư non tơ chạy thử một đoạn script, hí hửng vỗ ngực khi thấy “20,000 RPS” đi kèm với 0 lỗi, rồi đinh ninh vỗ đùi cái bép là hệ thống đã sẵn sàng rước khách. Một gã Kỹ sư Principal lão làng thì thừa biết thừa hiểu rằng: trừ phi mày đè cái Kernel Linux ra độ lại (tune), đâm lủng cái bẫy Coordinated Omission, và giả lập một màn hỗn loạn thực tế (realistic chaos), còn không thì cái con số hào nhoáng đó hoàn toàn là đồ dối trá (complete lie).
...
Cái trò lôi nguyên si một tọa độ GPS chính xác ra mà đem đi cache là một nhiệm vụ bất khả thi (impossible). Bởi vì mấy cái số thực dấu phẩy động (floating-point numbers) nó chính xác tới mức vô tận, hai ông khách dù đứng cách nhau vỏn vẹn 1 mét thì tọa độ cũng đã trật lất hoàn toàn (106.0001 so với 106.0002). Nếu bạn ngây thơ nặn cái khóa Redis (Redis key) kiểu mộc mạc như lat1,lng1:lat2,lng2, thì tỷ lệ trúng Cache (Cache Hit Rate) nhà bạn muôn đời sẽ đội sổ ở mức 0%.
...
Vẽ vời dăm ba cái lộ trình lèo tèo lên Google Maps thì dễ như ăn kẹo (trivial). Nhưng bắt phải vẽ cùng lúc (simultaneously) 100,000 cái lộ trình lịch sử của các chuyến xe, nhồi thêm đống ma trận Điểm xuất phát-Đích đến (Origin-Destination matrices), lại còn kẹp thêm ba cái hàng rào ảo H3 (dynamic H3 geofences) biến thiên liên tục thì sao? Lúc đó, bạn buộc phải quăng gánh nặng tính toán từ cái CPU còm cõi của trình duyệt (browser’s CPU) sang cho con GPU gánh vác nhờ vào phép thuật WebGL.
...
Dựng một cái API cùi bắp để réo gọi Graphhopper qua lệnh http.Get thì dễ ợt. Nhưng để rèn ra một cái API Gateway đẳng cấp Principal dư sức gánh còng lưng 10,000 cuốc xe nã request cùng lúc mà không đột tử, đó đích thực là một khóa masterclass về Hệ thống Phân tán (Distributed Systems).
Answer-first: Graphhopper là một con service tuyến dưới (downstream service) lệ thuộc cực nặng vào CPU. Nếu cái API Golang của bạn cứ nhắm mắt hứng đạn (traffic) rồi ném tuốt luốt thẳng xuống dưới, chỉ cần Graphhopper hắt hơi sổ mũi chạy chậm lại một nhịp thôi là bầy Goroutines sẽ dồn ứ lại thành một cục máu đông, vắt kiệt sạch RAM của server và kích hoạt một cú sập dây chuyền (cascading failure). Bạn buộc phải dựng lên một phòng tuyến “Bảo Vệ Đa Lớp” (Defense in Depth) xài tới chiêu Giới hạn Đồng thời (Concurrency Bounding), Cầu dao điện (Circuit Breakers), và trò Bắn tin Bất đồng bộ (Asynchronous Pub/Sub).
...
Một lỗi chí mạng ngây ngô của mấy bạn junior khi xây mấy cái app gọi xe là cắm thẳng cái API Gateway vào luôn Hệ thống Định tuyến (Routing Engine).
Answer-first: Graphhopper là một con quái vật ngốn CPU cực bạo (CPU-intensive). Nếu bạn bắt nó tính thời gian dự kiến (ETA) tới tận 10,000 ông tài xế đang online trong thành phố, mấy con server nhà bạn sẽ tan chảy theo đúng nghĩa đen. Bạn bắt buộc phải nhét Chỉ Mục Không Gian (Spatial Indexing) (như Uber H3 hay Redis GEO) vào làm “Màng lọc thô” (Pre-filter) siêu tốc độ. Cái chỉ mục này sẽ bới bèo ra bọ tìm cho ra 50 tài xế gần nhất “theo đường chim bay” chạy trơn tru trên RAM, và chỉ 50 mạng đó mới được đẩy xuống cho Graphhopper cày ải tính toán ETA nặng nề.
...
Vụ setup một cái hệ thống định tuyến local (local routing engine) nổi tiếng là khoai lang. Mấy bài tutorial chung chung toàn quang đại cho một cái lệnh Docker cơ bản, chạy xong nó lăn quay ra chết im ỉm (crashes silently) làm dân dev ngơ ngác chả hiểu gì.
Trong bài hướng dẫn này, chúng ta sẽ bỏ qua mấy cái setup “Hello World” con nít. Chúng ta sẽ xắn tay dựng một môi trường chuẩn production (production-grade) xịn xò, nhào nặn chung dữ liệu OpenStreetMap (OSM), một cái container Docker Graphhopper (Java) được tinh chỉnh đàng hoàng, và một cái API Gateway Golang gánh tải song song (high-concurrency) cực khỏe.
...
Khi xây dựng một hệ thống giao nhận hay logistics ép tải cao (high-scale), những bài hướng dẫn thuật toán chung chung trên mạng thường hay dắt mũi lập trình viên đi sai đường. Bọn họ cứ ra rả rằng A* lúc nào cũng ngon hơn Dijkstra. Thế nhưng, bước ra ngoài đời thực, trong thế giới của Hệ thống Định tuyến (Routing Engines) và Ma trận Khoảng cách (Distance Matrices), sự thật lại phũ phàng và phức tạp hơn rất nhiều.
...