Bài 1
Cùng một bộ route, ba gateway chọn khác nhau
Envoy, Kong và OpenResty đều nhận một danh sách route rồi chọn ra một cái cho mỗi request. Nghe như cùng một việc, nhưng ba bên định nghĩa “khớp” khác nhau và sắp thứ tự ưu tiên khác nhau. Bài này dựng đúng một bộ năm route trên cả ba, gửi cùng bảy đường dẫn qua, và ghi lại route nào thắng.
Bộ đo
Bốn container trên một mạng Docker. Backend là nginx trả về đúng đường dẫn nó nhận được, nên mỗi route được cấu hình để viết lại đường dẫn thành một dấu hiệu riêng — đọc phản hồi là biết route nào đã thắng.
server {
listen 8080;
default_type text/plain;
location / { return 200 "$request_uri\n"; }
}
Cả ba gateway nhận cùng năm route:
| loại | mẫu | |
|---|---|---|
| r1 | prefix | /a |
| r2 | khớp đúng | /a/b |
| r3 | prefix | /a/b/c |
| r4 | regex | /a/.*\.png$ |
| r5 | prefix | / |
Kong không có khái niệm “khớp đúng”, nên r2 ở Kong là một regex có neo hai đầu. Phiên bản đã đo: Envoy 1.31.10, Kong 3.9.3 chạy DB-less, OpenResty 1.31.1.1.
“Regex” của ba bên là ba thứ khác nhau
Trước khi nói tới thứ tự ưu tiên, có một khác biệt sâu hơn: cùng một chuỗi mẫu không khớp cùng
một tập đường dẫn. Đo bằng đúng một route regex /b$ cộng một route bắt tất cả:
| mẫu | đường dẫn | Envoy | Kong | OpenResty |
|---|---|---|---|---|
| /b$ | /b | khớp | khớp | khớp |
| /a/b | không | không | khớp | |
| /xb | không | không | không | |
| /b | /b | khớp | khớp | khớp |
| /b/c | không | khớp | khớp | |
| /a/b | không | không | khớp |
Ba dòng đó đủ để đọc ra ba quy tắc:
- Envoy —
safe_regexphải khớp toàn bộ đường dẫn. Mẫu/bkhông khớp/b/c. - Kong — mẫu sau dấu
~được neo ở đầu. Mẫu/bkhớp/b/cnhưng không khớp/a/b. - OpenResty — như nginx, regex được tìm ở bất kỳ đâu trong
đường dẫn. Mẫu
/b$khớp cả/a/b.
/admin, chuyển
từ nginx sang Envoy sẽ thôi khớp /admin/users; chuyển ngược lại thì bắt luôn
/v1/admin. Khi chuyển, phải viết lại mẫu cho đúng ngữ nghĩa nơi đến rồi thử lại
bằng chính đường dẫn thật.
Envoy không sắp lại gì cả
Với Envoy, route thắng là route đầu tiên khớp trong đúng thứ tự viết. Không có bước sắp xếp nào. Viết cùng năm route ở ba thứ tự khác nhau cho ba bảng khác hẳn nhau:
| đường dẫn | viết r1→r5 | viết r5→r1 | viết r4,r3,r2,r1,r5 | viết r2,r3,r4,r1,r5 |
|---|---|---|---|---|
| /a | r1 | r5 | r1 | r1 |
| /a/b | r1 | r5 | r2 | r2 |
| /a/b/c | r1 | r5 | r3 | r3 |
| /a/b/c/d | r1 | r5 | r3 | r3 |
| /a/x.png | r1 | r5 | r4 | r4 |
| /a/b/c/x.png | r1 | r5 | r4 | r3 |
| /z | r5 | r5 | r5 | r5 |
Cột thứ hai là trường hợp xấu nhất: viết route bắt tất cả lên đầu làm bốn route còn lại thành
vô dụng, và Envoy khởi động bình thường, không một dòng cảnh báo. Cột cuối
tinh vi hơn: chỉ vì prefix /a/b/c viết trước regex .png mà
/a/b/c/x.png đi sang route khác.
Cùng phép thử đó trên Kong và OpenResty: viết xuôi hay viết ngược đều cho y hệt một bảng. Cả hai đều sắp lại danh sách trước khi chạy.
Kong và nginx sắp theo cái gì
Với bộ năm route ở trên, Kong và OpenResty cho cùng một kết quả:
| đường dẫn | route thắng | vì sao |
|---|---|---|
| /a | r1 | chỉ prefix /a và / khớp; lấy cái dài hơn |
| /a/b | r2 | khớp đúng (Kong: regex có neo hai đầu) |
| /a/b/c | r3 | prefix dài nhất |
| /a/b/c/d | r3 | prefix dài nhất |
| /a/x.png | r4 | regex thắng prefix |
| /a/b/c/x.png | r4 | regex thắng cả prefix dài hơn |
| /z | r5 | chỉ còn route bắt tất cả |
Dòng áp chót là chỗ đáng chú ý: regex thắng prefix bất kể prefix dài bao nhiêu. Đo
riêng trên Kong với prefix /a/b/c/d/e và regex /a/.*\.png$:
/a/b/c/d/e/x.png đi vào regex, còn /a/b/c/d/e/x.txt mới rơi về
prefix. nginx cũng vậy, và ở nginx có ^~ để chặn bước regex — Kong và Envoy không
có công cụ tương đương.
~/a/b/.*$ và ~/a/.*\.png$ cùng khớp /a/b/x.png.
Đo với hai thứ tự viết ngược nhau: cả hai lần Kong đều chọn ~/a/b/.*$, dù mẫu kia
dài hơn. Nghĩa là không suy ra được từ thứ tự viết, cũng không từ độ dài mẫu. Đặt
regex_priority: 10 cho mẫu .png thì nó thắng — đó là công cụ duy nhất
kiểm soát được, và nên đặt rõ mỗi khi hai regex có thể chồng nhau.
Hai route trùng nhau thì sao
Viết hai route cùng một mẫu /a, trỏ tới hai backend khác nhau:
| gateway | kết quả |
|---|---|
| OpenResty | không khởi động: [emerg] duplicate location "/a" |
| Envoy | khởi động bình thường, route viết trước chạy |
| Kong | khởi động bình thường, và không phải route viết trước chạy |
Hàng cuối cần nói rõ. Đo ba cặp: route tên k1 viết trước, k2 viết sau
thì k2 thắng; zz trước aa sau thì zz thắng;
đảo lại aa trước zz sau thì vẫn zz thắng. Nghĩa là người
thắng đi theo tên route chứ không theo thứ tự viết — mà tên thì không ai chọn với ý
định quyết định ưu tiên. Chỉ nginx từ chối khởi động; hai gateway kia im lặng chọn một cái.
Phòng thí nghiệm
Gõ bộ route của bạn và những đường dẫn cần thử, rồi xem ba gateway trả lời thế nào. Dòng nào ba bên không đồng ý sẽ được tô vàng. Thuật toán là bản viết lại của cả ba và đã khớp 74/74 kết quả curl trên bộ đo ở trên.
Danh sách route
Route nào thắng
Nhớ gì
- “Regex” là ba thứ khác nhau: Envoy khớp toàn bộ, Kong neo ở đầu, nginx tìm ở bất kỳ đâu.
- Envoy lấy route đầu tiên khớp theo thứ tự viết; Kong và nginx sắp lại nên thứ tự viết không ảnh hưởng.
- Với Kong và nginx, regex thắng prefix kể cả prefix dài hơn nhiều.
- Giữa hai regex cùng
regex_priority, Kong chọn theo thứ tự không suy ra được; đặtregex_priorityrõ ràng. - Hai route trùng mẫu: nginx từ chối khởi động, Envoy lấy cái viết trước, Kong lấy một cái không theo thứ tự viết.
Bài tiếp theo hỏi câu còn lại: sau khi đã chọn được route, backend nhận được đường dẫn gì.