Skip to content

Latest commit

 

History

History
151 lines (91 loc) · 18.4 KB

Cache-Aside pattern.md

File metadata and controls

151 lines (91 loc) · 18.4 KB

‏Cache-Aside pattern

داده‌ها را در صورت نیاز در حافظه cache از طریق یک ذخیره‌گاه داده (data store) بارگیری کنید. با این کار می‌توان کارایی دستگاه را بهبود بخشید و به حفظ ثبات (consistency) بین داده‌های ذخیره شده در حافظه کَش (cache) و داده‌های موجود در سایر ذخیره‌گاه داده‌ها کمک کنید.

موضوع و مشکل

برنامه‌ها از حافظه کَش (cache) برای بهبود دسترسی متوالی به اطلاعات نگهداری شده در یک ذخیره‌گاه داده استفاده می‌کنند. بااین‌حال این بسیار غیرعملی است که انتظار داشته باشیم داده‌های cache همیشه کاملاً با داده‌های data store (شامل دیسک و...) مطابقت داشته باشند. برنامه‌ها باید راهبردی را پیاده‌سازی کنند تا مطمئن باشند که داده‌های موجود در cache تاحدامکان به‌روز هستند و همین‌طور برنامه‌ها باید بتوانند موقعیت‌های زمانی ای که داده‌های موجود در cache قدیمی شده‌اند را شناسایی و مدیریت کنند.

راه‌حل

بسیاری از دستگاه‌های ذخیره‌سازی تجاری (commercial caching systems)، عملیات خواندن را به‌صورت read-through و عملیات نوشتن را به‌صورت write-through/write-behind را ارائه می‌کنند. در این سیستم‌ها، یک برنامه، داده‌های موردنظر خود را با مراجعه به حافظه cache به دست می‌آورد. اگر داده‌ها در حافظه cache نباشد آنگاه از ذخیره‌گاه داده (data store) بازیابی شده و به حافظه cache اضافه می‌شود. هر گونه تغییر در داده‌های ذخیره شده در حافظه cache به طور خودکار به data store نیز بازگردانی و sync می‌شود.

به طور معمول حافظه cache این قابلیت گفته شده در بالا را ارائه نمی‌دهند؛ بنابراین این وظیفه بر روی برنامه‌هایی است که از حافظه‌های cache برای نگهداری داده‌ها استفاده می‌کنند.

یک application می‌تواند عملکرد ذخیره‌سازی فقط خواندنی (read-through) را با پیاده‌سازی راهبُرد Cache-Aside پیاده‌سازی کند. این راهبُرد، داده‌ها را در صورت لزوم در حافظه cache بارگذاری می‌کند. شکل زیر استفاده از الگوی Cache-Aside را برای ذخیره داده‌ها در حافظه کَش نشان می‌دهد.

cache-aside-diagram

اگر برنامه‌ای اطلاعات را به‌روزرسانی کند در ادامه می‌تواند با انجام اصلاحات در data store اصلی که احتمالاً دیسک یا موارد مشابه باشد به همراه نامعتبر کردن item مربوطه در حافظه cache، راهبُرد write-through را دنبال کند.

هنگامی که item بعدی موردنیاز است، استفاده از راهبُرد cache-aside در حافظه cache باعث می‌شود که داده‌های به‌روز شده از data store بازیابی شده و دوباره به حافظه کَش اضافه شوند.

مسائل و ملاحظات

هنگام تصمیم‌گیری در مورد نحوه اجرای این الگو به نکات زیر توجه کنید:

**طول عمر داده‌های cache شده: ** بسیاری از cacheها یک خط‌مشی یا policy انقضا دار را اجرا می‌کنند که داده‌ها را بی‌اعتبار می‌کند و در صورت عدم دسترسی به آن داده برای یک دوره مشخص، آنها را از حافظه cache حذف می‌کند. برای اینکه cache-aside مؤثر باشد باید مطمئن شوید که خط‌مشی انقضا (expiration policy) با الگوی دسترسی برنامه‌هایی که از داده‌ها استفاده می‌کنند مطابقت دارد. همین‌طور در پیاده‌سازی‌ها، مدت‌زمان انقضا را خیلی کوتاه نکنید؛ زیرا این امر می‌تواند باعث شود برنامه‌ها به طور مداوم داده‌ها را از data store بازیابی کرده و به حافظه کَش اضافه کنند. به طور مشابه، دوره انقضا را آن‌قدر طولانی نکنید که داده‌های ذخیره شده در حافظه کَش احتمالاً قدیمی و بی‌فایده شوند. به یاد داشته باشید که حافظه cache برای داده‌های نسبتاً ثابت یا داده‌هایی که مکرراً خوانده می‌شوند مفیدتر است.

