تصور کنید ما سیستمی داریم که در آن کاربران مقالاتی را اضافه می کنند. برای هر کاربر، آماری در مورد مقالههای او در داشبورد شخصیاش نمایش میدهیم: تعداد مقالات، میانگین تعداد کلمات، فراوانی انتشار و غیره. برای افزایش سرعت سیستم، این دادهها را در حافظه پنهان ذخیره میکنیم. برای هر گزارش یک کلید کش منحصر به فرد ایجاد می شود.
این سوال مطرح می شود: چگونه می توان چنین حافظه های پنهان را هنگام تغییر داده ها باطل کرد؟ یک روش پاک کردن دستی حافظه پنهان برای هر رویداد است، به عنوان مثال، هنگامی که یک مقاله جدید اضافه می شود:
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, ]) } }
این روش کار می کند، اما زمانی که با تعداد زیادی گزارش و کلید سروکار دارید، دست و پا گیر می شود. این جایی است که ذخیره برچسب شده به کمک شما می آید. کش برچسب گذاری شده اجازه می دهد تا داده ها نه تنها با یک کلید بلکه با آرایه ای از برچسب ها مرتبط شوند. متعاقباً، تمام رکوردهای مرتبط با یک برچسب خاص را می توان باطل کرد، که به طور قابل توجهی فرآیند را ساده می کند.
نوشتن یک مقدار در حافظه پنهان با برچسب ها:
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' ] )
باطل کردن حافظه پنهان توسط برچسب ها:
class UpdateCacheTagsOnArticleCreated { public function handle(event: ArticleCreatedEvent): void { this->taggedCacheService->updateTagsVersions([ 'user_article_cache_tag_' . user->id, ]) } }
در اینجا، تگ 'user_article_cache_tag_' . $user->id
نشان دهنده تغییرات در مقالات کاربر است. می توان از آن برای باطل کردن حافظه های پنهان وابسته به این داده استفاده کرد. یک برچسب خاص تر 'user_article_report_cache_tag_' . $user->id
اجازه می دهد تا فقط گزارش های کاربر پاک شود، در حالی که یک برچسب عمومی 'user_article_report'
حافظه پنهان گزارش را برای همه کاربران بی اعتبار می کند.
اگر کتابخانه ذخیره شما از برچسب گذاری پشتیبانی نمی کند، می توانید خودتان آن را پیاده سازی کنید. ایده اصلی این است که مقادیر نسخه فعلی تگ ها را ذخیره کنید، همچنین برای هر مقدار برچسب گذاری شده، نسخه های برچسب را که در زمان نوشته شدن مقدار در حافظه پنهان جاری بودند، ذخیره کنید. سپس هنگام بازیابی مقداری از حافظه نهان، نسخه های تگ فعلی نیز بازیابی می شوند و اعتبار آنها با مقایسه آنها بررسی می شود.
ایجاد کلاس TaggedCache
class TaggedCache { private cacheService: cacheService }
پیاده سازی متد set
برای نوشتن در حافظه پنهان با تگ. در این روش باید مقدار را در حافظه پنهان بنویسیم و همچنین نسخه های فعلی تگ های ارائه شده را بازیابی کرده و آنها را مرتبط با کلید کش خاص ذخیره کنیم. این با استفاده از یک کلید اضافی با یک پیشوند اضافه شده به کلید ارائه شده به دست می آید.
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 }
افزودن متد get
برای بازیابی مقادیر تگ شده از حافظه پنهان. در اینجا، مقدار را با استفاده از کلید، و همچنین نسخه های برچسب مرتبط با آن کلید، بازیابی می کنیم. سپس اعتبار تگ ها را بررسی می کنیم. اگر هر تگ نامعتبر باشد، مقدار از حافظه پنهان حذف شده و null
برگردانده می شود. اگر همه تگ ها معتبر باشند، مقدار ذخیره شده برگردانده می شود.
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 } }
پیاده سازی متد updateTagsVersions
برای به روز رسانی نسخه های تگ. در اینجا، ما روی همه تگهای ارائه شده تکرار میکنیم و نسخههای آنها را با استفاده از مثلاً زمان فعلی بهعنوان نسخه بهروزرسانی میکنیم.
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) } }
این رویکرد هم راحت و هم جهانی است. ذخیره برچسب شده نیاز به تعیین دستی همه کلیدها برای باطل شدن را برطرف می کند و فرآیند را خودکار می کند. با این حال، به منابع اضافی نیاز دارد: ذخیره داده های نسخه برچسب و بررسی اعتبار آنها با هر درخواست.
اگر سرویس کش شما سریع باشد و از نظر اندازه محدودیت زیادی نداشته باشد، این رویکرد تأثیر قابل توجهی بر عملکرد نخواهد داشت. برای به حداقل رساندن بار، می توانید کش برچسب گذاری شده را با مکانیسم های ذخیره محلی ترکیب کنید.
به این ترتیب، ذخیره برچسب نه تنها عدم اعتبار را ساده می کند، بلکه کار با داده ها را انعطاف پذیرتر و قابل درک تر می کند، به خصوص در سیستم های پیچیده با مقادیر زیادی داده به هم پیوسته.