Xây dựng một API cơ bản để kết nối với Graphhopper qua http.Get khá đơn giản. Tuy nhiên, để phát triển một API Gateway chuẩn kiến trúc có khả năng chịu tải hàng ngàn yêu cầu xử lý lộ trình đồng thời mà không bị ngắt kết nối hay quá tải đòi hỏi các giải pháp kiến trúc hệ thống phân tán chuyên sâu.

Answer-first: Graphhopper là một dịch vụ tuyến dưới (downstream service) phụ thuộc nhiều vào CPU. Nếu API Golang tiếp nhận lưu lượng (traffic) và chuyển tiếp toàn bộ xuống tuyến dưới mà không có kiểm soát, khi Graphhopper bị phản hồi chậm, các Goroutines sẽ dồn tích tụ lại, gây cạn kiệt tài nguyên bộ nhớ (RAM) và dẫn đến sự cố sập dây chuyền (cascading failure). Vì vậy, hệ thống cần một kiến trúc “Phòng thủ chuyên sâu” (Defense in Depth) ứng dụng Giới hạn Đồng thời (Concurrency Bounding), Cầu dao ngắt mạch (Circuit Breakers) và Truyền tin Bất đồng bộ (Asynchronous Pub/Sub).


1. Phòng Tuyến Đa Lớp (Defense in Depth): Bảo Vệ Routing Engine

Giới Hạn Đồng Thời (errgroup)

Khi phải xử lý tính toán nhiều lộ trình riêng biệt cùng lúc, bạn nên sử dụng thư viện golang.org/x/sync/errgroup. Điểm then chốt là cần thiết lập giới hạn qua g.SetLimit(10) nhằm ngăn chặn thảm họa quá tải dòng truy cập (thundering herd). Việc kiểm soát số lượng request gửi ra ngoài sẽ ngăn ngừa nguy cơ hệ thống tự gây quá tải (DDoS) lên dịch vụ Graphhopper nội bộ.

Cầu Dao Điện - Circuit Breaker (gobreaker)

Trong trường hợp Graphhopper phản hồi chậm do xử lý nặng, nếu không trang bị Circuit Breaker, API Gateway Golang sẽ liên tục tạo thêm kết nối mới cho tới khi cạn kiệt bộ nhớ. Bằng cách bao bọc các lời gọi dịch vụ bằng sony/gobreaker, cơ chế này sẽ chuyển sang trạng thái ngắt mạch (Fail Fast - Open state) khi tỷ lệ lỗi vượt ngưỡng, lập tức phản hồi lỗi 503 về phía client và dành thời gian cho Graphhopper phục hồi tài nguyên.

Triệt Tiêu Trùng Lặp (singleflight)

Khi hàng trăm người dùng cùng truy cập ứng dụng để xem thông tin ETA tại một vị trí sự kiện trong cùng thời điểm, thay vì gửi từng đó yêu cầu trùng lặp tới Graphhopper, bạn có thể áp dụng golang.org/x/sync/singleflight. Thư viện này sẽ gộp các yêu cầu đồng thời (concurrent requests) giống nhau thành một truy vấn duy nhất tới dịch vụ tuyến dưới, sau đó phân phối (broadcasting) kết quả về cho tất cả các yêu cầu đang chờ.


2. Tối Ưu Garbage Collection (GC) Protobuf Với Mảng Phẳng (Flattened Arrays)

Khi cần phát hành routing engine cho các dịch vụ nội bộ qua gRPC, việc định nghĩa một Ma trận Khoảng cách kích thước lớn (ví dụ: 10,000 x 10,000) cần được tính toán kỹ lưỡng.

Nếu sử dụng cấu trúc mảng lồng nhau (nested arrays): repeated MatrixRow rows trong đó mỗi row chứa repeated double distances, quá trình giải mã (deserializing) mảng lồng 10,000 x 10,000 trên Golang sẽ khởi tạo hàng chục triệu đối tượng nhỏ. Điều này tạo ra áp lực lớn cho tiến trình Dọn rác bộ nhớ (Garbage Collection - GC pause), khiến ứng dụng ngưng trệ trong nhiều giây.