**استخراج داده‌ها: ** اکثر حافظه‌های cache در مقایسه با data store ای که داده‌ها از آنجا منشأ می‌گیرند، اندازه محدودی دارند و در صورت لزوم، داده‌ها خاصی را برداشت و استخراج می‌کنند. اکثر حافظه‌های cache سیاستی را که least-recently-used نامیده می‌شود را برای انتخاب مواردی برای استخراج اتخاذ می‌کنند که این مسئله تا حدی ممکن است قابل‌تنظیم کردن (customizable) باشد. در نهایت باید ویژگی انقضای global و سایر خصوصیات cache را تنظیم کنید و ویژگی انقضای هر آیتم cache شده را برای اطمینان از مقرون‌به‌صرفه بودن استفاده حافظه cache را بررسی کنید. به‌عنوان‌مثال، اگر بازیابی یک آیتم حافظه کَش از data store بسیار پرهزینه است، نگه‌داشتن این آیتم در حافظه cache به قیمت مواردی که بیشتر در دسترس هستند؛ اما هزینه کمتری دارند، می‌تواند مفیدتر باشد.

پرایمینگ/Priming (در لغت به معنی بتونی کاری است) یا کردن یک cache: در بسیاری از روش‌ها، حافظه cache را با داده‌هایی که احتمالاً یک برنامه به‌عنوان بخشی از رویه راه‌اندازی(startup) خود به آن نیاز دارد، پر می‌کنند. اگر برخی از این داده‌ها منقضی شوند یا حذف شوند در این حالت نیز الگوی Cache-Aside همچنان می‌تواند مفید باشد.

پایداری (Consistency): پیاده‌سازی الگوی Cache-Aside ثبات و هماهنگی بین data store و cache را تضمین نمی‌کند. یک آیتم در data store را می‌تواند در هر زمانی با یک فرایند خارجی تغییر کند و این تغییر ممکن است تا دفعه بعدی که آیتم موردنظر بارگیری شود در حافظه cache منعکس نشود. در سیستمی که replicates data ( این مورد به معنی تکثیر پایگاه‌داده شناخته می‌شود، روشی برای کپی‌کردن داده‌ها است تا اطمینان حاصل شود که همه اطلاعات در زمان واقعی بین همه منابع داده یکسان می‌مانند.) در data storeها وجود دارد. اگر همگام‌سازی مکرر و چندمرتبه اتفاق بیفتد این مشکل می‌تواند بحرانی شود.

‌ Local (in-memory) caching. یک cache می‌تواند به‌صورت محلی (local) برای یک نمونه application باشد و در حافظه (in-memory) ذخیره شود. اگر یک برنامه در دفعات زیادی به داده‌‌های مشابه دسترسی داشته باشد، Cache-aside می‌تواند در این محیط مناسب باشد. بااین‌حال، این حافظه cache که به‌صورت local و private است پس نمونه‌‌های مختلف application می‌توانند هر کدام یک رونوشت از همان داده‌های حافظه cache داشته باشند. این داده‌ها می‌توانند به‌سرعت بین حافظه‌‌های cache دیگر ناسازگار و ناپایدار شوند، پس احتمالاً لازم باشد داده‌‌هایی که در یک حافظه cache به‌صورت private نگهداری می‌شوند منقضی شوند و داده‌ها دفعات بیشتری refresh/تازه‌سازی شوند. در این حالت، بررسی استفاده از سازوکار ذخیره‌سازی caching مشترک یا توزیع شده را در نظر بگیرید.

چه زمانی از این الگو استفاده کنیم

