Исходники.Ру - Программирование
Исходники
Статьи
Книги и учебники
Скрипты
Новости RSS
Магазин программиста

Главная » Статьи по программированию » .NET - Все статьи »

Обсудить на форуме Обсудить на форуме

Использование Cache API

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

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

В ASP.NET кроме класса HttpApplication был введен дополнительный класс HttpCache специально для создания кеша объектов. Работа с этим классом очень похожа на работу с Application, но есть и существенные различия. В первую очередь эти различия направлены на решения указанных выше проблем:

  • Управление памятью – кеш может автоматически удалять объекты для небопущения переполнения памяти.
  • Уничтожение объектов при наступлении некоторого события – объект может быть помещен в кеш с правилами его удаления.
  • Callback вызовы – кеш поддерживает возможность вызова методов при удалении объектов из кеша.

Доступ к кешу осуществляется через свойство Cache текущего контекста веб приложения. Помещать объекты в кеш можно как неявно (так же, как и при работе с Application), так и с помощью методов Add и Insert. Работа данных методов совершенно идентична и отличается только в возвращаемых значениях (метод Add возвращает ссылку на добавленный в кеш объект).

Рассмотрим простейший пример использования кеша объектов. Полученный из источника данных результат запроса поместим в кеш и будем выводить этот результат на страницу. Также будем выводить информацию о том, откуда берется результат – из кеша или из источника данных. Ниже приведен код обработчика события Load страницы, выполняющий эти действия:

private void Page_Load(object sender, System.EventArgs e)
{
	DataSet ds;
	if(Cache["dataset"] == null)
	{
		SqlDataAdapter myData = new SqlDataAdapter("select * from authors", "server=.;database=pubs;" +
			"uid=sa;pwd=Manowar");
		ds = new DataSet();
		myData.Fill(ds);
		Cache["dataset"] = ds;
		lblInfo.Text = "Данные получены из БД";
	}
	else
	{
		ds = (DataSet) Cache["dataset"];
		lblInfo.Text = "Данные получены из кеша";
	}
	DataGrid1.DataSource = ds.Tables[0].DefaultView;
	DataGrid1.DataBind();
}

В данном примере работа с объектом Cache очень похожа на работу с Application. Но с одним существенным отличием – перед получением объекта из кеша необходимо ВСЕГДА проверять наличие объекта! Помещение объекта в кеш (пусть даже 2 секунды назад) не дает гарантии, что этот объект все еще есть там (в этом состоит важнейшее различие Cache и Application).

Неявное помещение объекта в кеш в приведенном примере идентично следующему вызову: Cache.Insert("dataset", ds). Но метод Insert имеет 4 перегруженных варианта, с помощью которых можно управлять правилами хранения объекта в кеше (ведь именно для этого и был создан кеш :)).

Рассмотрим следующий пример. На сайте электронного магазина на многих страницах выводится информация о лидерах продаж. Источником этой информации является XML файл, который может изменяться во многих местах. Так как доступ к этой информации нужен часто – лучше ее конечно же хранить в кеше. Ну а отслеживанием изменений в данных пусть тот же кеш и занимается – благо данная возможность была добавлена разработчиками из Microsoft :).

Для контроля неизменяемости файла используется класс System.Web.Caching.CacheDependency. Если необходимо добавить эту функциональность к объекту, помещаемому в кеш, нужно создать экземпляр класса CacheDependency и передать его в качестве параметра в метод Cache.Insert:

System.Web.Caching.CacheDependency dependency = new System.Web.Caching.Dependency(Server.MapPath(filename));
Cache.Insert("key", value, dependency);

Теперь можно вернуться и к реализации примера. Код данного примера приведен ниже:

private void Page_Load(object sender, System.EventArgs e)
{
	DataSet ds;
	if(Cache["dataset"] == null)
	{
		ds = new DataSet();
		ds.ReadXml(Server.MapPath("FileDependencies.xml"));
		System.Web.Caching.CacheDependency dependency = 
			new System.Web.Caching.CacheDependency(Server.MapPath("FileDependencies.xml"));
		Cache.Insert("dataset", ds, dependency);
		lblInfo.Text = "Данные получены из файла";
	}
	else
	{
		ds = (DataSet) Cache["dataset"];
		lblInfo.Text = "Данные получены из кеша";
	}
	DataGrid1.DataSource = ds.Tables[0].DefaultView;
	DataGrid1.DataBind();
}

