Одноранговый сервер состояний ASP.NET

ОГЛАВЛЕНИЕ

Данная статья раскрывает распределенную реализацияю службы состояний ASP.NET

Скачать исходники - 73.4 Кб

Введение

Веб-разработчики ASP.NET имеют три встроенных средства для сохранения состояния сеанса, а именно: внутрипроцессная память, сервер SQL и сервер состояний.

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

Сервер SQL – средство хранения состояния сеанса вне процесса, работающее с пулом веб-серверов. Он хранит данные сеанса в базе данных сервера SQL. Это самый надежный вариант, но наименее быстродействующий. Главная проблема данного варианта в том, что очень часто разработчики хотят кэшировать данные, извлеченные из базы данных в состоянии сеанса, чтобы уменьшить поиск по базе данных. Состояние сеанса сервера SQL разрушает эту цель, так как кэширование в базе данных извлеченных из нее же данных не повышает быстродействие.

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

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

Данная проблема возникает даже в постоянных («липких») балансировщиках нагрузки ошибочно или из-за сбоя сервера.
В-третьих, проблема, о которой многие разработчики не знают, состоит в том, что веб-сервер и сервер состояний общаются на незашифрованном тексте. Перехватчик легко может захватить данные о состоянии сеанса, перемещающиеся по сети. Это может не быть угрозой, если все серверы работают во внутренней сети, но, безусловно, является основанием для тревоги, когда веб-серверы и серверы состояний разбросаны по интернету.

Одноранговый сервер состояний ASP.NET, представленный в данной статье, решает вышеуказанные проблемы, в то же время явно заменяя сервер состояний, предоставленный Microsoft.

Краткое описание

В основе однорангового сервера состояний лежит простая идея – позволить серверам состояний в сети безопасно общаться и при необходимости передавать друг другу данные о состоянии сеанса, как показано ниже.

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

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

Одноранговый сервер состояний полностью совместим с предыдущими версиями сервера состояний, предоставленного Microsoft, и включает в себя все упомянутые ранее преимущества.


 

Установка

Чтобы скомпилировать и установить сервер состояний:

  1. Скачайте файл с исходным кодом.
  2. Откройте решение в visual studio. (Visual Studio 2008 откроет “Мастер” преобразования. Выполните “Мастер”.)

    Сервер состояний появляется в двух видах. Один работает как консольное приложение, а другой работает как служба windows. Проект StateService компилируется как служба windows и может быть установлен и удален с помощью файлов install_service.bat и uninstall_service.bat. Проект ConsoleServer запускает службу как консольное приложение, которое намного проще тестировать и отлаживать. Оба проекта разделяют один и тот же исходный код и функционируют одинаково.
  3. Откройте окно свойств для проекта, который вы хотите скомпоновать.
  4. a. При использовании Visual Studio 2005 добавьте NET20 в поле символов условной компиляции вкладки “Компоновка”.
    b. При использовании Visual Studio 2008 выберите .NET Framework 3.5 в поле “Целевой каркас” вкладки “Приложение”.
  5. Скомпонуйте проект.
  6. Если вы скомпоновали проект StateService, перейдите в выходную папку и запустите install_service.bat, чтобы установить службу.
  7. Если на вашем компьютере уже работает служба состояний Microsoft, остановите ее.
  8. Если вы скомпоновали и установили службу windows, запустите “Одноранговая служба состояний” в списке Службы. Если вы скомпоновали консольный сервер, запустите ConsoleServer.exe или просто запустите отладку из Visual Studio.
  9. Теперь вы можете тестировать и запускать любые имеющиеся у вас веб-приложения с помощью работающего сервера состояний.

