Расширяем и улучшаем Cache в ASP.NETИсточник: realcoding
Про ASP.NET-объект Cache наверняка знает каждый web-разработчик на платформе .NET. Совсем не странно, ведь это единственное решение для кэширования данных web-приложения в ASP.NET, доступное прямо из коробки. Синхронизация обновлений
Закэшированным данным, как и многому в нашем мире, со временем свойственно терять актуальность. Поэтому, по истечении отведенного интервала времени или при изменении одной из зависимостей, сохраненный элемент исчезнет из кэша, и нам придется положить его туда заново. MSDN расскажет нам, как это сделать, и мы напишем так:
Все выглядит правильно, но ровно до тех пор, пока мы не осознаем, что код может выполняться одновременно в нескольких потоках. И в этих нескольких строчках мы только что организовали классическое состояние гонки (race condition). Ничего страшного, конечно, не произойдет, просто элемент кэша будет обновлен несколько раз, и каждый раз для этого мы обратимся к базе данных. Но это лишняя работа, и ее можно избежать, применив обычную double-check блокировку. Вот так:
Таким образом, мы гарантируем, что только один поток отправится в базу данных за списком товаров, а остальные подождут его возвращения. Можно писать такой код всякий раз, когда мы работаем с кэшем, но лучше реализовать расширение объекта Cache при помощи extension-метода.
Итак,
Если в кэше нашелся элемент с заданным ключом, метод просто возвратит его, а в противном случае установит блокировку и выполнит загрузку. Конструкции
нужны для того, чтобы не получить такую же гонку при удалении элемента кэша в другом потоке. Теперь для получения нашего списка товаров мы можем написать только одну строку, а все остальное наше расширение возьмет на себя. Перегрузки можно добавить по вкусу :)
Итак, с одной задачей мы расправились, но есть еще кое-что интересное. К примеру, ситуация, когда необходимо объявить невалидными сразу несколько связанных элементов кэша. ASP.NET Cache предоставляет нам возможность создания зависимости от одного или нескольких элементов. Примерно так:
И при обновлении элемента parent элемент child будет удален. Пока ничего не напоминает? Что ж, еще немного кода, и у нас появится полноценная…
Поддержка тегов и групповой инвалидации
Подсистема тегирования будет работать вполне прозрачно, используя уже описанный механизм зависимостей от ключа в кэше. Такими ключами и будут - угадайте что? - теги. При добавлении в кэш элемента с некоторой коллекцией тегов мы создадим соответствующее количество ключей в кэше и зависимость от них.
Здесь в качестве значения версии тега я использую текущее время, как рекомендовалось в статье о Memcached , но в нашем случае сравнивать ничего не придется, этим займется ASP.NET. Теперь при добавлении элемента в кэш мы можем легко и просто создать зависимость от указанных нами тегов.
Осталось совсем немного - обеспечить сброс такой группы элементов в кэше. Для этого нужно всего лишь обновить элементы кэша, представляющие интересующие нас теги. Все остальное произойдет само собой.
Обратите внимание на то, что в методе, создающем зависимость от тегов, использовался метод
, а здесь -
. Существенная разница между этими в остальном очень похожими методами в том, что первый записывает информацию в кэш только в том случае, если указанный ключ не был создан ранее, а второй - записывает в любом случае, затирая старые данные. Это различие очень важно в нашем случае, потому что при простом добавлении элемента в кэш нам не нужно обновлять уже существующие теги.
На этом, кажется, и все…
Я требую продолжения банкета!
А продолжать тут еще есть куда. Например, можно доработать приведенный здесь метод Get так, чтобы вместо немедленного удаления данные кэша временно "переезжали" в другую ячейку, и вместо блокировки возвращать запрошенную информацию из нее, пока новые данные загружаются в кэш.
Можно вместо extension-методов сделать некую абстракцию провайдера кэширования и работать с любым хранилищем без изменения кода приложения или полностью отключать кэш при отладке, использовать IoC… да мало ли что!
И я надеюсь, что подходы, описанные в моей статье, окажутся полезными для вас ;) UPDATE : Посмотрев незамыленным глазом на собственный код, я узрел в нем одну нехорошую вещь - блокировка при синхронизации выставлялась на весь кэш целиком. Поэтому я изменил extension-метод Get с тем, чтобы он принимал пользовательский объект для блокировки. |