private void btnAddValue_Click(object sender, System.EventArgs e)
{
	DataSet ds;
	if(Cache["dataset"] == null)
	{
		ds = new DataSet();
		ds.ReadXml(Server.MapPath("FileDependencies.xml"));
		Cache.Insert("dataset", ds, 
			new System.Web.Caching.CacheDependency(Server.MapPath("FileDependencies.xml")));
	}
	else
	{
		ds = (DataSet) Cache["dataset"];
	}
	ds.Tables[0].Rows.Add(new object[] {100, "Новый товар", 5000});
	ds.WriteXml(Server.MapPath("FileDependencies.xml"));
	Response.Redirect(Request.FilePath);
}

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

Класс System.Web.Caching.CacheDependency имеет 8 перегруженных конструкторов, с помощью которых можно указывать отслеживание изменений отдельного файла, каталога, списка файлов и каталогов, а также изменение других объектов в кеше (то есть объект может быть удален в зависимости от изменения значения другого объекта в кеше) и зависимость от другого объекта типа CacheDependency. Также для всех приведенных выше условий можно установить время, начиная с которого необходимо отслеживать эти изменения. Более подробную информацию обо всех перегрузках конструктора класса CahceDependency можно найти в MSDN.

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

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

private void Page_Load(object sender, System.EventArgs e)
{
	DataSet ds;
	if(Cache["dataset"] == null)
	{
		ds = new DataSet();
		ds.ReadXml(Server.MapPath("FileDependencies.xml"));
		Cache.Insert("dataset", ds, null, DateTime.Now.AddMinutes(5), 
			System.Web.Caching.Cache.NoSlidingExpiration); //1
//		Cache.Insert("dataset", ds, null, 
//			System.Web.Caching.Cache.NoAbsoluteExpiration, TimeSpan.FromMinutes(2)); //2
		lblInfo.Text = "Данные получены из файла";
	}
	else
	{
		ds = (DataSet) Cache["dataset"];
		lblInfo.Text = "Данные получены из кеша";
	}
	if(Cache["datetime"] == null)
	{
		System.Web.Caching.CacheDependency dependency = 
			new System.Web.Caching.CacheDependency(null, new string[] {"dataset"});
		Cache.Insert("datetime", DateTime.Now, dependency);
	}
	lblInfo.Text += "<br>Дата: " + Cache["datetime"].ToString();
	DataGrid1.DataSource = ds.Tables[0].DefaultView;
	DataGrid1.DataBind();
}

private void btnAddValue_Click(object sender, System.EventArgs e)
{
	DataSet ds = new DataSet();
	ds.ReadXml(Server.MapPath("FileDependencies.xml"));
	ds.Tables[0].Rows.Add(new object[] {100, "Новый товар", 5000});
	ds.WriteXml(Server.MapPath("FileDependencies.xml"));
	Response.Redirect(Request.FilePath);
}

В этом примере объект с данными помещается в кеш на 5 минут (если использовать строку 1) или на 2 минуты с момента последнего доступа к объекту (для строки 2). Также в этом коде показан пример использования зависимости одного объекта в кеше от другого (я сохраняю в кеше текущее время и устанавливаю зависимость этого объекта от объекта с данными). При изменении данных объект в кеше не будет удален до тех пор, пока не истечет время его хранения. При этом естественно возникает несогласованность данных, но, по условиям данной задачи, это допустимо. Данный способ кеширования данных (сохранение объекта в кеше на определенный промежуток времени) лучше всего подходит при кешировании объектов, хранящих данные из баз данных. К сожалению разработчики Microsoft так и не сделали возможность установки зависимости объектов в кеше от состояния данных в источнике данных, хотя это и анонсировалось.

