Bagaimana Apache APISIX Bisa Cepat?

Navendu Pottekkat

Navendu Pottekkat

June 12, 2023

Technology

"Kecepatan tinggi," "latensi minimum," dan "kinerja ultimate" sering digunakan untuk menggambarkan Apache APISIX. Bahkan ketika seseorang bertanya kepada saya tentang APISIX, jawaban saya selalu mencakup "API gateway cloud native berkinerja tinggi."

Benchmark kinerja (vs. Kong, Envoy) mengonfirmasi bahwa karakteristik ini memang akurat (uji sendiri).

Kecepatan tinggi, latensi minimum, dan kinerja ultimate

Tes dijalankan selama 10 putaran dengan 5000 rute unik pada Standard D8s v3 (8 vCPU, 32 GiB memori).

Tapi bagaimana APISIX mencapai ini?

Untuk menjawab pertanyaan itu, kita harus melihat tiga hal: etcd, tabel hash, dan pohon radix.

Dalam artikel ini, kita akan melihat lebih dalam tentang APISIX dan melihat apa itu serta bagaimana semua ini bekerja bersama untuk menjaga APISIX tetap mempertahankan kinerja puncak saat menangani lalu lintas yang signifikan.

etcd sebagai Pusat Konfigurasi

APISIX menggunakan etcd untuk menyimpan dan menyinkronkan konfigurasi.

etcd dirancang untuk bekerja sebagai penyimpanan key-value untuk konfigurasi sistem terdistribusi skala besar. APISIX dimaksudkan untuk didistribusikan dan sangat skalabel sejak awal, dan menggunakan etcd daripada database tradisional memfasilitasi hal itu.

Arsitektur APISIX

Fitur penting lain yang tidak bisa diabaikan untuk API gateway adalah ketersediaan tinggi, menghindari downtime dan kehilangan data. Anda dapat mencapai ini dengan efisien dengan menerapkan beberapa instance etcd untuk memastikan arsitektur cloud native yang toleran terhadap kesalahan.

APISIX dapat membaca/menulis konfigurasi dari/ke etcd dengan latensi minimum. Perubahan pada file konfigurasi diberitahukan secara instan, memungkinkan APISIX untuk memantau hanya pembaruan etcd alih-alih melakukan polling database secara sering, yang dapat menambah overhead kinerja.

Chart ini merangkum bagaimana etcd dibandingkan dengan database lain.

Tabel Hash untuk Alamat IP

Daftar izin/blokir berbasis alamat IP adalah kasus penggunaan umum untuk API gateway.

Untuk mencapai kinerja tinggi, APISIX menyimpan daftar alamat IP dalam tabel hash dan menggunakannya untuk pencocokan (O(1)) daripada mengulang melalui daftar (O(N)).

Seiring bertambahnya jumlah alamat IP dalam daftar, dampak kinerja dari penggunaan tabel hash untuk penyimpanan dan pencocokan menjadi jelas.

Di balik layar, APISIX menggunakan library lua-resty-ipmatcher untuk mengimplementasikan fungsi ini. Contoh di bawah ini menunjukkan bagaimana library digunakan:

local ipmatcher = require("resty.ipmatcher") local ip = ipmatcher.new({ "162.168.46.72", "17.172.224.47", "216.58.32.170", }) ngx.say(ip:match("17.172.224.47")) -- true ngx.say(ip:match("176.24.76.126")) -- false

Library ini menggunakan tabel Lua yang merupakan tabel hash. Alamat IP di-hash dan disimpan sebagai indeks dalam tabel, dan untuk mencari alamat IP tertentu, Anda hanya perlu mengindeks tabel dan menguji apakah itu nil atau tidak.

Menyimpan alamat IP dalam tabel hash

Untuk mencari alamat IP, pertama-tama menghitung hash (indeks) dan memeriksa nilainya. Jika tidak kosong, kita memiliki kecocokan. Ini dilakukan dalam waktu konstan O(1).

Pohon Radix untuk Routing

Mohon maaf karena menipu Anda dengan pelajaran struktur data! Tapi dengarkan saya; ini adalah bagian yang menarik.

Area kunci di mana APISIX mengoptimalkan kinerja adalah pencocokan rute.

APISIX mencocokkan rute dengan permintaan dari URI-nya, metode HTTP, host, dan informasi lainnya (lihat router). Dan ini perlu efisien.

Jika Anda telah membaca bagian sebelumnya, jawaban yang jelas adalah menggunakan algoritma hash. Tetapi pencocokan rute rumit karena beberapa permintaan dapat cocok dengan rute yang sama.

