Nagy terhelésű rendszerek fejlesztése 3.
Ha az előző részek tanácsai nem segítettek egy-két gépen megvalósíthatóvá tenni a feladatot, eljött a pillanat hogy szétrobbantsuk az alkalmazást apró darabokra.
A sorozatban megjelent
- Nagy terhelésű rendszerek fejlesztése 1. – Alkalmazás-evolúció
- Nagy terhelésű rendszerek fejlesztése 2.
- Nagy terhelésű rendszerek fejlesztése 3.
- Nagy terhelésű rendszerek fejlesztése 4. – Üzemeltetés
A cikkíró az említett technológiákat látta működni. Arra semmi garancia nincs, hogy az olvasó operációs rendszerén/PHP buildjén/csillagászati együttállásában ugyanúgy fognak működni. Egyszóval: ne tessék vakon az éles rendszer alá tolni mindent, ami itt van. Főleg ne egyszerre.
Célok
Az ember általában két okból vágyik clusterelt működésre. Egyrészt szeretné, ha az alkalmazása akkor is működőképes maradna, ha ne adj’ Isten belevág a ménkű az egyik gépbe. Ezt úgy hívjuk, hogy high availability (HA). azaz nagy rendelkezésre állás. Ez általában a single point of failure, azaz a kritikus hibapont megszüntetése.
Ezzel kapcsolatban egy jó tanács: rengetegszer látok olyan fórumtémákat, ahol valaki például GFS2-t (cluster fájlrendszer) akar üzemeltetni DRBD (blokkszintű diszkreplikáció egy másik gépre) fölött. A technológiák külön-külön nagyon jól működnek, azonban összevegyítve kisebb lesz a rendelkezésre állás, mint a gépeken egyesével.
A másik ok a tényleges load balancing (LB), azaz a terheléselosztás, ami azt jelenti, hogy a bejövő kéréseket több gép szolgálja ki. Ez utóbbi a könnyebben megvalósítható a két feladvány közül.
A HA/LB programozás problémái
A HA/LB rendszerek fejlesztése némiképp különbözik a hagyományos, egygépes fejlesztéstől. Nem lehet csak úgy megmarkolni egy programot, és bevágni egy clusterbe, hogy ott majd jól „terheléseloszlik” és „kiesésmentesedik”. Alapvetően az alkalmazásszintű redundanciát szinte lehetetlen az operációs rendszerben pótolni.
Munkamenetek
Az első trükkös probléma, amibe a kezdő cluster-üzemeltető belefut, a munkameneteké. A PHP például alapértelmezetten fájlokban tárolja a munkamenetek adatait, ami gyakorlatilag elérhetetlenné teszi őket a másik gépről. Egyáltalán nem javaslom a fájlrendszer szintű replikációs megoldásokat (NFS, DRBD stb.), mivel ezek igen könnyen szűk keresztmetszetté válhatnak az egyébként gyors adatelérést igénylő munkamenetkezelésben. Helyette inkább tegyük a sessionöket adatbázisba vagy akár memcache-be. (Természetesen bármilyen más NoSQL megoldás is megfelel.) Az utóbbi azért is érdekes, mert a PHP Memcache könyvtára rendelkezik egy beépített failover mechanizmussal ami vonzóvá teheti HA témakörben is. Ha esetleg ez nem áll rendelkezésre, érdemes megnézni a repcached-et.
Közös fájlok kiszolgálása
Sokszor van szükség statikus fájlok kiszolgálására. Vegyük például a profilképeket egy közösségi oldalon. A felhasználó feltölti a képet, mi lekonvertáljuk. Hogy ez valós időben történik-e vagy időzítjük, ebből a szempontból majdnem lényegtelen, viszont a kimenetet célszerű kirakni egynél több szerverre. Gyakorlatilag lehetetlen a lekonvertált képet az összes szerverünkre egyszerre kirakni.
Próbálkozni nyilván lehet, de sokkal célravezetőbb, ha nyilvántartjuk hogy melyik kép mely szervereken van, és írunk egy háttérfolyamatot, ami biztosítja a kellő szintű redundanciát. Azzal sokat nyerünk, hogy a fájlok elhelyezését háttérfolyamat kezeli. Egyrészt nem kell azon törni a fejünket, hogyan továbbítjuk minél több szervernek ugyanazt az adatot, és nem kell védekezni kiesés ellen, szimplán oda lehet adni a disztributáló programnak. Másrészt dinamikusan kezelhetjük azt, hogy egy képet nagyon sokan néznek meg (mert mondjuk kikerült a Subbára), vagy éppenséggel kiadhatjuk az utasítást egy szerver leürítésére, ha karban kell tartani. Ha kiesik egy kiszolgáló szerver, a háttérfolyamat azt észreveheti, és akár helyre is állíthatja a kötelező redundancia-szintet.
Egy szó mint száz, általánosan az alkalmazás szintjén sokkal kifinomultabb lehetőségeink vannak, mint ha a rendszergazda elintézi a fájlok kimásolását.
Közös fájlok elérése
A statikus fájlok kiszolgálásánál sokkal izgalmasabb problémakör a közös fájlok módosítása. Az esetek nagy többségében elkerülhető, hogy egy fájlba létrehozás után írni kelljen, azonban néhány (hangyányi kevés) esetben van létjogosultsága ennek is. Miután rengeteget töprengtünk azon, hogyan lehetne ezt elkerülni (ha lehet, mindenképpen tegyünk így), ideje elgondolkodni a megvalósításon is. Ha tipikusan egy helyről szerkesztjük a fájlt, akkor érdemes erre kijelölni egy gépet, és az eredményt, amilyen gyorsan csak lehet, a backup gépre átmenteni. Adott esetben itt érdemes DRBD-t vagy osztott storage egységet használni. (Ez utóbbit csak akkor, ha bőre van eresztve a büdzsé.)
Ha a fájl jellemzően több helyről hozzáférhető és módosítják is, érdemes az egészet vagy részeit adatbázisban tárolni, még akkor is, ha esetleg ez csökkentett teljesítményt eredményez.
LB, de hogyan?
Tegyük fel, hogy felkészítettük az alkalmazást arra, hogy HA/LB környezetben működjön, kitettük a kódot n szerverre, amik csak lesik, hogy jöjjön a forgalom. De nem jön, mert egy gépen van fönt az IP cím és csókolom.
Szegény ember balancere: a DNS
Szegény ember vízzel főz, de aki szegény, az igazából ne akarjon load balance-olni, mert csak ráfázik. A legfapadosabb megoldás a terhelés elosztására a DNS-ben fölvenni több A rekordot alacsony TTL-lel, és igény szerint tekerni. Ezt nem is részletezem tovább, aki ilyennel akarja szívatni magát, tegyen úgy, de ebből jó megoldás nem lesz. (Csak azért említettem meg, mert sokszor előkerül kérdésként.)
Reverse proxy
Ha HTTP-ről beszélünk, akkor a reverse proxy a legegyszerűbben konfigurálható és hosztolt gépes környezetben is használható megoldás. Itt semmi más nem történik, mint hogy az LB szerver kibontja a HTTP forgalmat, és tovább dobja valamelyik háttér (backend) szervernek. Ehhez természetesen kapcsolni kell valamilyen szolgáltatás-ellenőrzést is, ami figyeli a háttérszervereket.
Ha SSL-lel működő oldalt építünk, és a belső hálózatunk minimum saját VLAN-ban van, érdemes az SSL-t mindjárt a proxy szerveren terminálni, és belső hálón már kódolatlan forgalmat használni. További előnye, hogy mindenféle HTTP protokoll-beli dologtól (pl. Host
fejléc) tehetjük függővé az elosztást. Ez más megoldások esetén nem volna lehetséges.
A proxynak van egy hatalmas hátránya. Nehéz belőle HA rendszert építeni. Természetesen teljességgel működő megoldás az, hogy valamilyen az operációs rendszer szintjén működő megoldással az IP-címet ide-oda ráncigáljuk, azonban ez egy egészen önálló problémakör, és sok buktatót rejt (beragad a routerbe az ARP bejegyzés, splitbrain esetén mindkét helyen fönt lesz az IP stb.).
NAT
Ha nincs szükségünk a proxy nyújtotta előnyökre, vagy éppenséggel nem rendelkezünk túl sok publikus IP-címmel, érdekes megoldásként szóba jöhet a NAT, azaz a hálózati címfordítás. Ezúttal nem a forráscímet fordítja le a publikus IP-címünkre, mint az otthoni routerek, hanem a célcímet írja át terheléselosztó szerver. Ilyen megoldást például a Linux kernelben lévő IPVS (IP Virtual Server) modul is tud, viszont a proxyhoz hasonlóan ide is kell szolgáltatás-ellenőrzés.
Routing
Ha a cégünk el van látva egy-két hálózatossal és rendelkezünk jó sok publikus IP-vel, elővehetjük a routing módszerét is. Ennek az az előnye, hogy még a NAT-nál is kevesebb erőforrást eszik, és igen jól skálázódik. Ez gyakorlatilag annyit jelent, hogy az LB szervereken a beeső forgalmat nem a cél IP megváltoztatásával küldjük tovább, hanem egy az egyben átpasszoljuk a valós kiszolgálónak az IP-csomagot, aki mint a neki küldött csomagot értelmezi. Egy ilyen megoldás a Linux kernel IPVS moduljában rendelkezésre álló direct routing (gate) üzemmód.
A leírásból remélhetőleg kiviláglik, hogy a routing módszere nem implementálható hálózatos támogatás és tapasztalt rendszergazda nélkül, mivel igen alattomos buktatókat rejt. Ne próbáljátok ki tehát egy hosting centrumban, mert jobb esetben csak levágja a gépeteket a hálózatról.
Adatbázis balancing
Nagyobb adatbázisok és főként bonyolultabb lekérdezések esetén nagyon gyorsan eljön az a pillanat, hogy kikoppan az adatbázis szerver CPU-ja, vörösen izzanak a lemezek, és valahogy mégsem lehet belőle magasabb tempót kipréselni. Szerencsére van megoldás, a legtöbb adatbázisszerver, így például a MySQL is támogat replikációt. A leghatékonyabb megoldás MySQL esetén a master-slave üzemmód, ehhez azonban fel kell készíteni az alkalmazásunkat, hogy valamely véletlenszerűen megválasztott slave szerverről olvasson, és a master szerverre írjon. Ha ez nincs meg, csak master–master replikát vagy fél-intelligens SQL proxy-kat lehet használni, ez azonban kevéssé hatékony.
Egy másik, ennél is több gondolkodást igénylő megoldás a sharding, avagy a particionálás. Ebben valamilyen logika szerint szétvágjuk az adatbázisunkat (felhasználók kezdőbetűi, felhasználónév hash stb.), és ezeket az adatokat különböző szervereken tároljuk. A particionáláshoz érdemes olyan kulcsot választani, amit szinte biztosan nem akarunk egy menetben kiolvasni. Egy látogatottabb oldalnál a felhasználónév jó eséllyel ilyen, hiszen ki akarna a user
táblából több százezer rekordot kiolvasni? (Ha MySQL-t használunk, még emiatt sem kell főnie a fejünknek, hiszen van beépített támogatás a particionálásra.)
Konzisztencia vs. rendelkezésre állás
Bizonyára már feltűnt, hogy egy-két ponton olyan megoldások kerülnek említésre, amik adatvesztést okozhatnak. A leendő rendszer tervezésénél fontos kérdés az, hogy teljes adatkonzisztenciára vagy 100%-os rendelkezésre állásra vágyunk. Ha az előbbi a cél, nem tehetjük meg például azt, hogy leállás nélkül kifrissítjük a működő rendszer alá az új kódot, hanem le kell állítani, ki kell frissíteni minden szerverre az új verziót, és újra kell indítani a szolgáltatást. Ha viszont bevállalunk tranziens hibákat (megszakadt oldalletöltés, sikertelen képfeltöltés stb.) akkor jóval közelebb kerülünk az áhított 100%-os jelzéshez.
Záró gondolatok
Természetesen a technológiák nyers felsorolásával éppen csak megkarcoltuk azon problémákat, amelyek a nagy terhelésű rendszerek fejlesztését jellemzik. Talán látszik az, hogy meg kell tanulni másképp gondolkodni. Egy óriási, sokadik normálformás adatbázis helyett érdemes kisebb, önállóan is működőképes darabokat alkotni; a szerverek közötti adatmozgatásokat érdemes aszinkron módon végezni, és még sorolhatnám.
Mint azt már korábban is említettem, nem érdemes egyből nekiszaladni architekturális felhőkarcolót építeni, ráér az egy fél év múlva is, ha már felfutott az alkalmazás. Ha viszont arra a pár dologra, amire biztosan szükség lesz (adatbázis replika stb.) már készen áll az alkalmazás, a továbbépítés sem lesz olyan borzalmasan bonyolult.
Köszönöm Tyraelnek és Kayaponak a cikk megírásában nyújtott segítséget.
■
:) ez is jó cikk, mint az
Önálló
Jófajta cikk
A sorozatnak ez a része is jól sikerült.
Hasznos hiánypótló olvasmány lett, szerintem a terjedelmével sincs baj.
Köszi érte!
"Konzisztencia vs.
http://pooteeweet.org/blog/1785
itt eleg jol ki lett vesezve a tema.
inkabb a DRBD vs replikacio kapcsan lehetett volna megemliteni, hogy ha DRBD-vel csinalod a failovert, akkor csak 1 node-on futhat egyszerre a DB, ergo csak cold spare-ed van, ergo a failover eseteben vesztesz a rendelkezesre allasbol
mig ha replikaciot hasznalsz, akkor azonnal tudsz failover eseten mukodo node-ra valtani, viszont a konzisztencia neha ennek karat lathatja
Tyrael
Replikáció
csak idot kellene talalni ra.
mar csak idot kellene talalni a cikk megirasara, illetve nem elveszni a reszletekben, amihez nem artana valami tematikat kitalalnom, amire fel lehetne fuzni egy cikksorozatot. :)
Tyrael
Sharding és particionálás
A mysql esetén innoDB engine-nel a particionálás az adatbázis szerveren belüli optimalizálás, amelynek a segítségével az egy táblán belüli adatainkat osztjuk szét file-rendszer szinten particiókra, amelyek különböző fizikai meghajtókon lehetnek, így optimalizálható az I/O terhelés. Ez igazából a DBA vagy rendszergazda részéről igényel gondolkodást, bár véleményem szerint nem árt, ha a fejlesztők is végiggondolják a lehetőségeket.
A sharding esetén az adatokat több azonos szerkezetű táblákba osztjuk szét az alkalmazott logika alapján, amelyek gyakran más adatbázis szerverekre kerülnek szétosztásra. Itt már több kihívással is találkozni fogunk, és fejlesztői oldalon kell felkészülni rá, pl: shardok közti joinok elkerülése/kiváltása, elosztott lekérdezések, az autoincrementes kulcsok kezelése, stb.
eloszor en is szova akartam
Utolag valoszinuleg jobb lett volna ha ugy van felvezetve, hogy horizontalis(row splitting) vagy vertikalis particionalas(column splitting).
Illetve hogy hogy transzparens vagy nem transzparens modon tortenik a particionalas.
De nem hiszem, hogy egy ennyire reszletes leiras feltetlenul ennek a cikknek kellene a reszet kepeznie.
Tyrael
Session memcachebe?
azt irod:
"Egyáltalán nem javaslom a fájlrendszer szintű replikációs megoldásokat (NFS, DRBD stb.), mivel ezek igen könnyen szűk keresztmetszetté válhatnak az egyébként gyors adatelérést igénylő munkamenetkezelésben. Helyette inkább tegyük a sessionöket adatbázisba vagy akár memcache-be."
tudnal tanacsot adni arra, hogy a memcache server esetben hogyan lehet minimum redundanciat elerni? elsosorban arra gondolok, hogy mig az nfs esetben megoldhato, hogy hiba eseten ne bukjuk el a sessiont, addig a memcache esetben nincs lehetoseg reprodukalni a sessiont.
Valamit rosszul gondolok?
az is erdekelne meg, hogy az nfs esetben mik a tapasztalataid, mibol lesz bottleneck?
koszi elore is
Memcache
A sessionöknél pont az a lényeg hogy ne legyen túl nagy az overhead mivel minden AJAX kérés, oldallekérdezés stb. kiolvassa a sessiont majd vissza is írja ami NFSen keresztül tud lassú lenni. További probléma vele hogy a DLM (distributed lock manager) hajlamos elveszteni a szinkront, főleg ha sok gép használja. (Legalábbis én ezt tapasztaltam.)
Az NFS-sel kapcsolatban úgy nagy általánosságban az a tapasztalat hogy működik és akár jó is lehet viszont vagy soft módban használod és akkor el tud crashelni a fölötte futó alkalmazás vagy hard módban futtatod és akkor viszont összedől az egész cluster ha megborul egy backend. Ha szinkron módban használod akkor meg nem lesz gyors. Ettől függetlenül sajnos bizonyos dolgokra nincs alternatíva.
ahogy irta mar mas is, van
- memcache klienssel tobb szerverbe irsz, 1bol olvasol.
- http://repcached.lab.klab.org/
- attersz redis-re, ott lehet dumpolni a db-t, lehet perzisztense tenni (szabalyozhato, hogy milyen gyakran irja lemezre a modositasokat, akar minden irast is lehet sync-elni, szoval akar lemez/fajlrendszer szinten is lehet failovert uzemeltetni) illetve tud replikaciot, szoval master-slave felallassal is lehet HA megoldast biztositani.
amugy ha php-bol hasznalod a memcache-t, akkor hasznos olvasmany:
http://brian.moonspot.net/php-memcached-issues
Tyrael
Nagy terhelésű rendszerek fejlesztése 3.
Bár nem tudom a szerző mennyire jártas más nyelvekben, pl JAVA-ban a J2EE szabványt implementáló rendszerek (Glassfish, Websphere, JBoss..) elég jól skálázhatóak mind vizszintes mind függőleges irányba. Érdemes ennek is utána nézni, hogy működik, ha mást nem ötleteket nyerni onnan.
dns
Mi a probléma a dns lb módszerrel?
DNS LB
A másik probléma az, hogy ha az egyik szervereddel gond van, pont a fentiek miatt nem tudod gyorsan kivenni a konfigurációból, de ez igazából már kicsit a HA témaköre.