Föreställ dig att vi har ett system där användare lägger till artiklar. För varje användare visar vi statistik om deras artiklar i deras personliga instrumentpanel: antal artiklar, genomsnittligt antal ord, publiceringsfrekvens, etc. För att påskynda systemet cachar vi denna data. En unik cachenyckel skapas för varje rapport.
Frågan uppstår: hur ogiltigförklarar man sådana cacher när data ändras? Ett tillvägagångssätt är att manuellt rensa cachen för varje händelse, till exempel när en ny artikel läggs till:
class InvalidateArticleReportCacheOnArticleCreated { public function handle(event: ArticleCreatedEvent): void { this->cacheService->deleteMultiple([ 'user_article_report_count_' . event->userId, 'user_article_report_word_avg_' . event->userId, 'user_article_report_freq_avg_' . event->userId, ]) } }
Denna metod fungerar men blir krånglig när man hanterar ett stort antal rapporter och nycklar. Det är här taggad cachelagring kommer till användning. Tagged caching gör att data kan associeras inte bara med en nyckel utan också med en rad taggar. Därefter kan alla poster associerade med en specifik tagg ogiltigförklaras, vilket avsevärt förenklar processen.
Skriva ett värde till cachen med taggar:
this->taggedCacheService->set( key: 'user_article_report_count_' . user->id, value: value, tagNames: [ 'user_article_cache_tag_' . user->id, 'user_article_report_cache_tag_' . user->id, 'user_article_report' ] )
Ogiltigförklaring av cachen med taggar:
class UpdateCacheTagsOnArticleCreated { public function handle(event: ArticleCreatedEvent): void { this->taggedCacheService->updateTagsVersions([ 'user_article_cache_tag_' . user->id, ]) } }
Här, taggen 'user_article_cache_tag_' . $user->id
representerar ändringar i användarens artiklar. Den kan användas för att ogiltigförklara alla cacher som är beroende av dessa data. En mer specifik tagg 'user_article_report_cache_tag_' . $user->id
tillåter bara användarens rapporter att rengöras, medan en allmän tagg 'user_article_report'
ogiltigförklarar rapportcacheminne för alla användare.
Om ditt cachningsbibliotek inte stöder taggning kan du implementera det själv. Huvudidén är att lagra de aktuella versionsvärdena för taggar, såväl som för varje taggat värde, för att lagra taggversionerna som var aktuella när värdet skrevs till cachen. Sedan, när ett värde hämtas från cachen, hämtas även de aktuella taggversionerna, och deras giltighet kontrolleras genom att jämföra dem.
Skapa en TaggedCache
-klass
class TaggedCache { private cacheService: cacheService }
Implementera set
för att skriva till cachen med taggar. I den här metoden måste vi skriva värdet till cachen, samt hämta de aktuella versionerna av taggarna som tillhandahålls och spara dem som är associerade med den specifika cachenyckeln. Detta uppnås genom att använda en extra nyckel med ett prefix lagt till den medföljande nyckeln.
class TaggedCache { private cacheService: cacheService public function set( key: string, value: mixed, tagNames: string[], ttl: int ): bool { if (empty(tagNames)) { return false } tagVersions = this->getTagsVersions(tagNames) tagsCacheKey = this->getTagsCacheKey(key) return this->cacheService->setMultiple( [ key => value, tagsCacheKey => tagVersions, ], ttl ) } private function getTagsVersions(tagNames: string[]): array<string, string> { tagVersions = [] tagVersionKeys = [] foreach (tagNames as tagName) { tagVersionKeys[tagName] = this->getTagVersionKey(tagName) } if (empty(tagVersionKeys)) { return tagVersions } tagVersionsCache = this->cacheService->getMultiple(tagVersionKeys) foreach (tagVersionKeys as tagName => tagVersionKey) { if (empty(tagVersionsCache[tagVersionKey])) { tagVersionsCache[tagVersionKey] = this->updateTagVersion(tagName) } tagVersions[$tagName] = tagVersionsCache[tagVersionKey] } return tagVersions } private function getTagVersionKey(tagName: string): string { return 'tag_version_' . tagName } private function getTagsCacheKey(key: string): string { return 'cache_tags_tagskeys_' . key }
Lägger till get
-metoden för att hämta taggade värden från cachen. Här hämtar vi värdet med hjälp av nyckeln, såväl som taggversionerna som är associerade med den nyckeln. Sedan kontrollerar vi taggarnas giltighet. Om någon tagg är ogiltig raderas värdet från cachen och null
returneras. Om alla taggar är giltiga returneras det cachade värdet.
class TaggedCache { private cacheService: cacheService public function get(key: string): mixed { tagsCacheKey = this->getTagsCacheKey(key) values = this->cacheService->getMultiple([key, tagsCacheKey]) if (empty(values[key]) || empty(values[tagsCacheKey])) { return null } value = values[key] tagVersions = values[tagsCacheKey] if (! this->isTagVersionsValid(tagVersions)) { this->cacheService->deleteMultiple([key, tagsCacheKey]) return null } return value } private function isTagVersionsValid(tagVersions: array<string, string>): bool { tagNames = array_keys(tagVersions) actualTagVersions = this->getTagsVersions(tagNames) foreach (tagVersions as tagName => tagVersion) { if (empty(actualTagVersions[tagName])) { return false } if (actualTagVersions[tagName] !== tagVersion) { return false } } return true } }
Implementering av metoden updateTagsVersions
för att uppdatera taggversioner. Här itererar vi över alla taggar som tillhandahålls och uppdaterar deras versioner med till exempel den aktuella tiden som version.
class TaggedCache { private cacheService: cacheService public function updateTagsVersions(tagNames: string[]): void { foreach (tagNames as tagName) { this->updateTagVersion(tagName) } } private function updateTagVersion(tagName: string): string { tagKey = this->getTagVersionKey(tagName) tagVersion = this->generateTagVersion() return this->cacheService->set(tagKey, tagVersion) ? tagVersion : '' } private function generateTagVersion(): string { return (string) hrtime(true) } }
Detta tillvägagångssätt är både bekvämt och universellt. Taggad cachning eliminerar behovet av att manuellt specificera alla nycklar för ogiltigförklaring, vilket automatiserar processen. Det kräver dock ytterligare resurser: lagring av taggversionsdata och kontroll av deras giltighet med varje begäran.
Om din cachningstjänst är snabb och inte kraftigt begränsad i storlek kommer detta tillvägagångssätt inte att påverka prestandan nämnvärt. För att minimera belastningen kan du kombinera taggad cachning med lokala cachningsmekanismer.
På så sätt förenklar taggad cachelagring inte bara ogiltigförklaringen utan gör också arbetet med data mer flexibelt och begripligt, särskilt i komplexa system med stora mängder sammankopplade data.