Misalnya, jika kita memiliki rute /api/*, maka baik /api/create dan /api/destroy harus cocok dengan rute tersebut. Tetapi ini tidak mungkin dengan algoritma hash.

Ekspresi reguler bisa menjadi solusi alternatif. Rute dapat dikonfigurasi dalam regex, dan itu dapat mencocokkan beberapa permintaan tanpa perlu meng-hardcode setiap permintaan.

Jika kita mengambil contoh sebelumnya, kita dapat menggunakan regex /api/[A-Za-z0-9]+ untuk mencocokkan /api/create dan /api/destroy. Regex yang lebih kompleks dapat mencocokkan rute yang lebih kompleks.

Tapi regex lambat! Dan kita tahu APISIX cepat. Jadi, APISIX menggunakan pohon radix yang merupakan pohon prefiks terkompresi (trie) yang bekerja sangat baik untuk pencarian cepat.

Mari kita lihat contoh sederhana. Misalkan kita memiliki kata-kata berikut:

  • romane
  • romanus
  • romulus
  • rubens
  • ruber
  • rubicon
  • rubicundus

Pohon prefiks akan menyimpannya seperti ini:

Pohon prefiks

Traversal yang disorot menunjukkan kata "rubens."

Pohon radix mengoptimalkan pohon prefiks dengan menggabungkan node anak jika sebuah node hanya memiliki satu node anak. Contoh trie kita akan terlihat seperti ini sebagai pohon radix:

Pohon radix

Traversal yang disorot masih menunjukkan kata "rubens." Tapi pohonnya terlihat jauh lebih kecil!

Ketika Anda membuat rute di APISIX, APISIX menyimpannya dalam pohon-pohon ini.

APISIX kemudian dapat bekerja dengan sempurna karena waktu yang dibutuhkan untuk mencocokkan rute hanya bergantung pada panjang URI dalam permintaan dan tidak bergantung pada jumlah rute (O(K), K adalah panjang kunci/URI).

Jadi APISIX akan secepat itu ketika mencocokkan 10 rute saat Anda pertama kali memulai dan 5000 rute saat Anda melakukan scaling.

Contoh kasar ini menunjukkan bagaimana APISIX dapat menyimpan dan mencocokkan rute menggunakan pohon radix:

Contoh kasar pencocokan rute di APISIX

Traversal yang disorot menunjukkan rute /user/* di mana * mewakili prefiks. Jadi URI seperti /user/navendu akan cocok dengan rute ini. Contoh kode di bawah ini seharusnya memberikan kejelasan lebih pada ide-ide ini.

APISIX menggunakan library lua-resty-radixtree, yang membungkus rax, implementasi pohon radix dalam C. Ini meningkatkan kinerja dibandingkan dengan mengimplementasikan library dalam Lua murni.

Contoh di bawah ini menunjukkan bagaimana library digunakan:

local radix = require("resty.radixtree") local rx = radix.new({ { paths = { "/api/*action" }, metadata = { "metadata /api/action" } }, { paths = { "/user/:name" }, metadata = { "metadata /user/name" }, methods = { "GET" }, }, { paths = { "/admin/:name" }, metadata = { "metadata /admin/name" }, methods = { "GET", "POST", "PUT" }, filter_fun = function(vars, opts) return vars["arg_access"] == "admin" end } }) local opts = { matched = {} } -- mencocokkan rute pertama ngx.say(rx:match("/api/create", opts)) -- metadata /api/action ngx.say("action: ", opts.matched.action) -- action: create ngx.say(rx:match("/api/destroy", opts)) -- metadata /api/action ngx.say("action: ", opts.matched.action) -- action: destroy local opts = { method = "GET", matched = {} } -- mencocokkan rute kedua ngx.say(rx:match("/user/bobur", opts)) -- metadata /user/name ngx.say("name: ", opts.matched.name) -- name: bobur local opts = { method = "POST", var = ngx.var, matched = {} } -- mencocokkan rute ketiga -- nilai untuk `arg_access` diperoleh dari `ngx.var` ngx.say(rx:match("/admin/nicolas", opts)) -- metadata /admin/name ngx.say("admin name: ", opts.matched.name) -- admin name: nicolas

Kemampuan untuk mengelola sejumlah besar rute secara efisien telah membuat APISIX menjadi API gateway pilihan untuk banyak proyek skala besar.

Lihat di Balik Layar

Hanya ada begitu banyak yang bisa saya jelaskan tentang cara kerja internal APISIX dalam satu artikel.

Tetapi bagian terbaiknya adalah bahwa library yang disebutkan di sini dan Apache APISIX adalah sepenuhnya open source, artinya Anda dapat melihat di balik layar dan memodifikasi sendiri.

Dan jika Anda dapat meningkatkan APISIX untuk mendapatkan sedikit kinerja tambahan, Anda dapat berkontribusi perubahan kembali ke proyek dan membiarkan semua orang mendapatkan manfaat dari pekerjaan Anda.

Tags: