Wenn Sie Änderungen an einer Anwendung testen, gibt es einige Faktoren, die Sie nur in einer Produktionsumgebung und nicht in einem Entwicklungstestbett messen können. Beispiele hierfür sind die Auswirkungen von UI-Änderungen auf das Benutzerverhalten und die Auswirkungen auf die Gesamtleistung. Eine gängige Testmethode ist das A/B-Testing – auch Split-Testing genannt – bei dem ein (normalerweise kleiner) Teil der Benutzer auf die neue Version einer Anwendung umgeleitet wird, während die meisten Benutzer weiterhin die aktuelle Version verwenden.
In diesem Blogbeitrag untersuchen wir, warum es wichtig ist, beim Bereitstellen neuer Versionen Ihrer Webanwendung A/B-Tests durchzuführen und wie Sie mit NGINX und NGINX Plus steuern, welche Version einer Anwendung den Benutzern angezeigt wird. Konfigurationsbeispiele veranschaulichen, wie Sie NGINX- und NGINX Plus-Direktiven, -Parameter und -Variablen verwenden können, um genaue und messbare A/B-Tests durchzuführen.
Wie bereits erwähnt, können Sie mit A/B-Tests die Unterschiede in der Anwendungsleistung oder -effektivität zwischen zwei Versionen messen. Vielleicht möchte Ihr Entwicklungsteam die visuelle Anordnung der Schaltflächen in der Benutzeroberfläche ändern oder den gesamten Einkaufswagenprozess überarbeiten, möchte dabei jedoch die Abschlussrate der Transaktionen vergleichen, um sicherzustellen, dass die Änderung die gewünschten geschäftlichen Auswirkungen hat. Mithilfe von A/B-Tests können Sie einen bestimmten Prozentsatz des Datenverkehrs an die neue Version und den Rest an die alte Version senden und die Effektivität beider Versionen der Anwendung messen.
Oder vielleicht machen Sie sich weniger Sorgen über die Auswirkungen auf das Benutzerverhalten als vielmehr über die Auswirkungen auf die Leistung. Angenommen, Sie planen die Implementierung umfangreicher Änderungen an Ihrer Webanwendung und sind der Meinung, dass die Tests in Ihrer Qualitätssicherungsumgebung die möglichen Auswirkungen auf die Leistung in der Produktion nicht wirklich erfassen. In diesem Fall können Sie mit einer A/B-Bereitstellung die neue Version einem kleinen, festgelegten Prozentsatz von Besuchern zugänglich machen, um die Auswirkungen der Änderungen auf die Leistung zu messen und den Prozentsatz schrittweise zu erhöhen, bis Sie die geänderte Anwendung schließlich für alle Nutzer verfügbar machen.
NGINX und NGINX Plus bieten einige Methoden zur Steuerung, wohin der Datenverkehr von Webanwendungen gesendet wird. Die erste Methode ist in beiden Produkten verfügbar, während die zweite nur in NGINX Plus verfügbar ist.
Beide Methoden wählen das Ziel einer Anfrage basierend auf den Werten einer oder mehrerer NGINX-Variablen aus, die Merkmale des Clients (wie seine IP-Adresse) oder der Anfrage-URI (wie ein benanntes Argument) erfassen. Aufgrund ihrer Unterschiede eignen sie sich jedoch für unterschiedliche Anwendungsfälle beim A/B-Testen:
split_clients
wählt das Ziel einer Anfrage basierend auf einem Hash der aus der Anfrage extrahierten Variablenwerte. Die Menge aller möglichen Hashwerte wird auf die Anwendungsversionen aufgeteilt und Sie können jeder Anwendung einen unterschiedlichen Anteil dieser Menge zuweisen. Die Wahl des Zielortes erfolgt letztlich nach dem Zufallsprinzip.Sticky
-Route-
Methode haben Sie eine viel bessere Kontrolle über das Ziel jeder Anfrage. Die Auswahl der Anwendung erfolgt auf Basis der Variablenwerte selbst (nicht eines Hashes), so dass Sie explizit festlegen können, welche Anwendung Anfragen mit bestimmten Variablenwerten empfängt. Sie können auch reguläre Ausdrücke verwenden, um die Entscheidung nur auf Teile eines Variablenwerts zu stützen, und können einer Variable gegenüber einer anderen den Vorzug als Grundlage für die Entscheidung geben.split_clients
Bei dieser Methode legt der Konfigurationsblock split_clients
für jede Anfrage eine Variable fest, die bestimmt, an welche Upstream-Gruppe die Direktive proxy_pass
die Anfrage sendet. In der folgenden Beispielkonfiguration bestimmt der Wert der Variable „$appversion“
, wohin die Direktive „proxy_pass“
eine Anforderung sendet. Der Block split_clients
verwendet eine Hash-Funktion, um den Wert der Variablen dynamisch auf einen von zwei Upstream-Gruppennamen festzulegen, entweder version_1a oder version_1b .
http { # ... # Application 1a Upstream-Version_1a { Server 10.0.0.100:3001; Server 10.0.0.101:3001; } # Application 1b Upstream-Version_1b { Server 10.0.0.104:6002; Server 10.0.0.105:6002; } split_clients "${arg_token} " $appversion { 95% version_1a; * version_1b; } Server { # ... hören 80; Standort / { proxy_set_header Host $host; proxy_pass http://$appversion; } } }
Der erste Parameter der Direktive split_clients
ist die Zeichenfolge ( "${arg_token} "
in unserem Beispiel), das bei jeder Anfrage mit einer MurmurHash2-Funktion gehasht wird. URI-Argumente stehen NGINX als Variablen mit der Bezeichnung $ arg_name
zur Verfügung – in unserem Beispiel erfasst die Variable $arg_token
das URI-Argument mit der Bezeichnung token . Sie können jede NGINX-Variable oder Variablenfolge als zu hashende Zeichenfolge verwenden. Sie können beispielsweise die IP-Adresse des Clients (die Variable $remote_addr
), den Port ( $remote_port
) oder eine Kombination aus beiden hashen. Sie möchten eine Variable verwenden, die generiert wird, bevor die Anfrage von NGINX verarbeitet wird. Ideal sind Variablen, die Informationen zur ursprünglichen Anfrage des Clients enthalten; Beispiele hierfür sind die bereits erwähnte IP-Adresse/der Port des Clients, die Anfrage-URI oder sogar HTTP-Anfrageheader.
Der zweite Parameter der Direktive split_clients
(in unserem Beispiel $appversion
) ist die Variable, die dynamisch entsprechend dem Hash des ersten Parameters festgelegt wird. Die Anweisungen in den geschweiften Klammern unterteilen die Hash-Tabelle in „Buckets“, von denen jeder einen Prozentsatz der möglichen Hashes enthält. Sie können beliebig viele Buckets erstellen und sie müssen nicht alle gleich groß sein. Beachten Sie, dass der Prozentsatz für den letzten Bucket immer durch ein Sternchen (*) und nicht durch eine bestimmte Zahl dargestellt wird, da die Anzahl der Hashes möglicherweise nicht gleichmäßig in die angegebenen Prozentsätze aufgeteilt werden kann.
In unserem Beispiel legen wir 95 % der Hashes in einen Bucket, der mit der Upstream-Gruppe Version_1a verknüpft ist, und den Rest in einen zweiten Bucket, der mit Version_1b verknüpft ist. Der Bereich der möglichen Hashwerte reicht von 0 bis 4.294.967.295, sodass der erste Bucket Werte von 0 bis etwa 4.080.218.930 (95 % der Gesamtzahl) enthält. Die Variable „$appversion“
wird auf den Upstream gesetzt, der mit dem Bucket verknüpft ist, der den Hash der Variable „$arg_token“
enthält. Als konkretes Beispiel fällt der Hashwert 100.000.000 in den ersten Bucket, sodass $appversion
dynamisch auf version_1a gesetzt wird.
split_clients
-KonfigurationUm zu überprüfen, ob der Konfigurationsblock split_clients
wie vorgesehen funktioniert, haben wir eine Testkonfiguration erstellt, die die Anfragen im gleichen Verhältnis wie oben (95 % und der Rest) zwischen zwei Upstream-Gruppen aufteilt. Wir haben die virtuellen Server in den Gruppen so konfiguriert, dass sie eine Zeichenfolge zurückgeben, die angibt, welche Gruppe – Version_1a oder Version_1b – die Anforderung verarbeitet hat (die Testkonfiguration können Sie hier sehen). Anschließend haben wir mit curl
20 Anfragen generiert, wobei der Wert des URI- Argumenttokens durch Ausführen des cat
-Befehls auf der urandom-
Datei zufällig festgelegt wurde. Dies dient ausschließlich Demonstrations- und Zufallszwecken. Wie beabsichtigt wurde 1 von 20 Anfragen (95 %) von Version_1b bedient (der Kürze halber zeigen wir nur 10 der Anfragen).
# für x in {1..20}; führe curl 127.0.0.1?token=$(cat /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w 32 | head -n 1) aus; fertig Token: p3Fsa86HfJDFwZ9ZnYz4QbaZLzcb70Ka. Ausgeliefert von Siteversion_1a.
Token: a7z7afUz7zQijXwsLGfj6mWyPU8sCRIZ. Bereitgestellt von Site-Version_1a.
Zeichen: CjIr6W57NtPzChf4qbYMzD1Estif7jOH Ausgeliefert von Site-Version_1a. ... Ausgabe für 10 Anfragen ausgelassen ...
Token: gXq8cbG3jhl1LuYmICPTfDQT855gaO5y. Bereitgestellt von Site-Version_1a.
Zeichen: VVqGuNN3cRU2slsl5AWOR93aQX8nXOpk. Wird von Site-Version_1a bereitgestellt.
Token: z7KnewxTX5Sp6wscT0fgmTDohTsQuCmy . Bereitgestellt von Site-Version_1b!!
Token: fWOgH9WRrR0kLJZcIaYchpLhceaQgPD1. Bereitgestellt von Site-Version_1a.
Token: mTADMXrVnwnr1cd5JE6QCSkgTwfWUnDk. Bereitgestellt von Site-Version_1a.
Token: w7AzSNmNJtxWZaH6cXe2PWIFqst2o3oP. Bereitgestellt von Site-Version_1a.
Zeichen: QR7ay0dA39MmVlXtzgOVsj6SBTPi8ECC. Wird von Siteversion_1a bereitgestellt.
Sticky
-Route-
MethodeIn einigen Fällen möchten Sie möglicherweise eine statische Route definieren, indem Sie Client-Routing-Entscheidungen basierend auf dem gesamten oder einem Teil des Werts einer NGINX-Variable treffen. Sie können dies mit der Sticky
-Route-
Direktive tun, die nur in NGINX Plus verfügbar ist. Die Direktive nimmt eine Liste mit einem oder mehreren Parametern und legt die Route auf den Wert des ersten nicht leeren Parameters in der Liste fest. Mithilfe dieser Funktion können wir die Variable aus der Anforderung, die die Zielauswahl steuert, bevorzugt einstufen und so mehr als eine Methode zur Verkehrsaufteilung in einer einzigen Konfiguration unterbringen.
Es gibt zwei verschiedene Ansätze zur Verwendung dieser Methode.
User-Agent
des Clients.Sticky-
Route-
Direktive extrahiert den Routenindikator und leitet die Anfrage an den entsprechenden Server weiter.In unserem Beispiel verwenden wir den anwendungsseitigen Ansatz: Die Sticky
-Route-
Direktive in der Upstream-
Gruppe setzt die Route vorzugsweise auf den Wert, der im vom Server bereitgestellten Cookie angegeben ist (erfasst in $route_from_cookie
). Wenn der Client kein Cookie hat, wird die Route auf einen Wert aus einem Argument der Anforderungs-URI ( $route_from_uri
) gesetzt. Der Routenwert bestimmt dann, welcher Server
in der Upstream-Gruppe die Anforderung erhält – der erste Server, wenn die Route a
ist, der zweite Server, wenn die Route b
ist (die beiden Server entsprechen den beiden Versionen unserer Anwendung).
Upstream-Backend { Zone Backend 64k;
Server 10.0.0.200:8098 Route=a;
Server 10.0.0.201:8099 Route=b;
Sticky-Route $route_from_cookie $route_from_uri;
}
Aber das a
oder b
ist in eine viel längere Zeichenfolge im eigentlichen Cookie oder URI eingebettet. Um nur den Buchstaben zu extrahieren, konfigurieren wir für jedes Cookie und jede URI einen Map-
Konfigurationsblock:
Karte $cookie_route $route_from_cookie { ~.(?P<route>w+)$ $route;
}
Karte $arg_route $route_from_uri {
~.(?P<route>w+)$ $route;
}
Im ersten Map-
Block stellt die Variable $cookie_route
den Wert eines Cookies namens ROUTE dar. Der reguläre Ausdruck in der zweiten Zeile, der die PCRE-Syntax ( Perl Compatible Regular Expression ) verwendet, extrahiert einen Teil des Werts – in diesem Fall die Zeichenfolge ( w+
) nach dem Punkt – in die benannte Capture-Group- Route
und weist ihn der internen Variable mit diesem Namen zu. Der Wert wird in der ersten Zeile auch der Variable „$route_from_cookie“
zugewiesen, wodurch er für die Übergabe an die Sticky
-Route-
Direktive verfügbar wird.
Beispielsweise extrahiert der erste Map-
Block den Wert „ a “ aus diesem Cookie und weist ihn $route_from_cookie
zu:
ROUTE=iDmDe26BdBDS28FuVJlWc1FH4b13x4fn .a
Im zweiten Map-
Block stellt die Variable $arg_route
ein Argument namens Route in der Anforderungs-URI dar. Wie beim Cookie extrahiert der reguläre Ausdruck in der zweiten Zeile einen Teil der URI – in diesem Fall ist es die Zeichenfolge ( w+
) nach dem Punkt im Routenargument . Der Wert wird in die benannte Erfassungsgruppe gelesen, einer internen Variablen zugewiesen und auch der Variablen „$route_from_uri
“ zugewiesen.
Als Beispiel extrahiert der zweite Map-
Block den Wert b aus dieser URI und weist ihn $route_from_uri
zu:
www.example.com/shopping/my-cart?route=iLbLr35AeAET39GvWK2Xd2GI5c24y5go. b
Hier ist die vollständige Beispielkonfiguration.
http { # ...
map $cookie_route $route_from_cookie {
~.(?P<route>w+)$ $route;
}
map $arg_route $route_from_uri {
~.(?P<route>w+)$ $route;
}
Upstream-Backend {
Zone-Backend 64k;
Server 10.0.0.200:8098 Route=a;
Server 10.0.0.201:8099 Route=b;
Sticky-Route $route_from_cookie $route_from_uri;
}
Server {
Listen 80;
Standort / {
# ...
Proxy-Pass http://backend;
}
}
}
Sticky-
Route-
KonfigurationFür die Methode split_clients
haben wir eine Testkonfiguration erstellt, auf die Sie hier zugreifen können. Wir haben curl
entweder verwendet, um ein Cookie mit dem Namen ROUTE zu senden oder um ein Routenargument in die URI einzuschließen. Der Wert des Cookies oder Arguments ist eine zufällige Zeichenfolge, die durch Ausführen des Befehls cat
auf der Datei urandom
generiert wird, wobei .a oder .b angehängt wird.
Zuerst testen wir mit einem Cookie, das mit .a endet:
# curl --cookie "ROUTE=$(cat /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w 32 | head -n 1).a" 127.0.0.1 Cookie-Wert: R0TdyJOJvxBkLC3f75Coa29I1pPySOeQ.a Anforderungs-URI: / Ergebnisse: Site A – Wird auf Port 8089 ausgeführt
Dann testen wir mit einem Cookie, das auf .b endet.
# curl --cookie "ROUTE=$(cat /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w 32 | head -n 1).b" 127.0.0.1 Cookie-Wert: JhdJZrScTnPBLhqmzK3podNRcJAIc8ST.b Anforderungs-URI: / Ergebnisse: Site B - Wird auf Port 8099 ausgeführt
Abschließend testen wir ohne Cookie und stattdessen mit einem Routenargument in der Anforderungs-URI, das auf .a endet. Die Ausgabe bestätigt, dass NGINX Plus den aus der URI abgeleiteten Routenwert verwendet, wenn kein Cookie vorhanden ist (das Feld „Cookie
-Wert
“ ist leer).
# curl 127.0.0.1?route=$(cat /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w 32 | head -n 1).a Cookie-Wert:
Anforderungs-URI: /?route=yNp8pHskvukXK6XqbWefhVUcOBjbJv4v.a Ergebnisse: Site A – Wird auf Port 8089 ausgeführt
Die hier beschriebene Art von Test reicht aus, um zu überprüfen, ob eine Konfiguration die Anfragen wie beabsichtigt verteilt. Für die Interpretation der Ergebnisse eines tatsächlichen A/B-Tests ist jedoch eine viel detailliertere Protokollierung und Analyse der Art und Weise erforderlich, wie die Anfragen verarbeitet werden. Die richtige Vorgehensweise bei der Protokollierung und Analyse hängt von vielen Faktoren ab und geht über den Rahmen dieses Beitrags hinaus, aber NGINX und NGINX Plus bieten eine ausgefeilte, integrierte Protokollierung und Überwachung der Anforderungsverarbeitung.
Mit der Direktive „log_format“
können Sie ein benutzerdefiniertes Protokollformat definieren, das alle NGINX-Variablen enthält. Die in den NGINX-Protokollen aufgezeichneten Variablenwerte stehen dann zu einem späteren Zeitpunkt für eine Analyse zur Verfügung. Einzelheiten zur benutzerdefinierten Protokollierung und Laufzeitüberwachung finden Sie im NGINX Plus-Administratorhandbuch .
Achten Sie beim Entwerfen eines Experiments oder A/B-Testplans darauf, dass die Art und Weise, wie Sie die Anfragen zwischen Ihren Anwendungsversionen verteilen, die Ergebnisse nicht vorwegnimmt. Wenn Sie ein völlig zufälliges Experiment wünschen, liefert die Verwendung der Methode split_clients
und das Hashen einer Kombination aus mehreren Variablen die besten Ergebnisse. So bietet beispielsweise das Generieren eines eindeutigen experimentellen Tokens auf Grundlage der Kombination aus Cookie und Benutzer-ID einer Anfrage ein zufälligeres Testmuster als das Hashen nur des Browsertyps und der Browserversion des Clients, weil die Wahrscheinlichkeit groß ist, dass viele Benutzer denselben Browsertyp und dieselbe Version haben und daher alle auf dieselbe Version der Anwendung umgeleitet werden.
Sie müssen auch berücksichtigen, dass viele Benutzer zur sogenannten gemischten Gruppe gehören. Sie greifen von mehreren Geräten aus auf Webanwendungen zu – möglicherweise sowohl von ihrem Arbeits- und Heimcomputer als auch von einem mobilen Gerät wie einem Tablet oder Smartphone. Solche Benutzer verfügen über mehrere Client-IP-Adressen. Wenn Sie also die Client-IP-Adresse als Grundlage für die Auswahl der Anwendungsversion verwenden, werden ihnen möglicherweise beide Versionen Ihrer Anwendung angezeigt, wodurch Ihre Versuchsergebnisse verfälscht werden.
Die wahrscheinlich einfachste Lösung besteht darin, von den Benutzern eine Anmeldung zu verlangen, damit Sie ihre Sitzungscookies wie in unserem Beispiel der Sticky
-Route
-Methode verfolgen können. Auf diese Weise können Sie sie verfolgen und ihnen immer die gleiche Version senden, die sie beim ersten Zugriff auf den Test gesehen haben. Wenn dies nicht möglich ist, ist es manchmal sinnvoll, Benutzer in Gruppen einzuteilen, die sich während des Tests voraussichtlich nicht ändern. Beispielsweise können Sie mithilfe der Geolokalisierung Benutzern in Los Angeles eine Version und Benutzern in San Francisco eine andere Version anzeigen.
A/B-Tests sind eine effektive Möglichkeit, Änderungen an Ihrer Anwendung zu analysieren und zu verfolgen und die Anwendungsleistung zu überwachen, indem unterschiedliche Datenverkehrsmengen auf alternative Server aufgeteilt werden. Sowohl NGINX als auch NGINX Plus stellen Anweisungen, Parameter und Variablen bereit, mit denen ein solides Framework für A/B-Tests erstellt werden kann. Sie ermöglichen Ihnen außerdem, wertvolle Details zu jeder Anfrage zu protokollieren. Viel Spaß beim Testen!
Probieren Sie NGINX Plus und die Sticky
-Route
-Methode selbst aus – starten Sie noch heute Ihre kostenlose 30-Tage-Testversion oder kontaktieren Sie uns, um Ihre Anwendungsfälle zu besprechen .
„Dieser Blogbeitrag kann auf Produkte verweisen, die nicht mehr verfügbar und/oder nicht mehr unterstützt werden. Die aktuellsten Informationen zu verfügbaren F5 NGINX-Produkten und -Lösungen finden Sie in unserer NGINX-Produktfamilie . NGINX ist jetzt Teil von F5. Alle vorherigen NGINX.com-Links werden auf ähnliche NGINX-Inhalte auf F5.com umgeleitet."