Необходимо также упомянуть об одном важном ограничении в использовании установки зависимости хранения объекта от времени – для одного объекта можно использовать только один тип времени. При установке абсолютного времени хранения объекта в кеше второй параметр должен иметь значение System.Web.Caching.Cache.NoSlidingExpiration (или TimeSpan.Zero что суть одно и то же). Соответственно при использовании скользящего времени хранения объекта в кеше первый параметр должен быть равен System.Web.Caching.Cache.NoAbsoluteExpiration (или DateTime.MaxValue). При невыполнении этого условия будет сгенерировано исключение.

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

Для указания приоритета хранения объекта в кеше используется перечисление System.Web.Caching.CacheItemPriority, имеющее следующие значения (в порядке возрастания приоритета): Low, BelowNormal, Default, Normal, AboveNormal, High, NotRemovable. Объект, добавленный в кеш с приоритетом CacheItemPriority.NotRemovable, не будет удаляться при очистке кеша никогда. Последний параметр, который может принимать метод Insert при вставке объекта в кеш, имеет тип System.Web.Caching.CacheItemRemovedCallback. Этот параметр принимает в качестве значения делегата, вызываемого при удалении объекта из кеша. Вы можете создать обработчик например для оповещения приложения об удалении объекта из кеша.

Как и в прошлых примерах приводится только интересующий нас код:

private void Page_Load(object sender, System.EventArgs e)
{
	DataSet ds;
	if(Cache["dataset"] == null)
	{
		ds = new DataSet();
		ds.ReadXml(Server.MapPath("FileDependencies.xml"));
		Cache.Insert("dataset", ds, null, DateTime.Now.AddMinutes(1), 
			System.Web.Caching.Cache.NoSlidingExpiration,
			System.Web.Caching.CacheItemPriority.BelowNormal, 
			new System.Web.Caching.CacheItemRemovedCallback(this.CacheItemRemoved));
		lblInfo.Text = "Данные получены из файла";
	}
	else
	{
		ds = (DataSet) Cache["dataset"];
		lblInfo.Text += "<br>Данные получены из кеша";
	}
	DataGrid1.DataSource = ds.Tables[0].DefaultView;
	DataGrid1.DataBind();
}

private void CacheItemRemoved(string key, object val, System.Web.Caching.CacheItemRemovedReason reason)
{
	itemRemoved = true;
	this.reason = reason;
}

Не всегда удобно дожидаться момента удаления объекта из кеша по добавленным правилам. Например при довольно большом обновлении базы данных хорошо было бы также удалить из кеша объекты, хранящие старую информацию. Для удаления объектов из кеша предназначен метод Remove, принимающий один параметр – имя объекта в кеше.

Cache.Remove("dataset");

Последняя функциональность класса System.Web.Caching.Cache, на которую хотелось бы обратить ваше внимание, это реализация интерфейса IEnumerable, что позволяет быстро и удобно получать доступ ко всем объектам, хранящимся в кеше приложения.

private void Page_Load(object sender, System.EventArgs e)
{
	string strCacheContents;
	string strName;

	strCacheContents = "Содержимое кеша:<br>";
	foreach(DictionaryEntry objItem in Cache)
	{
		strName = objItem.Key.ToString();
		strCacheContents = strName + " = " + Cache[strName].ToString() + "<br>";
	}
	lblInfo.Text = strCacheContents;
}

Подводя итоги всему вышесказанному можно сказать следующее - правильное использование Cache API позволяет повысить быстродействие веб приложения (во многих случаях - в разы). Cache API представляет удобный интерфейс для хранения данных в кеше вместе с информацией о времени хранения объектов. Кешируя редкоизменяемые данные из источника данных можно резко снизить нагрузку на сервера баз данных и повысить скорость отклика. Но как и в других подобных случаях использованием Cache API не следует злоупотреблять.


Текст примеров данной статьи можно выкачать здесь


Может пригодится:


Автор: Manowar
Прочитано: 2750
Рейтинг:
Оценить: 1 2 3 4 5

Комментарии: (0)

Добавить комментарий
Ваше имя*:
Ваш email:
URL Вашего сайта:
Ваш комментарий*:
Код безопастности*:

Рассылка новостей
Рейтинги
© 2007, Программирование Исходники.Ру