از این الگو زمانی استفاده کنید که:

*‏ استفاده از cache معمولاً اجرای عملیات read-through و write-through به‌صورت native را ارائه نمی‌دهد.

*‏ تقاضای منابع مصرفی موردنیاز تا حد زیادی غیرقابل‌پیش‌بینی است. این الگو برنامه‌ها را قادر می‌سازد تا داده‌ها را بر اساس تقاضا و نیازمندی‌ها بارگیری کنند و هیچ فرضی در مورد اینکه یک برنامه از قبل به کدام یک از داده‌ها نیاز دارد را متصور نیست.

این الگو ممکن است مناسب نباشد:

*‏ هنگامی که مجموعه‌داده‌های ذخیره شده ثابت یا استاتیک است. اگر داده‌ها در فضای cache موجود جا می‌شوند پس در مرحله اول باید حافظه cache را با داده‌‌هایی که در هنگام راه‌اندازی (startup) برنامه موردنیاز است را پر کنید و سیاستی را اعمال کنید که از منقضی شدن داده‌ها جلوگیری می‌کند.

*‏ برای حالت caching session باید اطلاعات در یک میزبان وب اپلیکیشن موجود باشد. در این محیط، باید از معرفی وابستگی‌های مبتنی بر client-server اجتناب کنید.

مثال