Чтобы добавить одноранговые серверы:

  1. Скопируйте скомпилированный исполняемый файл и файл конфигурации приложения на другой компьютер в вашей сети.
  2. Откройте файл конфигурации и добавьте новый одноранговый узел сети в раздел <Peers(одноранговые узлы сети)>. Например, чтобы настроить сервер состояний для подключения к другому серверу состояний, работающему на компьютере по имени SV3 с номером однорангового порта 42425, добавьте <add key="MyPeer"value="pc2:42425"/>в раздел <Peers>.
  3. Вы можете запустить сервер состояний на компьютере, и он соединится с другим сервером(ами) состояний в сети.
  4. Установка сети в любой нужной вам топологии остается на ваше усмотрение. Например, рассмотрим сеть из трех серверов состояний, показанную ниже, каждый сервер состояний на каждом компьютере будет иметь показанную ниже конфигурацию:

Вы можете запустить несколько одноранговых консольных серверов на одном компьютере, но каждый консольный сервер должен иметь уникальный установленный порт веб-сервера и одноранговый порт.

Как работает сервер состояний

Microsoft предоставляет сервер состояний, работающий, как показано ниже.

Одноранговый сервер состояний работает точно так, как показано выше, за исключением случаев, когда сервер состояний не имеет запрошенного состояния сеанса, в случае чего он запрашивает состояние сеанса из сети перед тем, как ответить, как показано ниже:

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


 Пояснения реализации

Различные методы программирования применяются для реализации разных аспектов сервера состояний. Некоторые из значительных аспектов освещены ниже.

Платформа

Сервер состояний написан на C# 2.0, но нацелен на каркас NET 3.5, чтобы использовать преимущества класса ReaderWriterLockSlim. Если задан символ NET20, сервер использует вместо этого более медленный класс ReaderWriterLock и может быть нацелен на каркас .NET 2.0.

Протокол

Чтобы создать сервер состояний, способный явно заменить сервер состояний, необходимо получить и понять полную спецификацию протокола связи между веб-сервером и сервером состояний, предоставленным Microsoft. Действия, предпринятые для составления протокола, зафиксированы в обратном хронологическом порядке в http://ahuwanya.net/blog/category/Peer-to-Peer-Session-State-Service.aspx

Передача сообщений

Сервер в основном управляется сообщениями. Подсистема передачи сообщений показана ниже:

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

Если накопленные данные – полное сообщение HTTP, данные отправляются объекту MessageFactory (фабрика сообщений). Объект MessageFactory изучает данные, чтобы определить, какой подходящий экземпляр класса-потомка ServiceMessage (служебное сообщение) нужно создать. Создается экземпляр класса-потомка ServiceMessage, и его реализация метода Process вызывается для обработки сообщения.

Обработка параллельности

Пессимистический механизм параллельности используется во время доступа к состоянию сеанса в словаре сеанса, определенном классом SessionDictionary. Фрагмент состояния сеанса может считываться или изменяться только одним потоком одновременно. Поток объявляет монопольный доступ для производства действий над фрагментом состояния сеанса путем установки свойства IsInUse в истину. Это делается путем вызова метода элементарного сравнения  и перестановки CompareExchangeInUse (обертка метода .NET Interlocked.CompareExchange, производящего действия над свойством IsInUse). Установка данного свойства в истину извещает остальные потоки, что другой поток работает с указанным состоянием сеанса.

Если другой поток хочет получить доступ к тому же самому состоянию сеанса и попытается объявить монопольный доступ, попытка не удастся, так как другой поток уже имеет монопольный доступ. Поток продолжит пытаться получить монопольный доступ, и в итоге получит его, когда другой поток освободит доступ. Это работает довольно хорошо, так как большую часть времени только один поток должен иметь доступ к состоянию сеанса, и так как большинство операций над состоянием сеанса выполняются очень быстро. Операция экспорта (передачи), занимающая намного больше времени, обрабатывается с помощью несколько другого механизма и освещается ниже -  в разделе “Управление состязанием”.

Таймеры – или их отсутствие

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

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

Диагностика

Класс Diags применяется для слежения за сообщениями, записи действий сервера и обнаружения взаимоблокировок. Методы в классе Diags условные и не будут компилироваться в коде конфигурации выпуска.  Символ VERBOSE (словесный) может быть задан для просмотра или записи всех действий, происходящих на сервере. Это особенно полезно для консольного сервера, выводящего свои данные в консольное окно. Если символ VERBOSE не задан, отображается только важная информация или неожиданные ошибки.

