Кэширование в Windows Azure

от автора

Проблема кэширования встает перед любым высоконагруженным приложением. В Windows Azure, где основным алгоритмом увеличения производительности является добавление экземпляров приложения, роль кэша становится еще более важной, т.к. с его помощью можно обеспечить «общую память» для всех экземпляров.

MemoryCache

Вообще, этот класс не имеет отношение к Azure, но не упомянуть его в статье о кэшировании просто нельзя.

Начиная с .Net 4 появилось новое пространство имен System.Runtime.Caching. Занимается MemoryCache, как следует из названия, созданием хранилища объектов в памяти. Для меня он важен тем, что работает гораздо быстрее, чем Cache из пространства System.Web.Caching. Еще приятной особенностью является то, что можно создать несколько кэшей с разными настройками.

Для простоты работы, можно сразу описать параметры кэша в конфигурации и потом обращаться к нему через MemoryCache.Default. Другие кэши нужно инициализировать принудительно и хранить ссылки на них. Параметры можно задать как в рантайме, так и в конфигурации:

<configuration>   <system.runtime.caching>     <memoryCache>       <namedCaches>           <add name="default" cacheMemoryLimitMegabytes="0" physicalMemoryPercentage="0" pollingInterval="00:02:00" />       </namedCaches>     </memoryCache>   </system.runtime.caching> </configuration> 

In-Role Cache

Как правило, для у каждого приложения в облаке есть несколько экземпляров: с одной стороны это обеспечивает отказоустойчивость (SLA Azure в принципе требует не мене двух экземпляров) и масштабируемость при росте-падении нагрузок.

Регулярно возникает желание, обеспечить единую «память» для всех экземпляров. Самым простым примером будет сессия. Для этого в Azure имеется механизм In-Role Cache, который позволяет выделить часть или всю память экземпляра под кэш, и он будет доступен из всех экземпляров приложения.

Для его использования необходимо добавить в решение пакет Windows Azure Caching и настроить роли для работы с ним.

Сначала нам нужно включить кэш на экземплярах или добавить выделенные под кэш экземпляры в решение.

Внимание: кэш не поддерживается на сверхмалых (extra small) экземплярах.

Картинки про включение кэша

Включение кэша на существующей роли

Создание роли, выделенной под кэш

Всегда будет присутствовать кэш с именем “default”. Мы можем добавить кэши со своими именами и различными настройками.

Настройки кэша

Высокая доступность (High Availability).
Требует, чтобы в решении было не менее двух экземпляров, на которых расположен кэш. Данные попадающие в кэш будут храниться минимум на двух экземплярах и выход из строя одного экземпляра не приведет к потере данных. Использовать эту опцию стоит осторожно, т.к. кэш начинает поглощать гораздо больше ресурсов.

Оповещения (Notifications)
Включить-выключить механизм оповещений для кэша. Появились они буквально только-что и реальных сценариев для них, я пока не придумал.

Очистка (Eviction policy)
Как будет очищаться кэш в случае переполнения. Для включения пока доступна только одна опция – LRU (Least recent use). Т.е. к какому объекту меньше всего обращались – тот и будет удален.

Устаревание (Expiration Type) и время жизни (TTL, Time To Live)
Два взаимосвязанных параметра, указывают, как и за какой срок (в минутах) будут устаревать в кэше и исчезать из него. Т.е. если параметр очистки является больше аварийным (ситуация переполнения кэша ни к чему хорошему обычно не приводит), то устаревание позволяет нам описать как объекты должны исчезать из кэша при нормальной работе.
None. Объекты будут храниться в кэше вечно (до перезагрузки). Требует, чтобы время жизни было установлено в ноль.
Absolute.Объект храниться в кэше определенное время после того, как туда попал.
Sliding window. Моя любимая опция. Объект исчезнет из кэша через указанное время после последнего обращения. Т.е. объекты, к которым обращаются постоянно будут жить в кэше.

