برنامه نویس
آموزش Cache در Asp.Net Core (قسمت دوم : EasyCaching)
در این مقاله برای نمایش کد ها از GitHub Gist استفاده شده و ممکن است Load شدن کد ها کمی طول بکشد یا به نرم افزار های رفع تحریم نیاز داشته باشید.
در قسمت اول درمورد سیستم Cache دیفالت موجود در Asp.Net Core و مزیت ها و معایب آن گفتیم. قسمت اول مقاله را میتوانید از این لینک بخوانید.
در این قسمت میخواهیم یک پکیج محبوب و کاربردی برای پیاده سازی کش در Asp.Net Core را بررسی کنیم.
در دنیای امروز برنامه نویسی ، پکیج ها و فریمورک ها نقش بسیار مهمی را ایفا میکنند بطوری که در بسیاری ازین موارد استفاده از این پکیج ها عمل عاقلانه تری نسبت به دوباره نویسی فیچر های مربوطه است.
برای عمل کشینگ در Asp.Net Core نیز پکیج های فوقالعاده ای وجود دارد که در این مقاله به بررسی و استفاده از پکیج EasyCaching میپردازیم.
در این پکیج هر یک از متد های موجود در عملیات کشینگ بصورت بهینه ای تعریف شده که قابل استفاده است. سیستمی که این پکیج برای کش کردن داده ها استفاده میکند همان سیستم کش Asp.Net Core هست و بنوعی سوار بر این سیستم قابلیت های بیشتر و بهتری را اراعه میدهد.
این متد ها شامل :
- TrySet/TrySetAsync
- Set/SetAsync
- SetAll/SetAllAsync
- Get/GetAsync(with data retriever)
- Get/GetAsync(without data retriever)
- GetByPrefix/GetByPrefixAsync
- GetAll/GetAllAsync
- Remove/RemoveAsync
- RemoveByPrefix/RemoveByPrefixAsync
- RemoveAll/RemoveAllAsync
- Flush/FlushAsync
- GetCount
- GetExpiration/GetExpirationAsync
Refresh/RefreshAsync(This will be discarded later, just use set directly)
یکی از قابلیت های دیگر این پکیج سازگاری آن با انواع Cache Provider های موجود است. بطور خلاصه Cache Provider ها همان اراعه دهندگان حافظه Ram در قالب ها و ابزارهای مختلف هستند. برخی از این ها با داشتن الگوریتم های بهینه تر ، سرعت بالاتری از ردو بدل کردن اطلاعات در Ram را در اختیار ما قرار میدهند و Local بودن یا Distributed بودن را کنترل میکنند. Cache provider های گوناگونی وجود دارد که هریک به شکلی کار میکند ، برای مثال شما میتوانید با Provider ای مستقیما با خود Ram برای Get و Set کردن کش های خود در ارتباط باشید و یا در روشی دیگر از یک دیتابیس(Redis) ، جدا از دیتابیس اصلی برنامه ، که حافظه مصرفی آن Ram هست و منابع حافظه شمارا نیز مدیریت میکند ، برای کش های خود استفاده کنید و اطلاعات را بصورت ایندکس گذاری شده در Ram ذخیره کنید که به سرعت واکشی آن میفزاید.
بطور کل Cache Provider هایی که پکیج EasyCaching با آن ها سازگار است شامل :
- In-Memory
- Memcached
- Redis(Based on StackExchange.Redis)
- Redis(Based on csredis)
- SQLite
- Hybrid
- Disk
- LiteDb
یکی دیگر از مزیت های این پکیج سازگاری آن با Serializer های مختلف است. همانطور که میدانید دیتا های ورودی و خروجی در برنامه نیاز به Serialize شدن دارند. وقتی میخواهید دیتایی در دیتابیس ذخیره کنید آن را در قالب یک شی (Model) از کاربر دریافت میکنید و شما باید برای ذخیره این دیتا ، اطلاعات درون شی را به قالبی که قابل ذخیره شدن باشد در آورید که این عمل Serialize نام دارد. دقیقا برعکس این روند بعد از واکشی اطلاعات از دیتابیس ، اطلاعات را در قالب اشیایی که قابل نمایش به کاربر باشد (DeSerialize) در میاوریم.
در کش کردن هم چیزی که شما با آن سروکار دارید دیتا است پس برای ذخیره و واکشی این دیتا از هر حافظه ای چه دیتابیس چه Ram باید از یک Serializer استفاده کنید تا عملیات Serialize و DeSerialize را برایتان انجام دهد. Serializer های مختلفی وجود دارد که بصورت پکیج هایی اراعه شده اند و اما Serializer هایی که سیستم EasyCaching آن هارا پشتیبانی میکند شامل :
- BinaryFormatter
- MessagePack
- Newtonsoft.Json
- Protobuf
- System.Text.Json
در ادامه به پیاده سازی کش با استفاده از EasyCaching در سه Provider مختلف از این پکیج میپردازیم.
1_ پروایدر InMemory :
پروایدر InMemory یک سیستم Local Caching را برای ما به وجود میاورد. (در قسمت قبلی مقاله سیستم های Local(InMemory) و Distributed را بررسی کردیم و تفاوت های میان آن ها را گفتیم.)
برای استفاده از پروادر InMemory در EasyCaching باید پکیج زیر را نصب کنید :
Install-Package EasyCaching.InMemory
در مرحله بعد کانفیگ های مربوط به این پکیج را در کلاس Startup برنامه خود میاوریم :
راحت ترین روش افزودن این پکیج به Startup صرفا افزودن حالت پیشفرض آن به متد ConfigureServices است که به شرح زیر عمل میکنیم :
این حالت از کانفیگ پکیج ، تنظیمات دیفالت خود پکیج را برای برنامه قرار میدهد و شما میتوانید با استفاده از option های دیگر که در متد ()UseInMemory وجود دارد تنظیمات شخصی سازی شده از سیستم کشینگ خود را اعمال کنید.
و تمام. هم اکنون میتوان با استفاده از اینترفیس IEasyCachingProvider که این سرویس در اختیارمان قرار داده و عمل تزریق وابستگی آن در کلاس ها و کنترلر های مان دیتای در حال عبور را کش کنیم. متد های موجود در این اینترفیس به شرح زیر میباشد :
همانطور که قبلا گفته شد سیستم کش با دیتا مرتبط است و نیازمند یک Object Serializer جهت Serialize کردن اطلاعات ورودی و ذخیره آن در Target Storage مشخص شده است.
پکیج EasyCaching برای Provider های خود یک Object Serializer دیفالت قرار داده است و تا وقتی که شما آن را طبق نیازی خاص بصورت کاستوم تغییر نداده باشید از آن استفاده میکند.
در میان پنج Serializer معرفی شده که EasyCaching آن هارا ساپورت میکند ، BinaryFormatter بصورت دیفالت در همه Provider ها برقرار است و تا وقتی که یک Serializer انتخابی به EasyCaching معرفی نکنید این پکیج از این Serializer استفاده میکند.
برای استفاده از Serializer های دیگری که معرفی شده میتوانید از لینک های زیر کمک بگیرید :
2 _ پروایدر Redis :
ردیس یک دیتابیس Key Value محور هست که محل ذخیره سازی آن Ram هست و اطلاعات بصورت موقت در آن ذخیره میشود. بطور خلاصه Key Value یعنی یکبار کلید و مقداری برای آن کلید تعریف میشود و هروقت نام کلید تعریف شده صدا زده شد مقدار نسبت داده شده به آن در اختیار ما قرار میگیرد. برای مثال کلید "Name" و مقدار "James". با این انتصاب هروقت "Name" فراخوانده شود مقدار "James" را خواهیم داشت. سیستم Key Value بخاطر عدم پیچیدگی و سادگی ای که دارد بسیار سریع عمل میکند و همچنین ایندکس گذاری هایی که ردیس روی دیتا ها انجام میدهد باعث افزایش سرعت آن نیز خواهد شد که ردیس را به سریع ترین دیتابیس Key Value دنیا تبدیل کرده.
در اینجا با توجه به قابلیت هایی که ردیس داراست یکی از بهترین گزینه ها برای انتخاب بعنوان فضای ذخیره سازی کش ها بصورت Distributed است.
برای استفاده از این دیتابیس قدرتمند ابتدا باید از طریق یکی از روش های معمول اقدام به نصب آن کنید. میتوانید فایل نصبی را از وبسایت رسمی آن دانلود کنید و یا یا با استفاده از Docker اقدام به نصب آن نمایید.
پس از نصب این دیتابیس روی سیستم خود ، برای استفاده از آن در EasyCaching ابتدا باید پکیج مورد نیاز را نصب کنید.
Install-Package EasyCaching.Redis
ادامه کار به همان سادگی پروایدر قبلی هست و فقط کافیست EasyCaching و option ردیس را به کلاس Startup اضافه کنید.
با استفاده از متد UseRedis شما قابلیت استفاده از ردیس را در EasyCaching فعال میکنید و سپس باید اطلاعات Host و Port ردیس نصب شده روی سیستم خود را به این متد معرفی کنید.
اگر ردیس را بدون تنظیمات شخصی سازی شده و در همان حالت دیفالت خودش نصب کرده باشید Host و Port شما مانند نمونه بالا 127.0.0.1 و 6379 خواهد بود و نیازی به تغییر نیست.
در مرحله بعد برای استفاده از پروایدر ردیس ، اینترفیس IRedisCachingProvider در سرتاسر برنامه در دسترس خواهد بود. این اینترفیس علاوه بر اینکه متد های اصلی موجود در EasyCaching را ساپورت کرده ، بخاطر ساختار دیتابیسی که خود ردیس در اختیار ما قرار میدهد قابلیت های بیشتری نیز اراعه خواهد داد. این قابلیت ها خصیصه های ردیس هست چرا که این دیتابیس هم دقیقا شبیه به ساختار سیستم کش Key , Value را پشتیبانی میکند و در پی آن قابلیت هایی برای مدیریت بهتر کلید ها و مقادیر اراعه میدهد.
اینترفیس IRedisCachingProvider شامل تعداد زیادی از متد ها برای پشتیبانی از قابلیت های ردیس است که در ادامه همه آنهارا نام برده و برخی را توضیح مختصری خواهیم داد :
- متد های Keys
- متد های String
- متد های Hashes
- متد های List
- متد های Set
- متد های Stored Set
- متد های Hyperloglog
- متد های Geo
برای اطلاعات بیشتر از متد های دیگر موجود در ردیس میتوانید از این لینک استفاده کنید.
3_ پروایدر Hybrid :
این پروایدر روشی از کشینگ مابین Local Caching و Distributed Caching را اراعه میدهد و میتوانید از یک پروایدر Local مثل InMemory و پروایدر Distributed مثل Redis همزمان باهم استفاده کنید که در یک کانال باهم و در راستای هم کار میکنند.
اما سوال اینجاست که این قابلیت دقیقا چه کاری انجام میدهد؟
همانطور که قبلا گفته شد کش In-Memory سرعت بالاتری نسبت به کش Distributed دارد اما دچار معایبی در حالت چند سروری هست که این معایب از جمله حذف شدن دیتا یک سرور در صورت Down شدن آن ، Sync نبودن کش سرور ها باهم دیگر و دو نسخه کش کردن دیتا در هر سرور و موارد دیگر که میتوان نام برد.
اما از طرفی کش Distributed مشکلات چند سروری را با قرار دادن یک مرکزیت واحد کش در حافظه شبکه شده سرور ها برطرف میکند و اطلاعات سرور ها از یک منبع خوانده میشود و طبعا مشکلات In-Memory را نخواهیم داشت اما به دلیل ردو بدل شدن دیتا در محیط شبکه و عمل Serialize , Deserialize که هنگام عبور دیتا روی آن صورت میگیرد ، بخشی از سرعت کاهش خواهد یافت و درنهایت Performance کمتری نسبت به In-Memory اراعه میدهد.
حالا برای اینکه بتوانیم سیستم کش خودمان را طوری طراحی کنیم که عیب های (Local)In-Memory و Distributed را نداشته باشیم و هم بتوانیم از هریک به شکلی درست استفاده کنیم که هم اطلاعاتمان Sync باشد و هم از سرعت بالای In-Memory برخوردار شویم میتوانیم از پروایدر Hybrid استفاده کنیم.
شیوه کار این پروایدر به این صورت است که وقتی برنامه برای بار اول به کش In-Memory درخواستی ارسال میکند و کش مورد نظر در آن وجود ندارد ، برنامه یک درخواست دیگر به کش Distributed ارسال میکند و دیتای مورد نظر را به کاربر بازگشت میدهد و علاوه بر آن یک کپی از کش آن دیتا ، در کش In-Memory هم ایجاد میکند. با این ساختار از دفعات بعد که کاربر درخواستی ارسال کند دیتای درخواستی در In-Memory نیز موجود خواهد بود و سریع تر از بار اول پاسخ را ارسال خواهد کرد.
از طرفی نیز وقتی کاربر دیتای جدیدی را ذخیره میکند ابتدا آن دیتا در In-Memory کش شده و سپس با درخواست خود پروایدر در کش Distributed هم اعمال میشود تا در نهایت دیتابیس نیز آن را ذخیره کند.
وقتی این اتفاق میفتد پروایدر Hybrid با کمک پکیج Bus.Redis به کش In-Memory سرور های دیگر دستور Pull کردن دیتا کش های جدید را ارسال میکند و در نهایت همه سرور ها نیز به کمک Distributed مرکزی باهم Sync خواهند بود.
برای فعال سازی این پروایدر باید پکیج های زیر را در برنامه خود نصب کنید:
Install-Package EasyCaching.HybridCache
Install-Package EasyCaching.InMemory
Install-Package EasyCaching.Redis
Install-Package EasyCaching.Bus.Redis
در این مجموعه از پکیج ها از یک پروایدر Local(InMemory) و یک پروایدر distributed(Redis) استفاده شده و همانطور که گفته شد مدیریت هماهنگ سازی این دو توسط پکیج دیگری بنامEasyCaching.Bus.Redis
صورت میگیرد.
تنظیمات فعالسازی این پروایدر هم متشکل از تنظیمات دو پروایدر In-Memory و Redis بعلاوه معرفی این دو به هم در متد UseHybrid خواهد بود.
برای استفاده از این پروایدر باید اینترفیس IHybridCachingProvider را فراخوانی کنیم. متد های موجود در این اینترفیس همان متد هایی است که در اینترفیس IEasyCachingProvider وجود دارد و از نظر نام متد و روش استفاده تفاوتی میان آن نیست.
پیشنهاد شخصی در Distributed Cache ها :
همانطور که گفته شد Distributed کش ها گزینه مناسب تری برای برنامه های چند سروری هست اما در این حالت مواردی مثل Round Trip شبکه و جابجایی اطلاعات در این محیط بعلاوه Serialize , Deserialize هایی که باید انجام شود دلیلی میشود تا سرعت آن در پاسخ به درخواست های برنامه نسبت به حالت تک سروری(In-Memory) کمتر باشد. Hybrid Provider یکی از روش های حل این مشکل بوده که معرفی کردیم. اما برای اینکه تیر خلاص را به پیکره سیستم Distributed Cache خود بزنید و تریک فنی آخر را نیز روی آن اجرا کنید پیشنهاد میکنم از پکیج EasyCaching.Extensions.EasyCompressor که بر پایه پکیج EasyCaching نوشته شده استفاده کنید. این پکیج اطلاعات را قبل از کش شدن فشرده سازی میکند و حجم اطلاعات را به طور محسوسی کاهش میدهد که میزان فضای اشغالی Ram را کم کرده و همچنین عمل جابجایی اطلاعات را نیز تسریع میبخشد. میتوانید از این پکیج هم در Redis و هم در Hybrid استفاده کنید. چگونگی استفاده از آن نیز در لینک Github ذکر شده موجود است.
معرفی پروژه :
تا اینجا با مفاهیمی که برای شروع استفاده حرفه ای از کش در پروژه تان نیاز بود ، آشنا شدید. در پروژه های واقعی میتوانیم از این سیستم به روش های مختلفی در سطوح مختلفی از برنامه استفاده کنیم برای مثال کد های مربوط به عملیات کش را میتوان بصورت ساده در هر کنترلر تزریق و در اکشن ها استفاده کرد یا از لایه کنترلر آن را به لایه سرویس منتقل کرد ، در روشی دیگر میتوانیم یک Attribute برای این عمل در نظر بگیریم و یا اینکه آن را بصورت یک Middleware اختصاصی در برنامه پیاده کنیم.
در این پروژه علاوه بر اینکه سعی کرده ام استفاده از Provider های معرفی شده را در محیط واقعی تر پیاده سازی کنم ، علاوه بر آن در هر پروژه از این Solution کش را به شیوه ای متفاوت در لایه های مختلفی از برنامه قرار داده ام تا شما همراهان بتوانید طبق نیازتان از روشی مناسب و بهینه در پروژه های واقعی خود از آن استفاده کنید.
مقالات بیشتر در دات نت زوم
مطلبی دیگر از این انتشارات
آموزش Unit Testing با استفاده از NUnit و Moq بخش اول: آشنایی با NUnit
مطلبی دیگر از این انتشارات
آموزش Microservices در ASP.NET Core (سری اول)
مطلبی دیگر از این انتشارات
وب اسمبلی (WebAssembly) چیه؟ و چرا آینده Web هست؟!