Защита

Предоставленный Microsoft сервер состояний передает и получает нешифрованные данные на и от веб-сервера. Скорее всего, это было сделано ради быстродействия. Для обеспечения совместимости с предоставленным Microsoft сервером состояний одноранговый сервер состояний передает нешифрованные данные веб-серверу. Однако одноранговый сервер состояний может быть настроен на передачу зашифрованных данных между одноранговыми узлами. Это эффективно предотвращает атаки по перехвату данных в сети, если веб-сервер и связанные с ними серверы состояний установлены на одном и том же компьютере или в защищенной сети.

Например, рассмотрим конфигурацию веб-сервер – сервер состояний Microsoft, показанную ниже.

Два веб-сервера подключаются через открытый интернет, чтобы получить доступ к серверу состояний.
Используя одноранговые серверы состояний, сеть можно защитить, включив в нее веб-серверы, имеющие свои собственные локальные серверы состояний, безопасно подключающиеся к удаленному серверу состояний от их имени, как показано ниже:

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

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

Топологии сети

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

Показанная выше сеть A – кольцевая сеть одноранговых серверов состояний, которые по отдельности соединены с веб-серверами, в то время как сеть B - кольцевая сеть компьютеров, имеющих подключенные и работающие серверы состояний и веб-сервер. Существующие изолированные сети серверов состояний Microsoft  могут быть обновлены, чтобы сформировать большую одноранговую сеть, заменив серверы состояний Microsoft одноранговыми серверами состояний и соединив их, как показано на рисунке сети A. Сеть B использует защитное противодействие, упомянутое ранее, и является несколько более масштабируемой, так как любой узел в сети – веб-сервер и одноранговый сервер состояний.

Обе сети продолжат функционировать, если один узел откажет, в отличие от шинной сети, но чем больше узлов добавляется в сеть, тем больше времени требуется на прохождение сообщения через сеть.

Сеть C - сеть типа "звезда". Преимущество обладания сетью типа “звезда” в том, что независимо от количества новых узлов, добавленных в кольцевую сеть, сообщение доходит до любого узла в сети всего за два перехода (прыжка).
Сеть D – сеть из трех звездообразных сетей, образующая большую звездообразную сеть. Сообщение проходит через такую сеть также за меньшее число переходов. Обе сети страдают от недостатка: если центральный узел ломается, вся сеть ломается.

Путем соединения краевых узлов в сети D, в  сети E формируется частичная ячеистая сеть. Сеть E – продуманная комбинация кольцевой сети и звездообразной сети. Если центральный узел ломается, сеть продолжает функционировать, и сообщение проходит через сеть за меньшее число переходов, чем через кольцевую сеть.

Как показано, топология сети одноранговых серверов состояний ограничена лишь воображением проектировщика сети.

Интересные сценарии

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

Передачи при отключении

Чтобы гарантировать, что данные сеанса не пропадут во время отключения сервера, сервер состояний приступает к передаче всех своих данных состояния сеанса соединенным с ним одноранговым узлам циклическим образом, когда запускается отключение сервера.

Повторные передачи

Запрос сеанса в сети может точно не попасть в узел, хранящий сеанс, если он передает его, как показано ниже.

Как показано выше, узел 1 запрашивает сеанс A из сети почти в тот же момент, когда узел 4 хочет передать сеанс узлу 2.
Когда сообщение от узла 1 доходит до узла 2, узел 2 пересылает сообщение узлу 3, так как у него нет сеанса.
Когда сообщение доходит до узла 3, начинается передача сеанса между узлами 4 и 2, и к тому моменту, когда сообщение доходит до узла 4, передача завершена, и узел 4 больше не хранит сеанс и пересылает сообщение узлу 5.

Таким образом, сообщение проходит через сеть, не достигая ни одного узла с искомым сеансом, хотя сеанс существует в сети.

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