Настройка клиента кэша

В целом, всё довольно просто: в конфигурационном файле описываем, какие кэши у нас есть и где расположены. Вставляем в конфигурационный файл в секцию configuration следующие строки (их шаблон должен был уже создать NuGet при установки пакета кэширования).

<dataCacheClients>   <dataCacheClient name="default">     <autoDiscover isEnabled="true" identifier="CacheWorkerRole" />     <localCache isEnabled="true" sync="TimeoutBased" objectCount="100000" ttlValue="300" />   </dataCacheClient>   <dataCacheClient name="MyNamedCache">   … </dataCacheClients> 

В качестве идентификатора нужно указывать имя роли содержащей кэш в проекте. В нашем случае – CacheWorkerRole. А не имя точки доступа в Azure, типа mycoolapp.cloudapp.net.

Пояснений, наверное, требует только тэг localCache, который указывает, что экземпляр может хранить объекты у себя локально и принцип хранения. objectCount определяет сколько объектов мы будем хранить локально, при достижении указанного количества, кэш удалит из локальной копии 20% объектов, к которым дольше всего не обращались.

Синхронизация по времени (TimeBased) указывает, что объекты будут храниться в локальном кэше указанное в ttlValue количество секунд. Синхронизации по оповещениям (NotificationBased) требует, чтобы механизм оповещений был включен в кэше. В этом случае параметр ttlValue указывает, с какой частотой локальный кэш будет проверять наличие изменений в кэше.

Базовые настройки завершены. Теперь, для примера, подключим сессии нашей веб-роли к кэшу. В IIS это делается очень просто, заменив стандартный InProc провайдер сессии на провайдер, использующий кэш.

<sessionState mode="Custom" customProvider="AFCacheSessionStateProvider">       <providers>         <add name="AFCacheSessionStateProvider"               type="Microsoft.Web.DistributedCache.DistributedCacheSessionStateStoreProvider, Microsoft.Web.DistributedCache"               cacheName="default"               dataCacheClientName="default"               applicationName="AFCacheSessionState"/>       </providers> </sessionState> 

Сессия является прекрасным примером того, где можно использовать кэш. Но не стоит ограничиваться только ей. Например, можно сложить в кэш страницы, тогда другим экземплярам не придется тратить время на их построение. И само-собой, в кэш можно и нужно складывать наши собственные данные.

Перед тем, как перейти к примерам работы с кэшем из кода, остановимся на совсем новом виде кэша.

Cache Service

Пока кэш как сервис доступен в режиме предварительного просмотра (preview). Востребован он может быть в сценариях, где разные решения должны иметь доступ к одним и тем же данным. In-Role кэш доступен только в пределах того решения, к которому он привязан. Кэш-сервис такого недостатка лишен.

Настройка кэш-сервиса один в один такая же, как и настройка In-Role кэша, но проводить её нужно не в студии, а на портале управления Azure. В конфигурацию роли добавится ключ доступа к кэшу.

Еще плюсам кэш-сервиса можно отнести:
   — несколько меньшую цену;
   — отсутствие головной боли при обновлениях развертываний (они не будут затрагивать кэш);
   — поддержку протокола memcached, что позволяет подключить к нему не только PaaS решения, но и любой тип виртуалок.

Еще несколько слов по настройке

Создавая кэш стоит знать сколько объектов, в каком объеме будут в нём храниться и с какой частотой их будут читать и писать. Когда эти величины известны, данные нужно вбить в одну из эксельных табличек (для сервиса или ролей) и получить рекомендации по тому, сколько и чего вам нужно в терминах тарифицируемых единиц.

Объекты хранятся в кэше в серилизованном виде, потому для определения объема объекта нужно получить размер после серилизации собственно объекта и его ключа.

