Совсем недавно Microsoft презентовал большой пакет обновлений для платформы Windows Azure. В список нововведений входило долгожданное обновление Windows Azure Caching. Раньше разработчик сталкивался с некоторыми трудностями при работе с системой распределенного кэширования — кэш работал очень медленно. Так как Windows Azure хранил данные кэша на отдельных серверах, то на запрос выборки данных уходило порядка 30-100 мс, что является непозволительным для системы, ускоряющей доступ к данным.
Итак, что же изменили в системе кэширования?
Microsoft решила не отставать от своих конкурентов и добавила поддержку In-Memory Caching. Теперь данные хранятся не на отдельном сервере, а в оперативной памяти сервера и, соответственно, доступ к данным происходит моментально. Попробуем реализовать новый кэш и сравнить скорость работы со старым на примере ASP.NET MVC 3 (C#).
Caching Provider
Для доступа к кэшу нам понадобится провайдер. Чтобы реализовть провайдер, создадим для него интерфейс с необходимыми методами:
public interface ICacheProvider
{
void RemoveFromCache(string key);
T GetFromMethod<T>(string cacheKey, TimeSpan cacheDuration, Func<T> method);
T GetFromMethod<T>(string cacheKey, Func<T> method);
}
Первый метод, очевидно, удаляет объект из кэша по ключу, остальные получают (добавляют в случае отсутствия в кэше) данные с установленным ключом и периодом.
Следующим шагом реализуем сам провайдер. Здесь не описывается процесс установки Windows Azure SDK 1.7, подразумевается, что у разработчика стоят все необходимые библиотеки и компоненты для разработки под Windows Azure
Отметим несколько основных моментов, которые следует учесть при реализации провайдера:
- Кэш не имеет функционала полной очистки, поэтому нужно предусмотреть возможность инвалидации старых данных при выпуске обновлений;
- Запись в кэш может производить только один поток, во избежание ошибок;
- Windows Azure Caching не бесплатный функционал, поэтому нужно сократить обращения к кэшу до минимума.
Сам провайдер будет выглядеть так:
public class AzureCacheProvider : ICacheProvider
{
//Необходимо для инвалидации старого кэша при новом релизе
private readonly string _prefix = StaticHelper.GetCurrentVersion();
private readonly object _locker = new object();
private readonly DataCache _cache;
public AzureCacheProvider()
{
lock (_locker)
{
{
var factory = new DataCacheFactory();
_cache = factory.GetDefaultCache();
}
}
}
private void RemoveFromCache(string key, DataCache cache)
{
lock (_locker)
{
cache.Remove(_prefix + key);
HttpContext.Current.Items.Remove(_prefix + key);
}
}
private T GetFromCache<T>(string key, TimeSpan expiration, Func<T> method, DataCache cache)
{
object cacheItem = HttpContext.Current.Items[_prefix + key];
if (cacheItem == null)
{
cacheItem = cache.Get(_prefix + key);
if (cacheItem != null)
HttpContext.Current.Items[_prefix + key] = cacheItem;
}
if (cacheItem == null)
{
lock (_locker)
{
cacheItem = cache.Get(_prefix + key);
if (cacheItem == null)
{
cacheItem = method();
if (cacheItem != null)
{
HttpContext.Current.Items[_prefix + key] = cacheItem;
cache.Put(_prefix + key, cacheItem, expiration);
}
}
}
}
return (T)cacheItem;
}
public void RemoveFromCache(string key)
{
RemoveFromCache(key, _cache);
}
public T GetFromMethod<T>(string cacheKey, TimeSpan expirationSeconds, Func<T> method)
{
return GetFromCache(cacheKey, expirationSeconds, method, _cache);
}
public T GetFromMethod<T>(string cacheKey, Func<T> method)
{
return GetFromMethod(cacheKey, TimeSpan.FromMinutes(15), method);
}
}
HttpContext.Current.Items
здесь используется для хранения в памяти уже полученных в текущем запросе объектов из кэша, на случай повторного обращения к данным.
Конфигурация роли
После написания провайдера нужно настроить кэш. В версии SDK 1.7 в свойствах роли появилась новая вкладка Caching.
На данной вкладке нужно включить поддержку кэша (Enable Caching) и установить количество выделяемого объема памяти под наши данные (например: Сo-located Role: Cache Size 30%)
Web.config
Последней подготовкой будет добавление новых секций в Web.config. Если в самом начале файла отсутствует раздел configSections
, то его необходимо добавить. После чего внутрь поместить следующий код:
<section name="dataCacheClients" type="Microsoft.ApplicationServer.Caching.DataCacheClientsSection, Microsoft.ApplicationServer.Caching.Core" allowLocation="true" allowDefinition="Everywhere"/>
В конец Web.config добавляем описание секции dataCacheClients
:
<dataCacheClients>
<tracing sinkType="DiagnosticSink" traceLevel="Error" />
<dataCacheClient name="default">
<autoDiscover isEnabled="true" identifier="Указываем здесь название Web-роли, в которой настраивали кэш на предыдущем этапе" />
</dataCacheClient>
</dataCacheClients>
На этом настройка Windows Azure Caching закончена. Перейдем к проверке работоспособности.
Тестирование кэша
Для тестирования кэша создадим простенькую страничку, которая будет кэшировать текущее время:
<h1>Cache test</h1>
Before: <%=DateTime.Now.Millisecond%> ms
<%=Facade.Instance.Cache.GetFromMethod("currentTime", () => DateTime.Now)%>
After: <%=DateTime.Now.Millisecond%> ms
<%using(Html.BeginForm("ClearCache","Admin"))%>
<%{%>
<input type="submit" value="Clear Cache"/>
<%}%>
Facade.Instance.Cache
в коде выше является экземпляром объекта AzureCacheProvider
, полученным с помощью IoC.
Действие ClearCache
выглядит достаточно просто:
public ActionResult ClearCache()
{
Facade.Instance.Cache.RemoveFromCache("currentTime");
return View("admin");
}
Результаты
После компилирования проекта и запуска странички в облаке мы видим следующий результат:
Cache test
Before: 553 ms
6/10/2012 11:09:40 AM
After: 569 ms
При первом открытии страницы происходит добавление записи в кэш, которое в данном примере занимает где-то 16 мс. При последующем обновлении страницы результат получается таким:
Cache test
Before: 508 ms
6/10/2012 11:09:40 AM
After: 508 ms
Выборка данных из нового In-Memory Distributed Cache занимает меньше 1мс. По скорости, данный кэш не уступает InProc кэшу.
Как было раньше?
Чтобы узнать, как было раньше, необходимо откатить SDK до версии 1.6 и провести небольшую модификацию Web.config. Заменить секцию dataCacheClients
на:
<dataCacheClients>
<dataCacheClient name="default">
<hosts>
<host name="[Ваш ID].cache.windows.net" cachePort="22233" />
</hosts>
<securityProperties mode="Message">
<messageSecurity authorizationInfo="[Ваш ключ]"></messageSecurity>
</securityProperties>
</dataCacheClient>
</dataCacheClients>
После компиляции проекта и открытия тестовой страницы видим следующие результаты:
Cache test
Before: 521 ms
6/10/2012 11:09:40 AM
After: 550 ms
Т.е. 29 мс на добавление записи в кэш и после обновления странички:
Cache test
Before: 501 ms
6/10/2012 11:09:40 AM
After: 533 ms
32 мс на извлечение записи из кэша.
Итог
Последнее нововведение Microsoft решило одну из главных проблем Windows Azure — проблему распределенного кэширования. Теперь использовать распределенный кэш можно, не опасаясь за падение производительности приложения, а те, кто использовал для этого другие средства внутри облака Azure, могут вновь вернуться к функционалу предоставляемому Windows Azure API.
Автор: gurfing