Здесь узел 4 повторно передает сообщение, чтобы оно прошло обратно по пути, по которому оно пришло, и в итоге достигло бы узла 2, хранящего сеанс.

Вновь переданные сообщения – копии исходного сообщения, кроме того, что они имеют другой заголовок Идентификатор передачи, который одноранговые узлы используют для определения того, что это другая передача.


 Управление состязанием

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

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

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

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

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

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

Также организованная группа злонамеренных пользователей (или ботнет) может вызвать данный сценарий даже в правильно работающих  разделителях состояний и балансировщиках нагрузки.

Каждый сервер состояний имеет запросы, ожидающие обработки. Например, если очень востребованный сеанс находится на сервере состояний 3, запросы к данному серверу состояний будут очень быстро обрабатываться поочередно.
Серверы состояний 1 и 2 запускают передачи, запрашивающие передачу сессии. Сообщение в итоге доходит до сервера состояний 3, и запрос передается, например, серверу состояний 2. Запросы к серверу состояний 3, которые не были обработаны, будут ждать до момента завершения передачи.

После завершения передачи сеанса на сервер 2 обрабатываются запросы к серверу 2, в то время как запросы к серверу 3 запускают передачи, запрашивающие сеанс.

Передача, запущенная сервером состояний 1, достигает сервера состояний 2, и сеанс передается на сервер состояний 1. Это происходит непрерывно, и серверы продолжают передавать сеанс друг другу, в то время как большинство запросов ждут, так как даже когда сеанс передается, сервер состояний может обработать лишь несколько запросов до того, как сеанс передастся другому серверу состояний.

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

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

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

Если поступит много таких запросов, они быстро израсходуют все ресурсы процессора, и сервер остановится.

Хотя, может быть, невозможно помешать любой группе пользователей завалить сервер состояний запросами, сервер состояний защищается от подозрительных сеансов с помощью следующего принципа: любое ухудшение обслуживания из-за подозрительного сеанса должно главным образом влиять на пользователя данного сеанса, и достигает этой цели с помощью следующих алгоритмов:

  1. Когда запрос подлежит обработке, и сервер обнаруживает передаваемый сеанс, запрос перестает обрабатываться и ставится в очередь на обработку, когда передача завершится. Это не позволяет запросу пожирать циклы процессора во время ожидания, и освобождает ресурсы, чтобы другие запросы от других пользователей могли быть обработаны. Если число запросов в очереди, ожидающих передачи сеанса, слишком большое, то все эти сообщения отвергаются, так как свидетельствуют о подозрительном сеансе, и сервер не должен утруждаться их обработкой.
  2. После того как передача завершена, и поставленный в очередь запрос готов к повторной обработке, и сервер обнаруживает, что тот же самый сеанс снова передается в другом запросе, то этот запрос будет отвергнут и не станет обрабатываться, так как свидетельствует о крайней сомнительности сеанса.
  3. Перед тем как запрос пытается запросить сеанс из сети (путем широковещания), он проверяет, ждет ли он ответа на предыдущий запрос этого же сеанса, и если да,то запрос ставится в очередь в список запросов, которые будут обработаны, когда запрос будет получен. Это сокращает число сообщений GetTransferMessage, генерируемых в сети, что в свою очередь сокращает ненужные повторные передачи и поиски. Если число запросов в очереди, ожидающих прихода сеанса, слишком большое, то все эти запросы отвергаются, так как служат признаком подозрительного сеанса.
  4. Наконец, все входящие запросы помещаются в очередь с их конкретным идентификатором сеанса, и процессор обработки сообщений циклически опрашивает очереди входящих запросов и обрабатывает их один за другим, как показано ниже:


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


 Заключение

Одноранговый сервер состояний полностью совместим с прежними версиями предоставленного Microsoft сервера состояний и может явно заменить его. Одноранговые серверы состояний могут передавать сеансы друг другу, повышая надежность веб-приложений, зависящих от состояния сеанса. Одноранговые серверы состояний также служат слоем защиты, защищающим данные сеанса в сети.

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