Размер объекта в кэше после серилизации ограничен 8 мегабайтами. Если вы выходите за лимит, можно включить компрессию для кэша, тогда перед попаданием в кэш объекты будут сжиматься. Вообще, компрессия может положительно сказаться на работе кэша, если расходы на упаковку-распаковку будут меньше, чем расходы на сетевую передачу. Выяснить это, к сожалению, можно только экспериментально.

Для увеличения пропускной способности кэша можно увеличить кол-во подключений к нему параметром maxConnectionsToServer. По умолчанию создается только одно подключение к кэшу.

Работа с кэшем из кода

Всё необходимое для работы с кэшем живет в Microsoft.ApplicationServer.Caching.

Для начала создать объект кэша и командами Add, Put, Get и Remove начать работать с данными.

DataCache dc = new DataCache("default");  dc.Add("test", DateTime.Now);           //добавить объект в кэш dc.Put("test", DateTime.Now);           //добавить или заменить  DateTime dt=(DateTime)dc.Get("test");   //получить dc.Remove("test");                      //удалить 
Гонка

Для предотвращения гонки, следует использовать GetAndLock, PutAndUnlock и Unlock. Оператор GetAndLock не блокирует обычный Get и не мешает «грязному» чтению.

try {      DataCacheLockHandle lockHndl;      object value = dc.GetAndLock("test", new TimeSpan(0, 0, 5), out lockHndl);      //модифицируем объект      dc.PutAndUnlock("test", value, lockHndl);      //или dc.Unlock("test", lockHndl) если ничего не меняли } catch (DataCacheException de) {       if (de.ErrorCode == DataCacheErrorCode.KeyDoesNotExist)       {          //объекта нет       } } 

Чтение обновлений

Не трудно представить себе сценарий, при котором необходимо совершить какие-то действия при изменении объекта в кэше. Можно получать объект и сравнивать его с текущим значением, но можно снизить нагрузку на кэш используя метод GetIfNewer.
object val = DateTime.Now;
DataCacheItemVersion version = dc.Put(«test», val);

while (true) {      val = dc.GetIfNewer("test", ref version);      if (val != null)      {           //объект изменился      }      Thread.Sleep(1000); } 

Если объект появился в кэше, где-то в другом месте, можно получить его версию из объекта DataCacheItem.

DataCacheItem dci = dc.GetCacheItem("test"); DataCacheItemVersion version = dci.Version; object val = dci.Value; 

Регионы

Для группировки объектов, можно использовать регионы. В каждый из перечисленных выше методов вторым аргументом можно передать имя региона (предварительно его создав), в котором нужно создать объект. А после легко осуществлять перебор объектов в кэше.

if (dc.CreateRegion("region")) {      //региона не было, он создан }  dc.Put("test", DateTime.Now, "region");  foreach (KeyValuePair<string, object> kvp in dc.GetObjectsInRegion("region")) {       //обрабатываем объекты } 

У регионов есть пара особенностей, которые стоит учитывать при работе с ними.

Не стоит перебирать объекты в регионе напрямую как в коде выше. Если другой поток сложит или уберет в нем объект, нарвемся на исключение «коллекция изменилась».

Вторая особенность: регион живет в пределах одного экземпляра. Т.е. если распределение объектов по регионам неравномерно, то может возникнуть ситуация, когда один из экземпляров бездельничает, а другой кипит под нагрузкой.

О чём стоит помнить при работе с кэшем

   — Размер объекта в кэше ограничен 8 мегабайтами
   — Если используются регионы, они должны наполняться равномерно
   — Кэшируйте объекты локально всегда, когда это возможно.
   — Включайте высокую доступность только там, где это нужно.
   — Используйте блокировки (GetAndLock) только там, где это необходимо
   — Не читайте объекты, если они не обновлены (используйте GetIfNewer)

Надеюсь, эта статья поможет другим пройти по меньшему количеству граблей, чем собрал я. Успехов в разработке, пусть ваши приложения будут быстрыми.

ссылка на оригинал статьи http://habrahabr.ru/post/200744/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *