Comment Apache APISIX est-il rapide ?

Navendu Pottekkat

Navendu Pottekkat

June 12, 2023

Technology

"Haute vitesse", "latence minimale" et "performance ultime" sont souvent utilisés pour caractériser Apache APISIX. Même lorsque quelqu'un me demande à propos d'APISIX, ma réponse inclut toujours "passerelle API cloud native haute performance".

Les benchmarks de performance (contre Kong, Envoy) confirment que ces caractéristiques sont bien exactes (testez par vous-même).

Haute vitesse, latence minimale et performance ultime

Tests effectués sur 10 tours avec 5000 routes uniques sur Standard D8s v3 (8 vCPUs, 32 GiB de mémoire).

Mais comment APISIX atteint-il cela ?

Pour répondre à cette question, nous devons examiner trois éléments : etcd, les tables de hachage et les arbres radix.

Dans cet article, nous allons explorer les coulisses d'APISIX et voir ce que sont ces éléments et comment ils fonctionnent ensemble pour permettre à APISIX de maintenir une performance maximale tout en gérant un trafic important.

etcd comme centre de configuration

APISIX utilise etcd pour stocker et synchroniser les configurations.

etcd est conçu pour fonctionner comme un magasin de clés-valeurs pour les configurations de systèmes distribués à grande échelle. APISIX est conçu pour être distribué et hautement évolutif dès le départ, et l'utilisation d'etcd plutôt que des bases de données traditionnelles facilite cela.

Architecture d'APISIX

Une autre caractéristique indispensable pour les passerelles API est d'être hautement disponible, en évitant les temps d'arrêt et les pertes de données. Vous pouvez efficacement y parvenir en déployant plusieurs instances d'etcd pour garantir une architecture cloud native tolérante aux pannes.

APISIX peut lire/écrire des configurations depuis/vers etcd avec une latence minimale. Les modifications des fichiers de configuration sont notifiées instantanément, permettant à APISIX de surveiller uniquement les mises à jour d'etcd au lieu d'interroger fréquemment une base de données, ce qui peut ajouter une surcharge de performance.

Ce graphique résume comment etcd se compare aux autres bases de données.

Tables de hachage pour les adresses IP

Les listes d'autorisation/interdiction basées sur les adresses IP sont un cas d'utilisation courant pour les passerelles API.

Pour atteindre une haute performance, APISIX stocke la liste des adresses IP dans une table de hachage et l'utilise pour la correspondance (O(1)) plutôt que de parcourir la liste (O(N)).

À mesure que le nombre d'adresses IP dans la liste augmente, l'impact sur la performance de l'utilisation des tables de hachage pour le stockage et la correspondance devient évident.

Sous le capot, APISIX utilise la bibliothèque lua-resty-ipmatcher pour implémenter cette fonctionnalité. L'exemple ci-dessous montre comment la bibliothèque est utilisée :

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

La bibliothèque utilise des tables Lua qui sont des tables de hachage. Les adresses IP sont hachées et stockées comme indices dans une table, et pour rechercher une adresse IP donnée, il suffit d'indexer la table et de vérifier si elle est nulle ou non.

Stockage des adresses IP dans une table de hachage

Pour rechercher une adresse IP, elle calcule d'abord le hachage (index) et vérifie sa valeur. Si elle est non vide, nous avons une correspondance. Cela se fait en temps constant O(1).

Arbres radix pour le routage

Veuillez me pardonner de vous avoir trompé avec une leçon sur les structures de données ! Mais écoutez-moi, c'est là que cela devient intéressant.

Un domaine clé où APISIX optimise la performance est la correspondance des routes.

APISIX fait correspondre une route avec une demande à partir de son URI, des méthodes HTTP, de l'hôte et d'autres informations (voir routeur). Et cela doit être efficace.

Si vous avez lu la section précédente, une réponse évidente serait d'utiliser un algorithme de hachage. Mais la correspondance des routes est délicate car plusieurs demandes peuvent correspondre à la même route.

Par exemple, si nous avons une route /api/*, alors à la fois /api/create et /api/destroy doivent correspondre à la route. Mais cela n'est pas possible avec un algorithme de hachage.

Les expressions régulières peuvent être une solution alternative. Les routes peuvent être configurées dans une regex, et elles peuvent correspondre à plusieurs demandes sans avoir besoin de coder en dur chaque demande.

Si nous prenons notre exemple précédent, nous pouvons utiliser la regex /api/[A-Za-z0-9]+ pour correspondre à la fois à /api/create et /api/destroy. Des regex plus complexes pourraient correspondre à des routes plus complexes.

Mais les regex sont lentes ! Et nous savons qu'APISIX est rapide. Donc, à la place, APISIX utilise des arbres radix qui sont des arbres de préfixes compressés (trie) qui fonctionnent très bien pour les recherches rapides.

Regardons un exemple simple. Supposons que nous ayons les mots suivants :

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

Un arbre de préfixes les stockerait comme ceci :

Arbre de préfixes

Le parcours en surbrillance montre le mot "rubens".

Un arbre radix optimise un arbre de préfixes en fusionnant les nœuds enfants si un nœud n'a qu'un seul nœud enfant. Notre exemple de trie ressemblerait à ceci en tant qu'arbre radix :

Arbre radix

Le parcours en surbrillance montre toujours le mot "rubens". Mais l'arbre semble beaucoup plus petit !

Lorsque vous créez des routes dans APISIX, APISIX les stocke dans ces arbres.

APISIX peut alors fonctionner parfaitement car le temps nécessaire pour correspondre à une route ne dépend que de la longueur de l'URI dans la demande et est indépendant du nombre de routes (O(K), K est la longueur de la clé/URI).

Ainsi, APISIX sera aussi rapide pour correspondre à 10 routes lorsque vous commencez qu'à 5000 routes lorsque vous montez en charge.

Cet exemple grossier montre comment APISIX peut stocker et faire correspondre des routes en utilisant des arbres radix :

Exemple grossier de correspondance de routes dans APISIX

Le parcours en surbrillance montre la route /user/* où le * représente un préfixe. Ainsi, un URI comme /user/navendu correspondra à cette route. Le code d'exemple ci-dessous devrait clarifier ces idées.

APISIX utilise la bibliothèque lua-resty-radixtree, qui encapsule rax, une implémentation d'arbre radix en C. Cela améliore la performance par rapport à l'implémentation de la bibliothèque en Lua pur.

L'exemple ci-dessous montre comment la bibliothèque est utilisée :

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 = {}
}

-- correspond à la première route
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 = {}
}

-- correspond à la deuxième route
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 = {}
}

-- correspond à la troisième route
-- la valeur de `arg_access` est obtenue à partir de `ngx.var`
ngx.say(rx:match("/admin/nicolas", opts)) -- metadata /admin/name
ngx.say("admin name: ", opts.matched.name) -- admin name: nicolas

La capacité à gérer un grand nombre de routes efficacement a fait d'APISIX la passerelle API de choix pour de nombreux projets à grande échelle.

Regardez sous le capot

Il y a tant de choses que je peux expliquer sur le fonctionnement interne d'APISIX dans un seul article.

Mais le meilleur, c'est que les bibliothèques mentionnées ici et Apache APISIX sont entièrement open source, ce qui signifie que vous pouvez regarder sous le capot et modifier les choses vous-même.

Et si vous pouvez améliorer APISIX pour obtenir ce dernier petit gain de performance, vous pouvez contribuer les modifications au projet et permettre à tout le monde de bénéficier de votre travail.

Tags: