Önbellek
Önbellek [keş]
, bir kez zorlukla elde edilen verileri bir sonraki kullanım için saklayarak uygulamanızı
hızlandırır. Göstereceğiz:
- önbellek nasıl kullanılır
- depolama nasıl değiştirilir
- önbellek nasıl doğru bir şekilde geçersiz kılınır
Nette'de önbellek kullanımı çok kolaydır, ancak çok gelişmiş ihtiyaçları bile karşılar. Performans ve %100 dayanıklılık için tasarlanmıştır. Temelde en yaygın arka uç depolama alanları için adaptörler bulacaksınız. Etiket tabanlı geçersizleştirmeyi, zaman aşımını destekler, önbellek izdihamına karşı koruması vardır vb.
Kurulum
Kütüphaneyi Composer aracını kullanarak indirip kurabilirsiniz:
composer require nette/caching
Temel Kullanım
Önbellekle çalışmanın merkezi noktası Nette\Caching\Cache nesnesidir. Bir örneğini
oluştururuz ve kurucuya parametre olarak depolama adı verilen bir nesne geçiririz. Bu, verilerin fiziksel olarak depolanacağı
yeri (veritabanı, Memcached, diskteki dosyalar, …) temsil eden bir nesnedir. Depolamaya, Nette\Caching\Storage
türüyle dependency injection kullanarak
geçirmemizi isteyerek erişiriz. Tüm önemli bilgileri Depolama bölümünde bulacaksınız.
Sürüm 3.0'da, arayüzün hala I
öneki vardı, bu nedenle adı
Nette\Caching\IStorage
idi. Ayrıca, Cache
sınıfının sabitleri büyük harflerle yazılmıştı,
örneğin Cache::Expire
yerine Cache::EXPIRE
.
Aşağıdaki örnekler için, Cache
takma adını oluşturduğumuzu ve $storage
değişkeninde bir
depolama alanına sahip olduğumuzu varsayalım.
use Nette\Caching\Cache;
$storage = /* ... */; // Nette\Caching\Storage örneği
Önbellek aslında bir anahtar-değer deposudur, yani verileri ilişkisel dizilerde olduğu gibi anahtarlar altında okur ve yazarız. Uygulamalar bir dizi bağımsız bölümden oluşur ve hepsi tek bir depolama alanı kullanırsa (diskte tek bir dizin düşünün), er ya da geç anahtar çakışmaları meydana gelir. Nette Framework, tüm alanı ad alanlarına (alt dizinlere) bölerek sorunu çözer. Programın her bölümü daha sonra benzersiz bir ada sahip kendi alanını kullanır ve artık çakışma olmaz.
Alan adını Cache sınıfının kurucusunun ikinci parametresi olarak belirtiriz:
$cache = new Cache($storage, 'Full Html Pages');
Şimdi $cache
nesnesini kullanarak önbellekten okuyabilir ve ona yazabiliriz. Her ikisi için de
load()
yöntemi kullanılır. İlk argüman anahtardır ve ikincisi, anahtar önbellekte bulunamadığında
çağrılan bir PHP geri çağrısıdır. Geri çağrı değeri oluşturur, döndürür ve önbelleğe kaydedilir:
$value = $cache->load($key, function () use ($key) {
$computedValue = /* ... */; // pahalı hesaplama
return $computedValue;
});
İkinci parametreyi belirtmezsek $value = $cache->load($key)
, öğe önbellekte yoksa null
döndürülür.
Harika olan şey, önbelleğe herhangi bir serileştirilebilir yapının kaydedilebilmesidir, yalnızca dizeler olması gerekmez. Ve aynı şey anahtarlar için bile geçerlidir.
Öğeyi önbellekten remove()
yöntemiyle sileriz:
$cache->remove($key);
Bir öğeyi önbelleğe kaydetmek için $cache->save($key, $value, array $dependencies = [])
yöntemi de
kullanılabilir. Ancak, yukarıda belirtilen load()
yöntemini kullanmak tercih edilir.
Memoizasyon
Memoizasyon, bir fonksiyon veya metodun çağrısının sonucunu önbelleğe almak anlamına gelir, böylece aynı şeyi tekrar tekrar hesaplamadan bir dahaki sefere kullanabilirsiniz.
Metotlar ve fonksiyonlar call(callable $callback, ...$args)
kullanılarak memoize edilebilir:
$result = $cache->call('gethostbyaddr', $ip);
gethostbyaddr()
fonksiyonu böylece her $ip
parametresi için yalnızca bir kez çağrılır ve bir
dahaki sefere değer önbellekten döndürülür.
Ayrıca, daha sonra çağrılabilecek bir metot veya fonksiyon üzerinde memoize edilmiş bir sarmalayıcı oluşturmak da mümkündür:
function factorial($num)
{
return /* ... */;
}
$memoizedFactorial = $cache->wrap('factorial');
$result = $memoizedFactorial(5); // ilk kez hesaplar
$result = $memoizedFactorial(5); // ikinci kez önbellekten
Sona Erme & Geçersizleştirme
Önbelleğe kaydetme ile birlikte, daha önce kaydedilen verilerin ne zaman geçersiz hale geleceği sorusunu çözmek gerekir. Nette Framework, verilerin geçerliliğini sınırlamak veya kontrollü bir şekilde silmek (framework terminolojisinde “geçersiz kılmak”) için bir mekanizma sunar.
Verilerin geçerliliği, kaydetme anında save()
yönteminin üçüncü parametresi kullanılarak ayarlanır,
örneğin:
$cache->save($key, $value, [
$cache::Expire => '20 minutes',
]);
Veya load()
yönteminin geri çağrısına referansla iletilen $dependencies
parametresi
kullanılarak, örneğin:
$value = $cache->load($key, function (&$dependencies) {
$dependencies[Cache::Expire] = '20 minutes';
return /* ... */;
});
Veya load()
yöntemindeki 3. parametre kullanılarak, örneğin:
$value = $cache->load($key, function () {
return ...;
}, [Cache::Expire => '20 minutes']);
Sonraki örneklerde, ikinci varyantı ve dolayısıyla $dependencies
değişkeninin varlığını
varsayacağız.
Sona Erme
En basit sona erme, bir zaman sınırıdır. Bu şekilde verileri 20 dakika geçerlilik süresiyle önbelleğe kaydederiz:
// saniye sayısını veya UNIX zaman damgasını da kabul eder
$dependencies[Cache::Expire] = '20 minutes';
Her okumada geçerlilik süresini uzatmak istersek, bunu aşağıdaki gibi yapabiliriz, ancak dikkatli olun, önbellek ek yükü artacaktır:
$dependencies[Cache::Sliding] = true;
Bir dosya veya birden fazla dosyadan herhangi biri değiştiğinde verilerin süresinin dolmasına izin verme seçeneği kullanışlıdır. Bu, örneğin bu dosyaların işlenmesinden kaynaklanan verileri önbelleğe kaydederken kullanılabilir. Mutlak yolları kullanın.
$dependencies[Cache::Files] = '/path/to/data.yaml';
// veya
$dependencies[Cache::Files] = ['/path/to/data1.yaml', '/path/to/data2.yaml'];
Bir öğenin süresinin başka bir öğenin (veya birden fazla öğeden herhangi birinin) süresi dolduğunda dolmasına izin
verebiliriz. Bu, örneğin tüm bir HTML sayfasını önbelleğe kaydettiğimizde ve parçalarını başka anahtarlar altında
sakladığımızda kullanılabilir. Parça değiştiğinde, tüm sayfa geçersiz kılınır. Parçaları örneğin
frag1
ve frag2
anahtarları altında sakladıysak, şunu kullanırız:
$dependencies[Cache::Items] = ['frag1', 'frag2'];
Sona erme, her okumada öğenin hala geçerli olup olmadığına karar veren özel fonksiyonlar veya statik metotlar
kullanılarak da kontrol edilebilir. Bu şekilde, örneğin PHP sürümü değiştiğinde öğenin süresinin dolmasına izin
verebiliriz. Mevcut sürümü parametreyle karşılaştıran bir fonksiyon oluştururuz ve kaydederken bağımlılıklar arasına
[fonksiyon adı, ...argümanlar]
şeklinde bir dizi ekleriz:
function checkPhpVersion($ver): bool
{
return $ver === PHP_VERSION_ID;
}
$dependencies[Cache::Callbacks] = [
['checkPhpVersion', PHP_VERSION_ID] // checkPhpVersion(...) === false olduğunda süresi dolar
];
Tüm kriterler elbette birleştirilebilir. Önbellek daha sonra en az bir kriter karşılanmadığında sona erer.
$dependencies[Cache::Expire] = '20 minutes';
$dependencies[Cache::Files] = '/path/to/data.yaml';
Etiketlerle Geçersizleştirme
Çok kullanışlı bir geçersizleştirme aracı etiketlerdir. Önbellekteki her öğeye, herhangi bir dize olabilen bir etiket listesi atayabiliriz. Örneğin, önbelleğe alacağımız bir makale ve yorumları içeren bir HTML sayfamız olsun. Kaydederken etiketleri belirtiriz:
$dependencies[Cache::Tags] = ["article/$articleId", "comments/$articleId"];
Yönetim paneline geçelim. Burada makaleyi düzenlemek için bir form bulacağız. Makaleyi veritabanına kaydetmekle
birlikte, etikete göre önbellekten öğeleri silen clean()
komutunu çağıracağız:
$cache->clean([
$cache::Tags => ["article/$articleId"],
]);
Benzer şekilde, yeni bir yorum ekleme (veya bir yorumu düzenleme) yerinde, ilgili etiketi geçersiz kılmayı unutmayacağız:
$cache->clean([
$cache::Tags => ["comments/$articleId"],
]);
Bununla ne başardık? Makale veya yorumlar değiştiğinde HTML önbelleğimizin geçersiz kılınmasını (silinmesini)
sağladık. ID = 10 olan bir makale düzenlendiğinde, article/10
etiketinin zorunlu geçersizleştirilmesi
gerçekleşir ve belirtilen etiketi taşıyan HTML sayfası önbellekten silinir. Aynı şey, ilgili makalenin altına yeni bir
yorum eklendiğinde de olur.
Etiketler Journal gerektirir.
Öncelikle Geçersizleştirme
Önbellekteki bireysel öğelere bir öncelik ayarlayabiliriz, bu sayede örneğin önbellek belirli bir boyutu aştığında bunları silebiliriz:
$dependencies[Cache::Priority] = 50;
100'e eşit veya daha düşük önceliğe sahip tüm öğeleri sileceğiz:
$cache->clean([
$cache::Priority => 100,
]);
Öncelikler Journal gerektirir.
Önbelleği Silme
Cache::All
parametresi her şeyi siler:
$cache->clean([
$cache::All => true,
]);
Toplu Okuma
Önbelleğe toplu okuma ve yazma işlemleri için bulkLoad()
yöntemi kullanılır, buna anahtar dizisini
geçiririz ve değer dizisini alırız:
$values = $cache->bulkLoad($keys);
bulkLoad()
yöntemi, oluşturulan öğenin anahtarını alan ikinci bir geri çağırma parametresiyle
load()
yöntemine benzer şekilde çalışır:
$values = $cache->bulkLoad($keys, function ($key, &$dependencies) {
$computedValue = /* ... */; // pahalı hesaplama
return $computedValue;
});
PSR-16 ile Kullanım
Nette Cache'i PSR-16 arayüzüyle kullanmak için PsrCacheAdapter
adaptörünü kullanabilirsiniz. Nette Cache
ile PSR-16 uyumlu bir önbellek bekleyen herhangi bir kod veya kütüphane arasında sorunsuz entegrasyon sağlar.
$psrCache = new Nette\Bridges\Psr\PsrCacheAdapter($storage);
Şimdi $psrCache
'i PSR-16 önbelleği olarak kullanabilirsiniz:
$psrCache->set('key', 'value', 3600); // değeri 1 saatliğine kaydeder
$value = $psrCache->get('key', 'default');
Adaptör, getMultiple()
, setMultiple()
ve deleteMultiple()
dahil olmak üzere PSR-16'da
tanımlanan tüm yöntemleri destekler.
Çıktıyı Önbelleğe Alma
Çıktıyı yakalamak ve önbelleğe almak çok zarif bir şekilde yapılabilir:
if ($capture = $cache->capture($key)) {
echo ... // verileri yazdırıyoruz
$capture->end(); // çıktıyı önbelleğe kaydediyoruz
}
Çıktı zaten önbellekteyse, capture()
yöntemi onu yazdırır ve null
döndürür, bu nedenle
koşul yürütülmez. Aksi takdirde, çıktıyı yakalamaya başlar ve sonunda yazdırılan verileri önbelleğe kaydettiğimiz
$capture
nesnesini döndürür.
Sürüm 3.0'da yöntemin adı $cache->start()
idi.
Latte'de Önbelleğe Alma
Latte şablonlarında önbelleğe alma çok kolaydır, şablonun bir bölümünü
{cache}...{/cache}
etiketleriyle sarmak yeterlidir. Kaynak şablon değiştiğinde (önbellek bloğu içindeki dahil
edilen şablonlar dahil) önbellek otomatik olarak geçersiz kılınır. {cache}
etiketleri iç içe
yerleştirilebilir ve iç içe geçmiş bir blok geçersiz kılındığında (örneğin bir etiketle), üst blok da geçersiz
kılınır.
Etikette, önbelleğin bağlanacağı anahtarları (burada $id
değişkeni) belirtebilir ve sona erme süresini ve
geçersizleştirme etiketlerini ayarlayabilirsiniz.
{cache $id, expire: '20 minutes', tags: [tag1, tag2]}
...
{/cache}
Tüm öğeler isteğe bağlıdır, bu nedenle ne sona erme süresini ne de etiketleri, hatta anahtarları bile belirtmemiz gerekmez.
Önbellek kullanımı ayrıca if
kullanılarak koşullandırılabilir – içerik yalnızca koşul
karşılanırsa önbelleğe alınır:
{cache $id, if: !$form->isSubmitted()}
{$form}
{/cache}
Depolama
Depolama, verilerin fiziksel olarak depolandığı yeri temsil eden bir nesnedir. Bir veritabanı, Memcached sunucusu veya en erişilebilir depolama alanı olan diskteki dosyaları kullanabiliriz.
Depolama | Açıklama |
---|---|
FileStorage | diske dosyalara kaydeden varsayılan depolama |
MemcachedStorage | Memcached sunucusunu kullanır |
MemoryStorage | veriler geçici olarak bellekte tutulur |
SQLiteStorage | veriler SQLite veritabanına kaydedilir |
DevNullStorage | veriler kaydedilmez, test için uygundur |
Depolama nesnesine, Nette\Caching\Storage
türüyle dependency injection kullanarak geçirmemizi
isteyerek erişirsiniz. Nette, varsayılan depolama olarak verileri geçici dosyalar dizinindeki cache
alt
dizinine kaydeden bir FileStorage nesnesi sağlar.
Depolamayı yapılandırmada değiştirebilirsiniz:
services:
cache.storage: Nette\Caching\Storages\DevNullStorage
FileStorage
Önbelleği diskteki dosyalara yazar. Nette\Caching\Storages\FileStorage
depolama alanı, performans için çok
iyi optimize edilmiştir ve özellikle işlemlerin tam atomikliğini sağlar. Bu ne anlama geliyor? Önbelleği kullanırken,
başka bir iş parçacığı tarafından henüz tamamen yazılmamış bir dosyayı okumanız veya birinin onu “ellerinizin
altından” silmesi mümkün değildir. Bu nedenle önbellek kullanımı tamamen güvenlidir.
Bu depolama alanı ayrıca, önbellek silindiğinde veya henüz ısınmadığında (yani oluşturulmadığında) CPU kullanımında aşırı artışı önleyen önemli bir yerleşik işleve sahiptir. Bu, önbellek izdihamı önlemesidir. Bazen, aynı anda daha fazla sayıda eşzamanlı istek, önbellekten aynı şeyi (örneğin pahalı bir SQL sorgusunun sonucu) ister ve önbellekte olmadığı için tüm işlemler aynı SQL sorgusunu yürütmeye başlar. Yük böylece katlanır ve hatta hiçbir iş parçacığının zaman sınırında yanıt verememesi, önbelleğin oluşturulmaması ve uygulamanın çökmesi bile olabilir. Neyse ki, Nette'deki önbellek, bir öğe için birden fazla eşzamanlı istek olduğunda, onu yalnızca ilk iş parçacığının oluşturduğu, diğerlerinin beklediği ve ardından oluşturulan sonucu kullandığı şekilde çalışır.
FileStorage oluşturma örneği:
// depolama alanı diskteki '/path/to/temp' dizini olacak
$storage = new Nette\Caching\Storages\FileStorage('/path/to/temp');
MemcachedStorage
Memcached sunucusu, adaptörü Nette\Caching\Storages\MemcachedStorage
olan
yüksek performanslı bir dağıtılmış bellek depolama sistemidir. Yapılandırmada, standart 11211'den farklıysa IP adresini
ve bağlantı noktasını belirtiriz.
PHP memcached
uzantısı gerektirir.
services:
cache.storage: Nette\Caching\Storages\MemcachedStorage('10.0.0.5')
MemoryStorage
Nette\Caching\Storages\MemoryStorage
, verileri bir PHP dizisinde saklayan ve bu nedenle istek sona erdiğinde
kaybolan bir depolama alanıdır.
SQLiteStorage
SQLite veritabanı ve Nette\Caching\Storages\SQLiteStorage
adaptörü, önbelleği diskteki tek bir dosyaya
kaydetmenin bir yolunu sunar. Yapılandırmada bu dosyanın yolunu belirtiriz.
PHP pdo
ve pdo_sqlite
uzantılarını gerektirir.
services:
cache.storage: Nette\Caching\Storages\SQLiteStorage('%tempDir%/cache.db')
DevNullStorage
Depolamanın özel bir uygulaması, aslında verileri hiç saklamayan Nette\Caching\Storages\DevNullStorage
'dır.
Bu nedenle, önbelleğin etkisini ortadan kaldırmak istediğimizde test için uygundur.
Kodda Önbellek Kullanımı
Kodda önbellek kullanırken, bunu yapmanın iki yolu vardır. Birincisi, dependency injection kullanarak depolamayı
geçirmemizi istemek ve bir Cache
nesnesi oluşturmaktır:
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');
}
}
İkinci seçenek, doğrudan bir Cache
nesnesi geçirmemizi istemektir:
class ClassTwo
{
public function __construct(
private Nette\Caching\Cache $cache,
) {
}
}
Cache
nesnesi daha sonra doğrudan yapılandırmada şu şekilde oluşturulur:
services:
- ClassTwo( Nette\Caching\Cache(namespace: 'my-namespace') )
Journal
Nette, etiketleri ve öncelikleri journal adı verilen bir yerde saklar. Standart olarak bunun için SQLite ve
journal.s3db
dosyası kullanılır ve PHP pdo
ve pdo_sqlite
uzantıları
gereklidir.
Journal'ı yapılandırmada değiştirebilirsiniz:
services:
cache.journal: MyJournal
DI Servisleri
Bu servisler DI konteynerine eklenir:
Ad | Tür | Açıklama |
---|---|---|
cache.journal |
Nette\Caching\Storages\Journal | journal |
cache.storage |
Nette\Caching\Storage | depolama |
Önbelleği Devre Dışı Bırakma
Uygulamada önbelleği devre dışı bırakmanın bir yolu, depolama olarak DevNullStorage ayarlamaktır:
services:
cache.storage: Nette\Caching\Storages\DevNullStorage
Bu ayarın Latte'deki şablonların veya DI konteynerinin önbelleğe alınması üzerinde bir etkisi yoktur, çünkü bu kütüphaneler nette/caching servislerini kullanmaz ve kendi önbelleklerini yönetirler. Ayrıca, geliştirme modunda önbelleklerini devre dışı bırakmaya gerek yoktur.