Giải Pháp Tối Ưu: Sử dụng Mảng Phẳng 1 Chiều (Flattened 1D Array). Khai báo trong Protobuf thành repeated double data đi kèm int32 rowsint32 cols. Cấu trúc này chỉ khởi tạo một đối tượng duy nhất trên RAM. Vị trí ô dữ liệu (cell) cần tìm được xác định qua công thức index = row * cols + col.


3. Định Tuyến Bất Đồng Bộ (Asynchronous) Với Dapr Workflows

Giao thức HTTP vận hành theo cơ chế đồng bộ (synchronous), trong khi các phép tính toán ma trận lớn có thể kéo dài.

Khi xử lý các ma trận quy mô rất lớn (như tính toán khoảng cách từ 1,000 kho hàng đến 1,000 điểm giao), không thể duy trì kết nối HTTP mở liên tục.

  1. Gateway Golang tiếp nhận request và phát phát (publishes) sự kiện RouteRequested qua Dapr Pub/Sub, sau đó phản hồi cho client mã 202 Accepted và kết thúc kết nối.
  2. Một tiến trình chạy ngầm (background worker) tiếp nhận sự kiện. Do quy trình tìm đường gồm nhiều bước (Geocoding -> Graphhopper -> Notification), luồng xử lý được quản lý bằng Dapr Workflows.
  3. Dapr Workflows đảm bảo cơ chế Thực Thi Bền Vững (Durable Execution). Nếu tiến trình worker bị ngắt giữa chừng, Dapr sẽ khôi phục và tiếp tục (resumes) workflow từ mốc lưu trữ (checkpoint) gần nhất ngay khi tiến trình hoạt động trở lại. Trong năm 2026, tính năng này cực kỳ tối ưu cho kiến trúc Cloud-Native khi các pod Kubernetes thường xuyên bị xoay vòng (rotated).

Sau khi xây dựng thành công API Gateway vững chắc, làm thế nào để trực quan hóa (visualize) hàng trăm ngàn lộ trình xe cộ trên giao diện mà không gây giật lag trình duyệt? Mời bạn theo dõi Phần 5: UI Trực Quan Hóa Lộ Trình Bằng Mapbox & Deck.gl để tìm hiểu cách xây dựng bản đồ hiển thị hiệu năng cao.


Câu Hỏi Thường Gặp (FAQ): Các Vấn Đề Định Tuyến Thường Gặp

Tôi gửi lên Custom Model né Trạm Thu Phí nhưng Graphhopper không áp dụng. Nguyên nhân do đâu?
Đây là vấn đề liên quan đến tham số ch.disable=true. Chế độ Phân Cấp Rút Gọn (Contraction Hierarchies - Speed Mode) tính toán sẵn đường đi tối ưu nên không thể áp dụng các trọng số động (dynamic weights) tại thời điểm thực thi (runtime). Để sử dụng luật tùy biến, bạn cần thêm tham số query ?ch.disable=true trong request POST để chuyển Graphhopper về chế độ linh hoạt (Flexible Mode - sử dụng Dijkstra/A*).
Tại sao thời gian xử lý của Graphhopper không hiển thị trên dashboard Jaeger/Zipkin?
Trường hợp này do chưa truyền ngữ cảnh theo vết (tracing context). Trong Kratos v2, bạn cần cấu hình OpenTelemetry middleware cho HTTP client qua lệnh http.WithMiddleware(tracing.Client()). Cấu hình này sẽ bổ sung header traceparent theo chuẩn W3C vào yêu cầu HTTP, kết nối chuỗi vết từ gateway tới log của Graphhopper server.
Khi gửi yêu cầu Matrix API lớn, hệ thống trả về lỗi 400 Bad Request. Cách khắc phục thế nào?
Yêu cầu đã chạm ngưỡng Maximum visited nodes exceeded của Graphhopper. Đây là cơ chế an toàn trong file config.yml (routing.max_visited_nodes) nhằm tránh cạn kiệt bộ nhớ RAM. Thay vì nâng giới hạn cấu hình lên quá cao, bạn nên điều chỉnh tiến trình worker Golang để chia nhỏ (split) ma trận thành các ma trận con (sub-grids) nhỏ hơn để xử lý lần lượt.

🔗 Đọc thêm các chuyên đề liên quan: