Проблема кэширования встает перед любым высоконагруженным приложением. В Windows Azure, где основным алгоритмом увеличения производительности является добавление экземпляров приложения, роль кэша становится еще более важной, т.к. с его помощью можно обеспечить «общую память» для всех экземпляров.
Вообще, этот класс не имеет отношение к Azure, но не упомянуть его в статье о кэшировании просто нельзя.
Начиная с .Net 4 появилось новое пространство имен System.Runtime.Caching. Занимается MemoryCache, как следует из названия, созданием хранилища объектов в памяти. Для меня он важен тем, что работает гораздо быстрее, чем Cache из пространства System.Web.Caching. Еще приятной особенностью является то, что можно создать несколько кэшей с разными настройками.
Для простоты работы, можно сразу описать параметры кэша в конфигурации и потом обращаться к нему через MemoryCache.Default. Другие кэши нужно инициализировать принудительно и хранить ссылки на них. Параметры можно задать как в рантайме, так и в конфигурации:
Как правило, у каждого приложения в облаке есть несколько экземпляров: с одной стороны это обеспечивает отказоустойчивость (SLA Azure в принципе требует не мене двух экземпляров) и масштабируемость при росте-падении нагрузок.
Регулярно возникает желание, обеспечить единую «память» для всех экземпляров. Самым простым примером будет сессия. Для этого в Azure имеется механизм In-Role Cache, который позволяет выделить часть или всю память экземпляра под кэш, и он будет доступен из всех экземпляров приложения.
Для его использования необходимо добавить в решение пакет Windows Azure Caching и настроить роли для работы с ним.
Сначала нам нужно включить кэш на экземплярах или добавить выделенные под кэш экземпляры в решение.
Всегда будет присутствовать кэш с именем “default”. Мы можем добавить кэши со своими именами и различными настройками.
Высокая доступность (High Availability).
Требует, чтобы в решении было не менее двух экземпляров, на которых расположен кэш. Данные попадающие в кэш будут храниться минимум на двух экземплярах и выход из строя одного экземпляра не приведет к потере данных. Использовать эту опцию стоит осторожно, т.к. кэш начинает поглощать гораздо больше ресурсов.
Оповещения (Notifications)
Включить-выключить механизм оповещений для кэша. Появились они буквально только-что и реальных сценариев для них, я пока не придумал.
Очистка (Eviction policy)
Как будет очищаться кэш в случае переполнения. Для включения пока доступна только одна опция – LRU (Least recent use). Т.е. к какому объекту меньше всего обращались – тот и будет удален.
Устаревание (Expiration Type) и время жизни (TTL, Time To Live)
Два взаимосвязанных параметра, указывают, как и за какой срок (в минутах) будут устаревать в кэше и исчезать из него. Т.е. если параметр очистки является больше аварийным (ситуация переполнения кэша ни к чему хорошему обычно не приводит), то устаревание позволяет нам описать как объекты должны исчезать из кэша при нормальной работе.
None. Объекты будут храниться в кэше вечно (до перезагрузки). Требует, чтобы время жизни было установлено в ноль.
Absolute. Объект хранится в кэше определенное время после того, как туда попал.
Sliding window. Моя любимая опция. Объект исчезнет из кэша через указанное время после последнего обращения. Т.е. объекты, к которым обращаются постоянно будут жить в кэше.
В целом, всё довольно просто: в конфигурационном файле описываем, какие кэши у нас есть и где расположены. Вставляем в конфигурационный файл в секцию configuration следующие строки (их шаблон должен был уже создать NuGet при установки пакета кэширования).
В качестве идентификатора нужно указывать имя роли содержащей кэш в проекте. В нашем случае – CacheWorkerRole. А не имя точки доступа в Azure, типа mycoolapp.cloudapp.net.
Пояснений, наверное, требует только тэг localCache, который указывает, что экземпляр может хранить объекты у себя локально и принцип хранения. objectCount определяет сколько объектов мы будем хранить локально, при достижении указанного количества, кэш удалит из локальной копии 20% объектов, к которым дольше всего не обращались.
Синхронизация по времени (TimeBased) указывает, что объекты будут храниться в локальном кэше указанное в ttlValue количество секунд. Синхронизации по оповещениям (NotificationBased) требует, чтобы механизм оповещений был включен в кэше. В этом случае параметр ttlValue указывает, с какой частотой локальный кэш будет проверять наличие изменений в кэше.
Базовые настройки завершены. Теперь, для примера, подключим сессии нашей веб-роли к кэшу. В IIS это делается очень просто, заменив стандартный InProc провайдер сессии на провайдер, использующий кэш.
Сессия является прекрасным примером того, где можно использовать кэш. Но не стоит ограничиваться только ей. Например, можно сложить в кэш страницы, тогда другим экземплярам не придется тратить время на их построение. И само-собой, в кэш можно и нужно складывать наши собственные данные.
Перед тем, как перейти к примерам работы с кэшем из кода, остановимся на совсем новом виде кэша.
Пока кэш как сервис доступен в режиме предварительного просмотра (preview). Востребован он может быть в сценариях, где разные решения должны иметь доступ к одним и тем же данным. In-Role кэш доступен только в пределах того решения, к которому он привязан. Кэш-сервис такого недостатка лишен.
Настройка кэш-сервиса один в один такая же, как и настройка In-Role кэша, но проводить её нужно не в студии, а на портале управления Azure. В конфигурацию роли добавится ключ доступа к кэшу.
Еще плюсам кэш-сервиса можно отнести:
— несколько меньшую цену;
— отсутствие головной боли при обновлениях развертываний (они не будут затрагивать кэш);
— поддержку протокола memcached, что позволяет подключить к нему не только PaaS решения, но и любой тип виртуалок.
Создавая кэш стоит знать сколько объектов, в каком объеме будут в нём храниться и с какой частотой их будут читать и писать. Когда эти величины известны, данные нужно вбить в одну из эксельных табличек (для сервиса или ролей) и получить рекомендации по тому, сколько и чего вам нужно в терминах тарифицируемых единиц.
Объекты хранятся в кэше в серилизованном виде, потому для определения объема объекта нужно получить размер после серилизации собственно объекта и его ключа.
Размер объекта в кэше после серилизации ограничен 8 мегабайтами. Если вы выходите за лимит, можно включить компрессию для кэша, тогда перед попаданием в кэш объекты будут сжиматься. Вообще, компрессия может положительно сказаться на работе кэша, если расходы на упаковку-распаковку будут меньше, чем расходы на сетевую передачу. Выяснить это, к сожалению, можно только экспериментально.
Для увеличения пропускной способности кэша можно увеличить кол-во подключений к нему параметром maxConnectionsToServer. По умолчанию создается только одно подключение к кэшу.
Всё необходимое для работы с кэшем живет в Microsoft.ApplicationServer.Caching.
Для начала создать объект кэша и командами Add, Put, Get и Remove начать работать с данными.
Для предотвращения гонки, следует использовать GetAndLock, PutAndUnlock и Unlock. Оператор GetAndLock не блокирует обычный Get и не мешает «грязному» чтению.
Нетрудно представить себе сценарий, при котором необходимо совершить какие-то действия при изменении объекта в кэше. Можно получать объект и сравнивать его с текущим значением, но можно снизить нагрузку на кэш используя метод GetIfNewer.
Если объект появился в кэше, где-то в другом месте, можно получить его версию из объекта DataCacheItem.
Для группировки объектов, можно использовать регионы. В каждый из перечисленных выше методов вторым аргументом можно передать имя региона (предварительно его создав), в котором нужно создать объект. А после легко осуществлять перебор объектов в кэше.
У регионов есть пара особенностей, которые стоит учитывать при работе с ними.
Не стоит перебирать объекты в регионе напрямую как в коде выше. Если другой поток сложит или уберет в нем объект, нарвемся на исключение «коллекция изменилась».
Вторая особенность: регион живет в пределах одного экземпляра. Т.е. если распределение объектов по регионам неравномерно, то может возникнуть ситуация, когда один из экземпляров бездельничает, а другой кипит под нагрузкой.
— Размер объекта в кэше ограничен 8 мегабайтами
— Если используются регионы, они должны наполняться равномерно
— Кэшируйте объекты локально всегда, когда это возможно.
— Включайте высокую доступность только там, где это нужно.
— Используйте блокировки (GetAndLock) только там, где это необходимо
— Не читайте объекты, если они не обновлены (используйте GetIfNewer)
Надеюсь, эта статья поможет другим пройти по меньшему количеству граблей, чем собрал я. Успехов в разработке, пусть ваши приложения будут быстрыми.
Google Plus
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)
Надеюсь, эта статья поможет другим пройти по меньшему количеству граблей, чем собрал я. Успехов в разработке, пусть ваши приложения будут быстрыми.
Google Plus