Predpomnilnik
Predpomnilnik pospešuje aplikacijo s shranjevanjem podatkov, ki so enkrat težko pridobljeni, za prihodnjo uporabo. Predstavili vam bomo:
- Kako uporabljati predpomnilnik
- kako spremeniti shranjevanje predpomnilnika
- kako pravilno razveljaviti predpomnilnik
Uporaba predpomnilnika je v programu Nette zelo preprosta, pokriva pa tudi zelo napredne potrebe po predpomnilniku. Zasnovan je za zmogljivost in 100-odstotno trajnost. V osnovi boste našli adapterje za najpogostejše zaledne shrambe. Omogoča razveljavljanje na podlagi oznak, zaščito predpomnilnika pred stampedo, časovno iztekanje itd.
Namestitev
Prenesite in namestite paket s programom Composer:
composer require nette/caching
Osnovna uporaba
Središče dela s predpomnilnikom je objekt Nette\Caching\Cache. Ustvarimo njegov primerek in
konstruktorju kot parameter posredujemo tako imenovano shrambo. Ki je objekt, ki predstavlja mesto, kjer bodo podatki fizično
shranjeni (zbirka podatkov, Memcached, datoteke na disku, …). Objekt shrambe dobimo tako, da ga posredujemo z uporabo vbrizgavanja odvisnosti s tipom
Nette\Caching\Storage
. Vse bistvene informacije boste našli v razdelku
Shranjevanje.
V različici 3.0 je imel vmesnik še vedno tip I
prefix, so the name was
Nette\Caching\IStorage
. Tudi konstante razreda Cache
so se pisale z velikimi črkami, torej na primer
Cache::EXPIRE
namesto Cache::Expire
.
V naslednjih primerih predpostavimo, da imamo vzdevek Cache
in shrambo v spremenljivki
$storage
.
use Nette\Caching\Cache;
$storage = /* ... */; // primerek predpomnilnika Nette\Caching\Storage
Shramba je pravzaprav skladišče ključev, zato podatke pod ključi beremo in pišemo tako kot asociativna polja. Aplikacije so sestavljene iz več neodvisnih delov, in če bi vsi uporabljali eno shrambo (za predstavo: en imenik na disku), bi prej ali slej prišlo do trka ključev. Okvir Nette to težavo reši tako, da celoten prostor razdeli na imenske prostore (podimenike). Vsak del programa tako uporablja svoj prostor z edinstvenim imenom in do trkov ne more priti.
Ime prostora je določeno kot drugi parameter konstruktorja razreda Cache:
$cache = new Cache($storage, 'Full Html Pages');
Zdaj lahko za branje in pisanje iz predpomnilnika uporabimo objekt $cache
. Za oboje se uporablja metoda
load()
. Prvi argument je ključ, drugi pa povratni klic PHP, ki se pokliče, ko ključa ne najdemo v predpomnilniku.
Povratni klic ustvari vrednost, jo vrne in jo shrani v predpomnilnik:
$value = $cache->load($key, function () use ($key) {
$computedValue = /* ... */; // težka računanja.
return $computedValue;
});
Če drugi parameter ni naveden $value = $cache->load($key)
, se vrne null
, če elementa ni
v predpomnilniku.
Odlično je, da je mogoče v predpomnilnik shraniti vse serializabilne strukture, ne le nizov. Enako velja tudi za ključe.
Element se iz predpomnilnika izbriše z metodo remove()
:
$cache->remove($key);
Element lahko iz predpomnilnika izbrišete tudi z metodo $cache->save($key, $value, array $dependencies = [])
.
Vendar je zgornja metoda z uporabo load()
boljša.
Memoiziranje
Memoizacija pomeni predpomnjenje rezultata funkcije ali metode, tako da ga lahko uporabite naslednjič, namesto da vedno znova izračunavate isto stvar.
Metode in funkcije lahko memoizirate z uporabo call(callable $callback, ...$args)
:
$result = $cache->call('gethostbyaddr', $ip);
Funkcija gethostbyaddr()
se za vsak parameter $ip
pokliče samo enkrat, naslednjič pa se vrne
vrednost iz predpomnilnika.
Prav tako je mogoče ustvariti memoiziran ovoj za metodo ali funkcijo, ki ga lahko pokličemo pozneje:
function factorial($num)
{
return /* ... */;
}
$memoizedFactorial = $cache->wrap('factorial');
$result = $memoizedFactorial(5); // ga šteje.
$result = $memoizedFactorial(5); // ga vrne iz predpomnilnika.
Iztek veljavnosti in razveljavitev
Pri uporabi predpomnilnika je treba obravnavati vprašanje, da bodo nekateri predhodno shranjeni podatki sčasoma postali neveljavni. Okvir Nette zagotavlja mehanizem, kako omejiti veljavnost podatkov in jih nadzorovano izbrisati (“razveljaviti”, če uporabimo terminologijo okvira).
Veljavnost podatkov se določi ob shranjevanju s tretjim parametrom metode save()
, npr:
$cache->save($key, $value, [
$cache::Expire => '20 minutes',
]);
ali z uporabo parametra $dependencies
, ki se s sklicevanjem posreduje povratnemu klicu v metodi
load()
, npr:
$value = $cache->load($key, function (&$dependencies) {
$dependencies[Cache::Expire] = '20 minutes';
return /* ... */;
});
Ali z uporabo tretjega parametra v metodi load()
, npr:
$value = $cache->load($key, function () {
return ...;
}, [Cache::Expire => '20 minutes']);
V naslednjih primerih bomo predpostavili drugo varianto in s tem obstoj spremenljivke $dependencies
.
Iztek veljavnosti
Najpreprostejše prenehanje veljavnosti je časovna omejitev. Tukaj je prikazan način za shranjevanje podatkov v predpomnilnik, ki velja 20 minut:
// sprejme tudi število sekund ali časovni žig UNIX
$dependencies[Cache::Expire] = '20 minutes';
Če želimo z vsakim branjem podaljšati obdobje veljavnosti, lahko to dosežemo na ta način, vendar bodite pozorni, to bo povečalo režijske stroške predpomnilnika:
$dependencies[Cache::Sliding] = true;
Priročna možnost je možnost, da se podatki iztečejo, ko se spremeni določena datoteka ali ena od več datotek. To lahko na primer uporabite za predpomnjenje podatkov, ki so rezultat procesiranja teh datotek. Uporaba absolutnih poti.
$dependencies[Cache::Files] = '/path/to/data.yaml';
// ali
$dependencies[Cache::Files] = ['/path/to/data1.yaml', '/path/to/data2.yaml'];
Elementu v predpomnilniku lahko dovolimo, da poteče, ko poteče rok veljavnosti drugega elementa (ali enega od več drugih).
To lahko uporabimo, kadar v predpomnilnik shranimo celotno stran HTML in njene dele pod drugimi ključi. Ko se odlomek spremeni,
celotna stran postane neveljavna. Če imamo fragmente shranjene pod ključi, kot sta frag1
in frag2
,
bomo uporabili:
$dependencies[Cache::Items] = ['frag1', 'frag2'];
Iztek veljavnosti lahko nadzorujemo tudi s funkcijami po meri ali statičnimi metodami, ki ob branju vedno odločijo, ali je
element še veljaven. Elementu lahko na primer dovolimo, da poteče, kadar koli se spremeni različica PHP. Ustvarili bomo
funkcijo, ki bo primerjala trenutno različico s parametrom, pri shranjevanju pa bomo dodali polje v obliki
[function name, ...arguments]
k odvisnostim:
function checkPhpVersion($ver): bool
{
return $ver === PHP_VERSION_ID;
}
$dependencies[Cache::Callbacks] = [
['checkPhpVersion', PHP_VERSION_ID] // poteče, ko checkPhpVersion(...) === false
];
Seveda lahko vsa merila kombiniramo. Predpomnilnik se izteče, če vsaj eno merilo ni izpolnjeno.
$dependencies[Cache::Expire] = '20 minutes';
$dependencies[Cache::Files] = '/path/to/data.yaml';
Neveljavnost z uporabo oznak
Oznake so zelo uporabno orodje za razveljavitev. Vsakemu elementu, ki je shranjen v predpomnilniku, lahko dodelimo seznam oznak, ki so poljubni nizi. Predpostavimo na primer, da imamo stran HTML s člankom in komentarji, ki jo želimo shraniti v predpomnilnik. Pri shranjevanju v predpomnilnik določimo oznake:
$dependencies[Cache::Tags] = ["article/$articleId", "comments/$articleId"];
Sedaj pa preidimo k upravljanju. Tu imamo obrazec za urejanje člankov. Hkrati s shranjevanjem članka v zbirko podatkov
pokličemo ukaz clean()
, ki bo izbrisal predpomnilniške elemente po oznakah:
$cache->clean([
$cache::Tags => ["article/$articleId"],
]);
Prav tako na mestu dodajanja novega komentarja (ali urejanja komentarja) ne bomo pozabili razveljaviti ustrezne oznake:
$cache->clean([
$cache::Tags => ["comments/$articleId"],
]);
Kaj smo dosegli? Da bo naš predpomnilnik HTML razveljavljen (izbrisan) vsakič, ko se članek ali komentarji spremenijo. Pri
urejanju članka z ID = 10 se oznaka article/10
razveljavi in stran HTML z oznako se izbriše iz predpomnilnika.
Enako se zgodi, ko pod ustrezen članek vstavite nov komentar.
Oznake zahtevajo Journal.
Neveljavnost po prednostni nalogi
Posameznim elementom v predpomnilniku lahko določimo prioriteto in tako jih bo mogoče nadzorovano izbrisati, ko na primer predpomnilnik preseže določeno velikost:
$dependencies[Cache::Priority] = 50;
Izbriši vse elemente s prioriteto, ki je enaka ali manjša od 100:
$cache->clean([
$cache::Priority => 100,
]);
Prednostne naloge zahtevajo tako imenovani dnevnik.
Počisti predpomnilnik
Parameter Cache::All
izbriše vse:
$cache->clean([
$cache::All => true,
]);
Množično branje
Za množično branje in pisanje v predpomnilnik se uporablja metoda bulkLoad()
, pri kateri posredujemo polje
ključev in dobimo polje vrednosti:
$values = $cache->bulkLoad($keys);
Metoda bulkLoad()
deluje podobno kot load()
z drugim parametrom povratne zveze, ki mu posredujemo
ključ ustvarjenega elementa:
$values = $cache->bulkLoad($keys, function ($key, &$dependencies) {
$computedValue = /* ... */; // težka računanja.
return $computedValue;
});
Uporaba s PSR-16
Za uporabo predpomnilnika Nette Cache z vmesnikom PSR-16 lahko uporabite PsrCacheAdapter
. Ta omogoča nemoteno
integracijo med predpomnilnikom Nette Cache in katero koli kodo ali knjižnico, ki pričakuje predpomnilnik, združljiv
s PSR-16.
$psrCache = new Nette\Bridges\Psr\PsrCacheAdapter($storage);
Zdaj lahko $psrCache
uporabljate kot predpomnilnik PSR-16:
$psrCache->set('key', 'value', 3600); // shrani vrednost za 1 uro
$value = $psrCache->get('key', 'default');
Adapter podpira vse metode, opredeljene v PSR-16, vključno s getMultiple()
, setMultiple()
in
deleteMultiple()
.
Izhodno predpomnjenje
Izhod lahko zelo elegantno zajamete in shranite v predpomnilnik:
if ($capture = $cache->capture($key)) {
echo ... // tiskanje nekaterih podatkov
$capture->end(); // shranite izpis v predpomnilnik
}
V primeru, da je izhod že v predpomnilniku, ga metoda capture()
izpiše in vrne null
, tako da se
pogoj ne bo izvedel. V nasprotnem primeru prične z bufferiranjem izpisa in vrne objekt $capture
, s pomočjo
katerega na koncu shranimo podatke v predpomnilnik.
V različici 3.0 se je metoda imenovala $cache->start()
.
Predpomnjenje v Latte
Predpomnjenje v predlogah Latte je zelo enostavno, le del predloge ovijte
z oznakami {cache}...{/cache}
. Predpomnilnik se samodejno razveljavi, ko se spremeni izvorna predloga (vključno
z vsemi vključenimi predlogami znotraj oznak {cache}
). Značke {cache}
so lahko vgnezdene, in ko je
vgnezdeni blok razveljavljen (na primer z oznako), je razveljavljen tudi nadrejeni blok.
V oznaki je mogoče določiti ključe, na katere bo vezan predpomnilnik (tukaj spremenljivka $id
), ter nastaviti
potek veljavnosti in razveljavitev oznake
{cache $id, expire: '20 minutes', tags: [tag1, tag2]}
...
{/cache}
Vsi parametri so neobvezni, zato vam ni treba navesti poteka veljavnosti, oznak ali ključev.
Uporabo predpomnilnika lahko tudi pogojite s spletno stranjo if
– vsebina se nato predpomeni samo, če je
pogoj izpolnjen:
{cache $id, if: !$form->isSubmitted()}
{$form}
{/cache}
Shrambe
Skladišče je objekt, ki predstavlja mesto, kjer so podatki fizično shranjeni. Uporabimo lahko podatkovno zbirko, strežnik Memcached ali najbolj razpoložljivo shrambo, ki so datoteke na disku.
Shranjevanje | Opis |
---|---|
FileStorage | privzeto shranjevanje s shranjevanjem v datoteke na disku |
MemcachedStorage | uporablja strežnik Memcached |
MemoryStorage | podatki so začasno v pomnilniku |
SQLiteStorage | podatki so shranjeni v podatkovni zbirki SQLite |
DevNullStorage | podatki niso shranjeni – za namene testiranja |
Objekt shrambe dobite tako, da ga posredujete z uporabo vbrizgavanja odvisnosti s tipom
Nette\Caching\Storage
. Nette privzeto zagotavlja objekt FileStorage, ki shranjuje podatke v podmapo
cache
v imeniku za začasne
datoteke.
Shranjevanje lahko spremenite v konfiguraciji:
services:
cache.storage: Nette\Caching\Storages\DevNullStorage
FileStorage
Predpomnilnik zapiše v datoteke na disku. Shramba Nette\Caching\Storages\FileStorage
je zelo dobro optimizirana
za zmogljivost, predvsem pa zagotavlja popolno atomičnost operacij. Kaj to pomeni? Da se pri uporabi predpomnilnika ne more
zgoditi, da bi prebrali datoteko, ki je druga nit še ni v celoti zapisala, ali da bi jo kdo izbrisal “pod rokami”. Uporaba
predpomnilnika je torej popolnoma varna.
Ta shramba ima vgrajeno tudi pomembno funkcijo, ki preprečuje izjemno povečanje porabe procesorja, ko je predpomnilnik izbrisan ali hladen (tj. ni ustvarjen). To je preprečevanje stampeda predpomnilnika. Zgodi se, da v nekem trenutku obstaja več hkratnih zahtevkov, ki želijo isto stvar iz predpomnilnika (npr. rezultat drage poizvedbe SQL), in ker ta ni shranjena v predpomnilniku, vsi procesi začnejo izvajati isto poizvedbo SQL. Obremenitev procesorja se pomnoži in lahko se celo zgodi, da se nobena nit ne more odzvati v časovnem roku, predpomnilnik se ne ustvari in aplikacija se sesuje. Na srečo predpomnilnik v Nette deluje tako, da ob več hkratnih zahtevah za en element le-tega ustvari le prva nit, druge počakajo in nato uporabijo ustvarjen rezultat.
Primer ustvarjanja shrambe datotek:
// shranjevanje bo imenik '/path/to/temp' na disku
$storage = new Nette\Caching\Storages\FileStorage('/path/to/temp');
MemcachedStorage
Strežnik Memcached je visoko zmogljiv porazdeljeni sistem za shranjevanje, katerega
adapter je Nette\Caching\Storages\MemcachedStorage
. V konfiguraciji določite naslov IP in vrata, če se razlikujejo
od standardnih 11211.
Zahteva razširitev PHP memcached
.
services:
cache.storage: Nette\Caching\Storages\MemcachedStorage('10.0.0.5')
MemoryStorage
Nette\Caching\Storages\MemoryStorage
je pomnilnik, ki podatke hrani v polju PHP in se tako izgubi, ko se
zahteva konča.
Shramba SQLiteStorage
Podatkovna baza SQLite in adapter Nette\Caching\Storages\SQLiteStorage
ponujata način predpomnjenja v eni sami
datoteki na disku. V konfiguraciji je določena pot do te datoteke.
Zahteva razširitvi PHP pdo
in pdo_sqlite
.
services:
cache.storage: Nette\Caching\Storages\SQLiteStorage('%tempDir%/cache.db')
DevNullStorage
Posebna izvedba shrambe je Nette\Caching\Storages\DevNullStorage
, ki dejansko sploh ne shranjuje podatkov. Zato je
primerna za testiranje, če želimo odpraviti učinek predpomnilnika.
Uporaba predpomnilnika v kodi
Pri uporabi predpomnilnika v kodi lahko to storite na dva načina. Prvi je, da objekt za shranjevanje dobite tako, da ga
posredujete z uporabo injekcije odvisnosti, nato
pa ustvarite objekt Cache
:
use Nette;
class ClassOne
{
private Nette\Caching\Cache $cache;
public function __construct(Nette\Caching\Storage $storage)
{
$this->cache = new Nette\Caching\Cache($storage, 'my-namespace');
}
}
Drugi način je, da pridobite objekt za shranjevanje Cache
:
class ClassTwo
{
public function __construct(
private Nette\Caching\Cache $cache,
) {
}
}
Objekt Cache
se nato ustvari neposredno v konfiguraciji na naslednji način:
services:
- ClassTwo( Nette\Caching\Cache(namespace: 'my-namespace') )
Dnevnik
Nette shranjuje oznake in prednostne naloge v tako imenovani dnevnik. Privzeto se za to uporabljata SQLite in datoteka
journal.s3db
, zahtevani pa sta razširitvi PHP pdo
in pdo_sqlite
.
Dnevnik lahko spremenite v konfiguraciji:
services:
cache.journal: MyJournal
Storitve DI
Te storitve so dodane vsebniku DI:
Ime | Vrsta | Opis |
---|---|---|
cache.journal |
Nette\Caching\Storages\Journal | journal |
cache.storage |
Nette\Caching\Storage | repozitorij |
Izklop predpomnilnika
Eden od načinov za izklop predpomnilnika v aplikaciji je nastavitev shrambe na DevNullStorage:
services:
cache.storage: Nette\Caching\Storages\DevNullStorage
Ta nastavitev ne vpliva na predpomnjenje predlog v Latte ali vsebniku DI, saj ti knjižnici ne uporabljata storitev nette/caching in upravljata svoj predpomnilnik neodvisno. Poleg tega njihovega predpomnilnika v razvojnem načinu ni treba izklopiti.