Nedávno jsem přemýšlel (a konečně proč ne?), jak zvýšit výkon našeho webového serveru. Situace začínala být neunosná zejména v množství zabrané paměti, protože na swapu leželo mnoho dat.
Abych byl konkrétnější, popíšu vám počáteční konfiguraci:
Požadavky vyřizoval apache (1.3), který má plno modulů (php, mod_perl, mnoho perlovských knihoven) a vykazoval značné paměťové nároky (40–80 MB rezidentní + 150 MB sdílených dat na proces), ve špičce bylo potřeba i 100 procesů a stejně se některé musely odmítat (modulem tsunami), protože zátěž byla příliš velká a docházelo k výraznému swapovaní. Protože (100 procesů * 60 MB) + 150 MB = 6 GB paměti a server měl jenom 2 GB paměti.
Na další 2 GB momentálně nebylo a stejně by to problém nevyřešilo, takže přišla na řadu optimalizace. Velmi dobrou inspirací mi byla i stránka develooper.com/modperl/performance_tuning.html, kde jsem našel zajímavé řešení nazvané reverzní proxy.
Klasickou proxy jistě každý čtenář rootu zná. Zatímco normální proxy je co nejblíže ke koncovému uživateli, reverzní proxy je na druhé straně „drátu“. Je tedy bezprostředně před aplikačním serverem.
A k čemu je to vlastně dobré, mít před vlastním serverem ještě jednu vrstvu? Uvedu opět příklad ze života:
Na našem serveru vyřizujeme 90 požadavků za sekundu a počítáme každou milisekundu. Podívejme se, co vlastně server nejvíce zdržuje. HTTP hlavička. Ta má řádově stovku bytů. Načtení hlavičky o velikosti 400 bytů trvá sedm milisekund, což je 63 % doby, kterou se server může maximalně zabývat celým požadavkem! No a pokud vám vadí, že na čtení dat z portu vám slouží několik desítek megabajtů veliké monstrum, tak máte první důvod, proč se zabývat reverzní proxy.
Revezní proxy, která může být velmi malá, načte požadavek od uživatele, a až jej celý načte, předá ho dále na aplikační server. I kdyby to bylo po 100megabitové síti, stráví aplikační server komunikací s uživatelem tisíckrát menší dobu. Pokud navíc generování stránky je netriviální úkol, můžete použít reverzní proxy s cachovaním a váš velký aplikační server nemusí trávit svůj drahocenný čas poskytováním statických stránek.
Než se podíváme, jak to celé nastavit, dovolím si uvést náš koncový stav po optimalizaci:
Nyní na serveru běží jako reverzní proxy apache (2.0) s paměťovým modelem worker a je zapnut pouze mod_proxy a nic jiného. Paměťové nároky jsou 13 MB RES + 543 MB SHR na proces. Je spuštěno sedm procesů (každý z nich má 65 threadů). Tj. (7 procesů * 13 MB) + 543 MB = 634 MB. Apache2 posílá své požadavky na localhost, kde běží původní apache. Těch ovšem stačí pouhých 30! Celkem je tedy paměťová náročnost 634 MB (apache2) + (30 * 60 MB) + 150 MB (apache1.3)) = 2,5 GB. Tj. 42 % původní hodnoty. A to prosím s novým nastavením máme zapnuté KeepAlive (samozřejmě jenom na proxy), takže kvalitativní zlepšení by měli poznat i koncoví uživatelé. U původního řešení by bylo čekání několik sekund (sekund!! ježíškovy očička!), po skončení dotazu naprosto nemyslitelné.
Nastavení
Pokud stále ještě čtete, tak vás to asi stále zajímá a chtěli byste vědět, jak to celé bezbolestně nastavit na vašem webu. Tak jdeme na to.
Jako reverzní proxy jsem použil apache2. Původně jsem chtěl použít squid, ale nepodařilo se mi s ním rozchodit virtualní hosting. Proxy, ať už to je apache, squid, nebo jiný program, připojíte na původní IP adresu na port 80 místo původního web serveru. Tak zajistíte, že pro koncového uživatele to bude plně transparentní a nemusí si nikde nic nastavovat, což by v praxi ani nešlo.
Abych předešel nedorozumění: v dalším textu budu používat dva různé apache. Jeden jako reverzní proxy a budu ho označovat jako apache2, druhý coby původního apache, který slouží jako aplikační server, a budu ho označovat jednoduše apache.
V nastavení apache2 zaveďte moduly mod_rewrite a mod_proxy. Pozor: v debianní implicitní konfiguraci zároveň mod_proxy natahuje i mod_cache, který možná nebudete potřebovat. Já jsem ho nepotřeboval, takže se o něm nebudu rozepisovat. Zapněte volbu
ProxyPreserveHost On
Ta zajistí, že apache2 zachová hlavičku Host: z původního požadavku, a můžete tak bez problémů používat virtual hosting. Zapněte přepisování:
RewriteEngine On RewriteCond %{REQUEST_URI} !^/proxy-status RewriteRule (.*) http://localhost$1 [P] ProxyPassReverse / http://localhost/
První řádek zapne přepisování adres. Druhý řádek řekne, že se to netýká dokumentu /proxy-status. Na tuto adresu vám doporučuji pověsit modul mod_status, abyste měli přehled o stavu apache2 pro ladění parametrů, jako jsou MaxSpare a MinSpare. Třetí řádek řekne, že všechny zbývající dotazy se mají přesměrovat přes engine proxy ([P]) na localhost. Poslední řádek zajistí změnu HTTP přesměrování v hlavičkách Location a Content-Location, které obsahují odkaz na localhost zpět na původní server.
Původní apache navěste na localhost:
BindAddress 127.0.0.1
Pokud používáte virtuální hosting, musíte asi změnit i direktivu NameVirtualHost:
NameVirtualHost *
a všechny direktivy VirtualHost:
<VirtualHost *>
Kdo dosud používal hvězdičkovou notaci, změní jenom BindAdress a má o práci méně.
No a mohli bychom být hotovi. Kdo však chce nějak pracovat s IP adresami uživatelů, nechť čte dále. Příchozí požadavky na apache totiž přirozeně přicházejí z 127.0.0.1, což se vám asi nebude líbit. Takže jako řešení jsem použil modul rpaf, který podvrhne apachi jako IP adresu tu, která je uvedena v hlavičce X-Forwarded-For. Nastavení je jednoduché:
<IfModule mod_rpaf.c> RPAFenable On RPAFproxy_ips 127.0.0.1 </IfModule>
Direktiva RPAFproxy_ips říká, že se bude měnit IP adresa jenom od proxy serverů uvedených v parametru.
Tak a nyní je to konečně vše. Pokud máte více serverů, můžete si trošku více vyhrát s přepisováním a postavit reverzní proxy pro více serverů.
P.S. Pokud byste jako proxy chtěli vyzkoušet squid, tak zdenajdete návod, jak na to (v angličtině). Nicméně jak jsem se již zmiňoval, nepodařilo se mi se squidem rozjet virtuální hosting.