Cache
A Cache felgyorsítja az alkalmazást azáltal, hogy az egyszer nehezen megszerzett adatokat elmenti a későbbi felhasználásra. Megmutatjuk:
- hogyan használjuk a cache-t
- hogyan változtassuk meg a tárolót
- hogyan érvénytelenítsük helyesen a cache-t
A cache használata a Nette-ben nagyon egyszerű, miközben nagyon fejlett igényeket is lefed. Teljesítményre és 100%-os ellenállóságra tervezték. Alapból adaptereket talál a leggyakoribb háttértárolókhoz. Lehetővé teszi a tag-ek alapján történő érvénytelenítést, az időbeli lejárást, védelmet nyújt a cache stampede ellen stb.
Telepítés
A könyvtárat a Composer eszközzel töltheti le és telepítheti:
Alapvető használat
A cache-sel vagy gyorsítótárral való munka középpontjában az Nette\Caching\Cache objektum áll. Létrehozunk egy
példányt belőle, és paraméterként átadjuk a konstruktornak az úgynevezett tárolót. Ez egy olyan objektum, amely azt a
helyet képviseli, ahol az adatok fizikailag tárolódnak (adatbázis, Memcached, fájlok a lemezen, …). A tárolóhoz úgy
juthatunk hozzá, hogy dependency injection
segítségével kérjük át a Nette\Caching\Storage
típussal. Minden lényegeset megtudhat a Tárolók szakaszban.
A 3.0-s verzióban az interfésznek még volt I
előtagja, tehát a neve
Nette\Caching\IStorage
volt. Továbbá a Cache
osztály konstansai nagybetűkkel voltak írva, tehát
például Cache::EXPIRE
a Cache::Expire
helyett.
A következő példákhoz feltételezzük, hogy létrehoztunk egy Cache
aliast, és a $storage
változóban van a tároló.
A cache valójában egy key–value store, tehát az adatokat kulcsok alatt olvassuk és írjuk, ugyanúgy, mint az asszociatív tömböknél. Az alkalmazások számos független részből állnak, és ha mindegyik ugyanazt a tárolót használná (képzeljünk el egyetlen könyvtárat a lemezen), előbb-utóbb kulcsütközés következne be. A Nette Framework ezt a problémát úgy oldja meg, hogy az egész teret névtérekre (alkönyvtárakra) osztja. Minden programrész ezután a saját, egyedi nevű terét használja, és így már nem fordulhat elő ütközés.
A névtér nevét a Cache osztály konstruktorának második paramétereként adjuk meg:
Most már a $cache
objektum segítségével olvashatunk a gyorsítótárból és írhatunk bele. Mindkettőre a
load()
metódus szolgál. Az első argumentum a kulcs, a második pedig egy PHP callback, amely akkor hívódik meg,
ha a kulcs nem található a cache-ben. A callback generálja az értéket, visszaadja, és az elmentődik a cache-be:
Ha a második paramétert nem adjuk meg $value = $cache->load($key)
, akkor null
-t ad vissza, ha az
elem nincs a cache-ben.
Nagyszerű, hogy a cache-be bármilyen szerializálható struktúrát tárolhatunk, nem csak stringeket. És ugyanez igaz még a kulcsokra is.
Az elemet a gyorsítótárból a remove()
metódussal töröljük:
Elemet a gyorsítótárba a $cache->save($key, $value, array $dependencies = [])
metódussal is menthetünk.
Azonban a fentebb bemutatott load()
használata preferált.
Memoizáció
A memoizáció egy függvény vagy metódus hívásának eredményének gyorsítótárazását jelenti, hogy legközelebb újra felhasználhassuk anélkül, hogy újra kiszámítanánk ugyanazt.
Metódusokat és függvényeket memoizáltan hívhatunk a call(callable $callback, ...$args)
segítségével:
A gethostbyaddr()
függvény így minden $ip
paraméterre csak egyszer hívódik meg, és
legközelebb már a cache-ből adódik vissza az érték.
Lehetőség van arra is, hogy egy memoizált burkolót hozzunk létre egy metódus vagy függvény köré, amelyet később hívhatunk meg:
Lejárat & érvénytelenítés
A cache-be való mentéskor felmerül a kérdés, hogy a korábban elmentett adatok mikor válnak érvénytelenné. A Nette Framework egy mechanizmust kínál az adatok érvényességének korlátozására vagy azok irányított törlésére (a keretrendszer terminológiájában „érvénytelenítésére”).
Az adatok érvényességét a mentéskor állítjuk be a save()
metódus harmadik paraméterével, pl.:
Vagy a load()
metódus callbackjének referenciaként átadott $dependencies
paraméterével, pl.:
Vagy a load()
metódus 3. paraméterével, pl.:
A további példákban a második változatot feltételezzük, és így a $dependencies
változó
létezését.
Lejárat
A legegyszerűbb lejárat az időkorlát. Így 20 perces érvényességgel mentünk adatokat a cache-be:
Ha minden olvasással meg szeretnénk hosszabbítani az érvényességi időt, azt a következőképpen érhetjük el, de vigyázat, a cache rezsije ezzel megnő:
Ügyes lehetőség, hogy az adatokat akkor járassuk le, amikor egy fájl vagy több fájl közül valamelyik megváltozik. Ezt például akkor használhatjuk, ha ezeknek a fájloknak a feldolgozásából származó adatokat mentjük a cache-be. Használjon abszolút elérési utakat.
Lejárathatunk egy elemet a cache-ben akkor, amikor egy másik elem (vagy több másik közül valamelyik) lejár. Ezt akkor
használhatjuk, ha például egy egész HTML oldalt mentünk a cache-be, és más kulcsok alatt annak töredékeit. Amint egy
töredék megváltozik, az egész oldal érvénytelenné válik. Ha a töredékeket pl. frag1
és frag2
kulcsok alatt tároljuk, használjuk ezt:
A lejáratot saját függvényekkel vagy statikus metódusokkal is vezérelhetjük, amelyek minden olvasáskor eldöntik, hogy
az elem még érvényes-e. Így például lejárathatunk egy elemet mindig, amikor a PHP verziója megváltozik. Létrehozunk egy
függvényt, amely összehasonlítja az aktuális verziót a paraméterrel, és a mentéskor hozzáadjuk a függőségek közé a
[függvény neve, ...argumentumok]
formátumú tömböt:
Természetesen minden kritérium kombinálható. A cache akkor jár le, ha legalább egy kritérium nem teljesül.
Érvénytelenítés tag-ekkel
Nagyon hasznos érvénytelenítő eszközök az úgynevezett tag-ek. Minden cache-beli elemhez hozzárendelhetünk egy tag-listát, amelyek tetszőleges stringek. Legyen például egy HTML oldalunk egy cikkel és hozzászólásokkal, amelyet gyorsítótárazni fogunk. Mentéskor megadjuk a tag-eket:
Lépjünk át az adminisztrációba. Itt találunk egy űrlapot a cikk szerkesztéséhez. A cikk adatbázisba mentésével
együtt meghívjuk a clean()
parancsot, amely törli a cache-ből az elemeket a tag alapján:
Ugyanígy az új hozzászólás hozzáadásának (vagy egy hozzászólás szerkesztésének) helyén ne felejtsük el érvényteleníteni a megfelelő tag-et:
Mit értünk el ezzel? Azt, hogy a HTML cache érvénytelenné válik (törlődik), amikor a cikk vagy a hozzászólások
megváltoznak. Ha egy 10-es ID-jú cikket szerkesztünk, akkor kényszerített érvénytelenítés történik az
article/10
tag-re, és a HTML oldal, amely ezt a tag-et hordozza, törlődik a cache-ből. Ugyanez történik egy új
hozzászólás beszúrásakor a megfelelő cikk alá.
A tag-ekhez úgynevezett Journal szükséges.
Érvénytelenítés prioritással
Az egyes cache-elemekhez beállíthatunk prioritást, amellyel törölhetjük őket, ha például a cache meghalad egy bizonyos méretet:
Töröljük az összes elemet, amelyek prioritása 100 vagy annál kisebb:
A prioritásokhoz úgynevezett Journal szükséges.
Cache törlése
A Cache::All
paraméter mindent töröl:
Tömeges olvasás
A cache-ből való tömeges olvasásra és írásra a bulkLoad()
metódus szolgál, amelynek átadunk egy
kulcstömböt, és egy értéktömböt kapunk vissza:
A bulkLoad()
metódus hasonlóan működik, mint a load()
, a második paraméter callbackkel is,
amelynek átadódik a generált elem kulcsa:
Használat PSR-16-tal
A Nette Cache PSR-16 interfésszel való használatához használhatja a PsrCacheAdapter
adaptert. Lehetővé
teszi a zökkenőmentes integrációt a Nette Cache és bármely olyan kód vagy könyvtár között, amely PSR-16 kompatibilis
cache-t vár.
Most már használhatja a $psrCache
-t PSR-16 cache-ként:
Az adapter támogatja az összes PSR-16-ban definiált metódust, beleértve a getMultiple()
,
setMultiple()
és deleteMultiple()
metódusokat is.
Kimenet gyorsítótárazása
Nagyon elegánsan lehet a kimenetet elfogni és gyorsítótárazni:
Abban az esetben, ha a kimenet már a cache-ben van, a capture()
metódus kiírja azt és null
-t ad
vissza, tehát a feltétel nem teljesül. Ellenkező esetben elkezdi a kimenet elfogását és visszaadja a $capture
objektumot, amelynek segítségével végül elmentjük a kiírt adatokat a cache-be.
A 3.0-s verzióban a metódus neve $cache->start()
volt.
Gyorsítótárazás Latte-ban
A sablonokban való gyorsítótárazás a Latte-ban nagyon egyszerű, csak a sablon
egy részét kell {cache}...{/cache}
tagekkel körbevenni. A cache automatikusan érvénytelenné válik, amikor a
forrás sablon megváltozik (beleértve az esetlegesen beillesztett sablonokat a cache blokkon belül). A {cache}
tagek egymásba ágyazhatók, és ha egy beágyazott blokk érvénytelenné válik (például egy tag miatt), akkor a
fölérendelt blokk is érvénytelenné válik.
A tagben megadhatók kulcsok, amelyekhez a cache kötődni fog (itt a $id
változó), és beállítható a
lejárat és a címkék az érvénytelenítéshez.
Minden elem opcionális, így nem kell megadnunk sem a lejáratot, sem a címkéket, végül még a kulcsokat sem.
A cache használata feltételhez is köthető az if
segítségével – a tartalom csak akkor lesz
gyorsítótárazva, ha a feltétel teljesül:
Tárolók
A tároló egy objektum, amely azt a helyet képviseli, ahol az adatok fizikailag tárolódnak. Használhatunk adatbázist, Memcached szervert, vagy a leginkább elérhető tárolót, ami a lemezen lévő fájlok.
Tároló | Leírás |
---|---|
FileStorage | alapértelmezett tároló, amely a lemezen lévő fájlokba ment |
MemcachedStorage | Memcached szervert használ |
MemoryStorage | az adatok ideiglenesen a memóriában vannak |
SQLiteStorage | az adatok SQLite adatbázisba mentődnek |
DevNullStorage | az adatok nem mentődnek, tesztelésre alkalmas |
A tároló objektumhoz úgy juthat hozzá, hogy dependency injection segítségével kéri át a
Nette\Caching\Storage
típussal. Alapértelmezett tárolóként a Nette a FileStorage objektumot biztosítja, amely
az adatokat az ideiglenes fájlok
könyvtárában lévő cache
alkönyvtárba menti.
A tárolót a konfigurációban módosíthatja:
FileStorage
A cache-t fájlokba írja a lemezen. A Nette\Caching\Storages\FileStorage
tároló nagyon jól optimalizált a
teljesítményre, és mindenekelőtt biztosítja a műveletek teljes atomicitását. Mit jelent ez? Azt, hogy a cache
használatakor nem fordulhat elő, hogy olyan fájlt olvassunk be, amelyet egy másik szál még nem írt ki teljesen, vagy hogy
valaki “a kezünk alól” törölje azt. A cache használata tehát teljesen biztonságos.
Ez a tároló egy fontos beépített funkcióval is rendelkezik, amely megakadályozza a CPU extrém kihasználtságának növekedését abban a pillanatban, amikor a cache törlődik vagy még nincs felmelegítve (azaz létrehozva). Ez a cache stampede elleni védelem. Előfordul, hogy egy időben több párhuzamos kérés érkezik, amelyek ugyanazt a dolgot akarják a cache-ből (pl. egy drága SQL lekérdezés eredményét), és mivel az nincs a gyorsítótárban, minden folyamat ugyanazt az SQL lekérdezést kezdi el végrehajtani. A terhelés így megsokszorozódik, és akár az is előfordulhat, hogy egyetlen szál sem tud válaszolni az időkorláton belül, a cache nem jön létre, és az alkalmazás összeomlik. Szerencsére a Nette cache úgy működik, hogy több párhuzamos kérés esetén egy elemre csak az első szál generálja azt, a többiek várnak, majd felhasználják a generált eredményt.
Példa a FileStorage létrehozására:
MemcachedStorage
A Memcached szerver egy nagy teljesítményű, elosztott memóriában történő
tárolási rendszer, amelynek adaptere a Nette\Caching\Storages\MemcachedStorage
. A konfigurációban megadjuk az
IP-címet és a portot, ha az eltér a standard 11211-től.
Szükséges a memcached
PHP kiterjesztés.
MemoryStorage
A Nette\Caching\Storages\MemoryStorage
egy olyan tároló, amely az adatokat egy PHP tömbben tárolja, és így a
kérés befejeztével elvesznek.
SQLiteStorage
Az SQLite adatbázis és az Nette\Caching\Storages\SQLiteStorage
adapter lehetőséget kínál a cache egyetlen
fájlba történő mentésére a lemezen. A konfigurációban megadjuk ennek a fájlnak az elérési útját.
Szükséges a pdo
és pdo_sqlite
PHP kiterjesztés.
DevNullStorage
A tároló speciális implementációja a Nette\Caching\Storages\DevNullStorage
, amely valójában egyáltalán
nem tárol adatokat. Így tesztelésre alkalmas, amikor ki akarjuk küszöbölni a cache hatását.
Cache használata a kódban
A cache kódban való használatakor kétféleképpen járhatunk el. Az első az, hogy dependency injection segítségével átkérjük a
tárolót, és létrehozunk egy Cache
objektumot:
A második lehetőség az, hogy közvetlenül a Cache
objektumot kérjük át:
A Cache
objektumot ezután közvetlenül a konfigurációban hozzuk létre ezzel a módszerrel:
Journal
A Nette a címkéket és prioritásokat az úgynevezett journalba menti. Alapértelmezés szerint ehhez SQLite-ot és a
journal.s3db
fájlt használja, és szükséges a pdo
és pdo_sqlite
PHP
kiterjesztés.
A journalt a konfigurációban módosíthatja:
DI szolgáltatások
Ezek a szolgáltatások kerülnek hozzáadásra a DI konténerhez:
Név | Típus | Leírás |
---|---|---|
cache.journal |
Nette\Caching\Storages\Journal | journal |
cache.storage |
Nette\Caching\Storage | tároló |
Cache kikapcsolása
Az alkalmazásban a cache kikapcsolásának egyik módja, ha a DevNullStorage-t állítjuk be tárolóként:
Ez a beállítás nincs hatással a sablonok gyorsítótárazására a Latte-ban vagy a DI konténerben, mivel ezek a könyvtárak nem használják a nette/caching szolgáltatásait, és önállóan kezelik a cache-t. Egyébként a cache-üket nem szükséges kikapcsolni fejlesztői módban.