Разширяване и подобряване на кеша

Всеки уеб разработчик на .NET знае за обекта ASP.NET Cache. Изобщо не е странно, защото това е единственото решение за кеширане на данни от уеб приложения в ASP.NET, достъпно веднага след изваждането от кутията. Достатъчно представен и лек, оборудван с механизми за приоритет, изпреварване, зависимост и обратно извикване, Cache е много подходящ за малки приложения, работещи в AppDomain. Изглежда, че Microsoft е предоставил всичко необходимо ... Но все пак искам да го направя малко по-добро. Какво точно?

Синхронизиране на актуализации

Кешираните данни, както много в нашия свят, са склонни да губят уместност с течение на времето. Следователно, след като изтече определеният интервал от време или когато една от зависимостите се промени, записаният елемент ще изчезне от кеша и ще трябва да го поставим отново там. MSDN ще ни каже как да направим това и ще го напишем така:

продукти; продукти = ( Списък

)Кеш [ "Продукти" ]; if (products == null ) products = db.Products.ToList(); Cache.Insert( "Продукти" , продукти); >

* Този изходен код беше подчертан с инструмента за открояване на изходния код.

Всичко изглежда правилно, но точно докато разберем, че кодът може да се изпълнява едновременно в няколко нишки. И в тези няколко реда ние току-що настроихме класическо състезателно условие. Нищо ужасно, разбира се, няма да се случи, просто елементът на кеша ще се актуализира няколко пъти и всеки път ще се обръщаме към базата данни за това. Но това е допълнителна работа и може да се избегне чрез прилагане на обичайното заключване с двойна проверка. Като този:

частен статичен обект _lock = нов обект ();

стойност на обекта; if ((value = Cache[ "Products" ]) == null ) заключване(_lock) if ((value = Cache[ "Products" ]) == null ) value = db.Products.ToList(); Cache.Insert( "Продукти" , стойност); > > > var products = ( List

* Този изходен код беше подчертан с инструмента за открояване на изходния код.

По този начин гарантираме, че само една нишка ще отиде в базата данни за списъка с продукти, а останалите ще чакат да се върне. Можем да пишем код по този начин винаги, когато работим с кеш, но е по-добре да разширим Cache обекта с метод за разширение.

public static T Get ( този кеш кеш, ключ на низ, обект @lock, селектор на Func, DateTime absoluteExpiration) стойност на обекта; if (( value = cache.Get(key)) == null ) lock (@lock) if (( value = cache.Get(key)) == null ) value = selector(); cache.Insert(ключ, стойност, null, absoluteExpiration, Cache.NoSlidingExpiration, CacheItemPriority.Normal, null); > > > връщане (T) стойност; > * Този изходен код беше подчертан с инструмента за открояване на изходния код.

Ако елемент с дадения ключ бъде открит в кеша, методът просто ще го върне, в противен случай ще получи заключване и ще се зареди. Конструкциите стойност = Cache.Get(key) са необходими, за да не се получи същото състезание при изтриване на кеш елемент в друга нишка. Сега, за да получим нашия списък с продукти, можем да напишем само един ред, а нашето разширение ще се погрижи за останалото. Претоварванията могат да се добавят на вкус :)

частен статичен обект myLock = нов обект() ; . var products = Cache.Get List

>( "Продукти" , myLock, () => db.Products.ToList(), DateTime .Now.AddMinutes(10));

* Този изходен код беше подчертан с инструмента за открояване на изходния код.

И така, ние се справихме с една задача,но има нещо друго интересно. Например ситуация, при която е необходимо да се обявят за невалидни няколко свързани кеш елемента наведнъж. ASP.NET Cache ни дава възможност да създадем зависимост от един или повече елементи. така:

низ [] зависимости = < "родител" >; Cache.Insert( "child" , someData, new CacheDependency( null , dependencies)); * Този изходен код беше маркиран с инструмента за открояване на изходния код.

И когато актуализирате елементаparent, елементътchildще бъде премахнат. Все още не ви напомня за нищо? Е, малко повече код и ще имаме пълноправен ...

Поддръжка на етикети и анулиране на групи

Подсистемата за маркиране ще работи доста прозрачно, използвайки вече описания механизъм за зависимост на кеша. Тези ключове ще бъдат - познайте какво? - етикети. Когато добавяме елемент към кеша с някаква колекция от тагове, ще създадем съответния брой ключове в кеша и зависимост от тях.

public static CacheDependency CreateTagDependency( this Cache cache, params string [] tags) if (tags == null tags.Length return null ;

дълга версия = DateTime.UtcNow.Ticks; for ( int i = 0; i "_tag:" + tags[i], version, null, DateTime .MaxValue, Cache.NoSlidingExpiration, CacheItemPriority.NotRemovable, null); > връщане на нова CacheDependency( null, tags.Select(s => "_tag:" + s).ToArray()); >

* Този изходен код беше подчертан с инструмента за открояване на изходния код.

Тук използвам текущото време като стойност на версията на маркера, както се препоръчва в статията за Memcached, но в нашия случай не е нужно да сравняваме нищо, ASP.NET ще се погрижи за това. Сега, когато добавяме елемент към кеша, можем лесно да създадем зависимост от таговете, които сме посочили.

Сache.Insert( "key" , value , Сache.CreateTagDependency( "tag1" , "tag2" ));

* Този изходен код беше подчертан с инструмента за открояване на изходния код.

Остава съвсем малко - да се осигури нулирането на такава група елементи в кеша. За да направим това, просто трябва да актуализираме кеш елементите, представляващи маркерите, които ни интересуват. Всичко друго ще стане от само себе си.

public static void Inval >this Cache cache, params string [] тагове) дълга версия = DateTime .UtcNow.Ticks; for ( int i = 0; i "_tag:" + tags[i], version, null, DateTime .MaxValue, Cache.NoSlidingExpiration, CacheItemPriority.NotRemovable, null); > >

* Този изходен код беше подчертан с инструмента за открояване на изходния код.

Имайте предвид, че методът, който създава зависимостта от тагове, използва метода cache.Add, но тук е cache.Insert. Съществената разлика между тези иначе много сходни методи е, че първият записва информация в кеша само ако указаният ключ не е бил създаден по-рано, а вторият го записва така или иначе, като презаписва старите данни. Това разграничение е много важно в нашия случай, защото като просто добавим елемент към кеша, не е необходимо да актуализираме съществуващите тагове.

Изглежда това е всичко...

Настоявам за продължаване на банкета!

И има още какво да продължим. Например, можете да промените метода Get, показан тук, така че вместо незабавно изтриване на данните от кеша, той временно да се „премести“ в друга клетка и вместо да блокира, да върне исканата информация от нея, докато новите данни се зареждат в кеша.

Вместо методи за разширение, можете да направите някаква абстракция на доставчика на кеширане и да работите с всяко хранилище, без да променяте кода на приложението, или напълно да деактивирате кеша по време на отстраняване на грешки, да използвате IoC ... даникога не знаеш!

И се надявам, че подходите, описани в моята статия, ще бъдат полезни за вас ;)

АКТУАЛИЗАЦИЯ: След като погледнах собствения си код с отворено око, видях едно лошо нещо в него - заключването на синхронизацията беше зададено на целия кеш. Така че промених метода на разширениеGet, за да приема персонализиран заключващ обект.

Hardcore conf в C++. Каним само професионалисти.