Освобождение ресурсов в .NET приложениях

У C# есть одно очень большое преимущество и в то же время большой недостаток – автоматическая сборка мусора. При классических Desktop приложениях это прекрасно, когда платформа за нас убирает весь мусор и освобождает память, но в Web, это далеко не всегда так хорошо.

Когда мы программируем Web приложение, то Web запросы в основном короткие и наш код должен выполнять небольшую операцию и работать максимально быстро. Если не помогать сборщику мусора, то ресурсы сервера могут улететь в трубу просто мгновенно.

Первое, что улетает в трубу – это соединения к серверу MS SQL Server. В .NET мы можем создать соединение SqlConnection, открыть его и не закрывать, потому что .NET сборщик мусора сделает все за нас. Я уже несколько раз видел Web код, когда программисты так и поступали. Дело в том, что они открывают соединение в одном месте, а используют в другом, и чтобы не открывать соединение дважды за один запрос, его открывают где-то вначале, и не закрывают, в надежде на то, что .NET сделает это за нас.

Хорошо, вот открыли мы соединение с базой данных:

public ActionResult Index() 
{
  SqlConnection connection = new SqlConnection();
  connection.ConnectionString = "строка соединения";
  connection.Open();
  
  . . .
  . . .

  return View();
}

Внимание вопрос – когда будет закрыто соединение с базой данных? Да, соединение будет закрыто за нас, и нам не нужно по идее заботится о ресурсах, но вот когда эти ресурсы будут освобождены? А фиг его знает. После выполнения этого кода соединение будет все еще открытым и занятым. Реальное освобождение ресурсов может произойти через минуту, а может через пять. Это значит, что на сервере будет куча открытых ресурсов запрещенных к использованию. Сервер не безграничен в количестве одновременно доступных соединений, тут все зависит от настроек и когда вы дойдете до максимума, новое соединение открыть будет невозможно и сайт упадет до тех пор, пока сборщик мусора не освободит не используемые соединения.

Внимание, тут я использовал два очень важных слова – открытые и занятые соединения. Это две разные вещи. Давайте посмотрим на цикл жизни соединения. Когда вы впервые открываете (вызываете метод Open) объекта SqlConnection, то .NET делает следующее:

  • - выделяет необходимые объекту ресурсы
  • - устанавливает соединение с базой данных

После этого соединение открыто и занято вашим объектом. Когда вы вызываете метод Close или Dispose, то .NET объект SqlConnection уничтожается, а открытое соединение остается в живых на некоторое время и оно находится в специальном пуле. Соединение все еще открыто, но оно свободно для использования и может быть привязано в любому другому объекту SqlConnection.

Теперь, если вы попытаетесь открыть новый объект SqlConnection, то .NET проверит пул на наличие уже открытых соединение с такой же строкой подключения. Если они есть, то будет создан новый объект, но новое физическое соединение с сервером устанавливаться не будет, будет использовано существующее из пула. Таким образом экономится время на обмен приветственными сообщениями с базой данных, а объект SqlConnection практически мгновенно становится доступным к использованию.

Я сопровождаю сайт с миллионами пользователей и у нас даже в час пик, количество одновременно открытых соединений к базе данных не превышает 600. А вне часы пик, держится на отметке в 200 соединений. Недавно мы запускали небольшое обновление и вне часы пик количество соединений взлетело до 1500, а в часы пик сайт упал из-за того, что не хватило свободного места в пуле. Мы увидели косяк как раз тогда, когда сайт упал из-за недостаточного количества соединений. Сразу после обновления как-то не проверили, сколько открытых соединений.

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

При разработке Web приложений, если где-то нужно открывать ресурсы, всегда используйте конструкцию using:

public ActionResult Index() 
{
  using (SqlConnection connection = new SqlConnection())
  {
   connection.ConnectionString = "строка соединения";
   connection.Open();
  
   . . .
   . . .
  }

  return View();
}

В этом случае .NET знает, что соединение нам нужно только на период, пока мы находимся внутри using блока. Как только мы выходим за его пределы, платформа сразу видит, что этот ресурс нам не нужен и его можно освобождать. И тут мы точно можем знать (если программисты Microsoft не налажают), то соединение с базой данных будет закрыто сразу после выхода из блока using. Вам не обязательно вызывать явно метод Close, достаточно просто использовать using.

Сборка мусора в .NET работает отлично, главное правильно ей пользоваться и помогать платформе указывать на то, когда ресурсы уже больше не нужны. Дальше она все возьмет на себя.

А как насчет такого случая, когда нужно использовать соединение в разных местах программы для выполнения двух разных запросах в двух разных местах? Если using не может объять оба кода (они находятся в разных методах), то не стоит даже пытаться объять необъятное. Создайте два объекта SqlConnection и откройте соединение дважды. Это не проблема для сервера, потому что он может использовать Connection Pool. Сразу же после закрытия первого соединения, оно реально не будет закрыто. Будет уничтожен объект и соединение будет помечено как свободное. В течении некоторого времени (легко конфигурируется) соединение будет оставаться открытым и при создании следующего объекта SqlConnection, не нужно тратить время на установку соединения с сервером, можно использовать уже открытое из пула.

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

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



Внимание!!! Если ты копируешь эту статью себе на сайт, то оставляй ссылку непосредственно на эту страницу. Спасибо за понимание

Комментарии

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

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

О блоге

Программист, автор нескольких книг серии глазами хакера и просто блогер. Интересуюсь безопасностью, хотя хакером себя не считаю

Обратная связь

Без проблем вступаю в неразборчивые разговоры по e-mail. Стараюсь отвечать на письма всех читателей вне зависимости от страны проживания, вероисповедания, на русском или английском языке.

Пишите мне