در Microsoft Azure می‌توانید از Azure Cache برای پایگاه‌داده (https://redis.io)[Redis] جهت ایجاد یک cache توزیع شده استفاده کنید که می‌تواند توسط چندین نمونه از یک برنامه به اشتراک گذاشته شود.

در این مثال؛ کد زیر از سرویس‌گیرنده StackExchange.Redis استفاده می‌کند که یک کتابخانه کلاینت Redis است که برای دات‌نت نوشته شده است. برای اتصال به Azure Cache برای مثال Redis، متد ConnectionMultiplexer.Connect ثابت (static) را فراخوانی کرده و connection string را ارسال کنید. این روش یک ConnectionMultiplexer را برمی‌گرداند که نشان‌دهنده برقراری اتصال است. یکی از روش‌‌های اشتراک‌گذاری یک نمونه ConnectionMultiplexer در برنامه شما، داشتن یک ویژگی استاتیک یا ثابت است که یک نمونه متصل شده را برمی‌گرداند، مشابه مثال زیر. این رویکرد یک روش ایمن و thread-safe برای مقداردهی اولیه تنها یک نمونه متصل فراهم می‌کند.

private static ConnectionMultiplexer Connection;

// Redis connection string information
private static Lazy<ConnectionMultiplexer> lazyConnection = new Lazy<ConnectionMultiplexer>(() =>
{
    string cacheConnection = ConfigurationManager.AppSettings["CacheConnection"].ToString();
    return ConnectionMultiplexer.Connect(cacheConnection);
});

public static ConnectionMultiplexer Connection => lazyConnection.Value;

متد GetMyEntityAsync در مثال کد زیر، اجرای الگوی Cache-Aside را نشان می‌دهد. این روش با استفاده از رویکرد خواندنی(read-through)، یک شی را از حافظه کَش بازیابی می‌کند.

یک شیء با استفاده از یک شناسه عدد صحیح به‌عنوان کلید شناسایی می‌شود. متد GetMyEntityAsync سعی می‌کند یک آیتم را با این کلید از حافظه کَش بازیابی کند. اگر یک مورد منطبق پیدا شد پس نتیجه برگردانده می‌شود. اگر هیچ تطبیقی در حافظه کَش وجود نداشته باشد، متد GetMyEntityAsync شی را از یک ذخیره‌گاه داده یا data store بازیابی می‌کند و آن را به حافظه کَش اضافه می‌کند و سپس آن را برمی‌گرداند. کدی که در واقع داده‌ها را از data store می‌خواند در اینجا نشان داده نمی‌شود، زیرا به ذخیره داده بستگی دارد. توجه داشته باشید که مورد ذخیره شده در حافظه کَش به‌گونه‌ای پیکربندی شده است که منقضی شود تا در صورت به‌روزرسانی در جای دیگری از فرسوده‌شدن آن جلوگیری شود.

// Set five minute expiration as a default
private const double DefaultExpirationTimeInMinutes = 5.0;

public async Task<MyEntity> GetMyEntityAsync(int id)
{
  // Define a unique key for this method and its parameters.
  var key = $"MyEntity:{id}";
  var cache = Connection.GetDatabase();

  // Try to get the entity from the cache.
  var json = await cache.StringGetAsync(key).ConfigureAwait(false);
  var value = string.IsNullOrWhiteSpace(json)
                ? default(MyEntity)
                : JsonConvert.DeserializeObject<MyEntity>(json);

  if (value == null) // Cache miss
  {
    // If there's a cache miss, get the entity from the original store and cache it.
    // Code has been omitted because it is data store dependent.
    value = ...;

    // Avoid caching a null value.
    if (value != null)
    {
      // Put the item in the cache with a custom expiration time that
      // depends on how critical it is to have stale data.
      await cache.StringSetAsync(key, JsonConvert.SerializeObject(value)).ConfigureAwait(false);
      await cache.KeyExpireAsync(key, TimeSpan.FromMinutes(DefaultExpirationTimeInMinutes)).ConfigureAwait(false);
    }
  }

  return value;
}

نمونه‌ها از Azure Cache برای Redis برای دسترسی به data store و بازیابی اطلاعات از cache استفاده می‌کنند. برای اطلاعات بیشتر، به استفاده از کش Azure برای Redis و نحوه ایجاد یک برنامه وب با Azure Cache for Redis و How to create a Web App with Azure Cache for Redisمراجعه کنید .

public async Task UpdateEntityAsync(MyEntity entity)
{
    // Update the object in the original data store.
    await this.store.UpdateEntityAsync(entity).ConfigureAwait(false);

    // Invalidate the current cache object.
    var cache = Connection.GetDatabase();
    var id = entity.Id;
    var key = $"MyEntity:{id}"; // The key for the cached object.
    await cache.KeyDeleteAsync(key).ConfigureAwait(false); // Delete this key from the cache.
}

توجه داشته باشید ترتیب مراحل مهم است. قبل از حذف آیتم از حافظه کَش باید data store را به‌روزرسانی کنید. اگر ابتدا مورد ذخیره شده را حذف کنید، یک پنجره زمانی کوچک وجود دارد که کلاینت ممکن است قبل از به‌روزرسانی data store آن را مورد را واکشی کند. این منجر به ازدست‌رفتن حافظه کَش (به دلیل حذف آیتم از حافظه کَش) می‌شود، و باعث می‌شود نسخه قبلی مورد از فروشگاه داده واکشی شده و دوباره به حافظه کَش اضافه شود در نتیجه، داده‌های کش قدیمی خواهد بود.

موضوعات مرتبط

اطلاعات زیر ممکن است هنگام اجرای این الگو مهم باشد:

-‏ الگوی Reliable web app. این الگو به شما نشان می‌دهد که چگونه الگوی cache-aside را برای وب اپلیکیشن مبتنی بر محیط ابری هستند را اعمال کنید.

-‏ Caching Guidance. اطلاعات بیشتری در مورد نحوه ذخیره‌سازی داده‌ها در یک راه‌حل ابری و مسائلی که باید هنگام پیاده‌سازی cache در نظر بگیرید را ارائه می‌دهد.

-‏ Data Consistency Primer. اپلیکیشن‌‌های مبتنی بر محیط ابری معمولاً از داده‌‌هایی استفاده می‌کنند که در  data storeها پخش شده‌اند. مدیریت و حفظ پایداری داده‌ها در این محیط یک جنبه حیاتی از سیستم است به‌ویژه مسائل هم‌زمانی (concurrency) و دردسترس‌بودن (availability) که ممکن است ایجاد شود. این آغازگر مسائل مربوط به یکپارچگی (data consistency) در میان داده‌‌های توزیع شده را توصیف و خلاصه می‌کند که چگونه یک اپلیکیشن می‌تواند یکپارچگی تدریجی(eventual consistency) را برای حفظ دردسترس‌بودن داده‌ها پیاده‌سازی کند.