Поиск на сайте: Расширенный поиск


Новые программы oszone.net Читать ленту новостей RSS
CheckBootSpeed - это диагностический пакет на основе скриптов PowerShell, создающий отчет о скорости загрузки Windows 7 ...
Вы когда-нибудь хотели создать установочный диск Windows, который бы автоматически установил систему, не задавая вопросо...
Если после установки Windows XP у вас перестала загружаться Windows Vista или Windows 7, вам необходимо восстановить заг...
Программа подготовки документов и ведения учетных и отчетных данных по командировкам. Используются формы, утвержденные п...
Red Button – это мощная утилита для оптимизации и очистки всех актуальных клиентских версий операционной системы Windows...
OSzone.net Microsoft Разработка приложений Облако/Azure Azure Web Sites Масштабирование веб-приложения с помощью Azure Web Sites RSS

Масштабирование веб-приложения с помощью Azure Web Sites

Текущий рейтинг: 0 (проголосовало 0)
 Посетителей: 809 | Просмотров: 969 (сегодня 0)  Шрифт: - +

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

В этот момент самое время заняться масштабированием вашего веб-приложения и предоставить ему дополнительные вычислительные ресурсы, больше памяти или более мощную серверную часть базы данных. Наиболее распространенная форма масштабирования в облаке — горизонтальная, т. е. добавление вычислительных экземпляров, которые позволяют выполнять веб-приложение одновременно на нескольких веб-серверах (экземплярах). Такие облачные платформы, как Microsoft Azure, дают возможность легко масштабировать нижележащую инфраструктуру, которая поддерживает ваше веб-приложение, предоставляя ему любое количество веб-серверов в виде виртуальных машин (VM) по мере необходимости. Однако, если ваше веб-приложение не рассчитано на масштабирование и выполнение в нескольких экземплярах, оно не сможет использовать преимущества дополнительных ресурсов и не даст ожидаемых результатов.

В этой статье рассматриваются ключевые концепции и шаблоны проектирования веб-приложений с расчетом на масштабирование. Детали реализации и примеры ориентированы на веб-приложения, выполняемые в Microsoft Azure Web Sites.

Но для начала важно отметить, что масштабирование веб-приложения очень сильно зависит от контекста и архитектуры. Используемое в этой статье веб-приложение весьма простое, тем не менее оно затрагивает фундаментальные основы масштабирования веб-приложения, особенно при выполнении в Azure Web Sites.

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

Этап 1: знакомимся с приложением

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

Приложение, которое я буду использовать в этой статье, — WebMatrix Photo Gallery Template for ASP.NET Web Pages (bit.ly/1llAJdQ). Этот шаблон является отличным способом изучить, как применять ASP.NET Web Pages для создания реальных веб-приложений. Это полностью функциональное веб-приложение, позволяющее создавать фотоальбомы и загружать изображения. Изображения может просматривать кто угодно, но оставлять комментарии разрешается только зарегистрированным пользователям. Веб-приложение Photo Gallery можно развернуть в Azure Web Sites из WebMatrix или напрямую с портала Azure Portal через Azure Web Sites Gallery.

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

Рассмотрим каждое из этих ограничений в деталях.

Файл PhotoGallery.sdf, содержащийся в папке App_Data, является базой данных SQL Server Express по умолчанию, которая распространяется вместе с приложением. SQL Server Express упрощает разработку приложения на начальном уровне и служит отличным инструментом для обучения, но накладывает серьезные ограничения на способность приложения к масштабированию. База данных SQL Server Express — это, по сути, файл в файловой системе. Приложение Photo Gallery в своем текущем состоянии нельзя безопасно масштабировать в нескольких экземплярах. Попытка такого масштабирования может привести к появлению у вас нескольких экземпляров файла базы данных SQL Server Express, каждый из которых будет локальным файлом, скорее всего не синхронизированным с остальными. Даже если все экземпляры веб-сервера используют одну и ту же файловую систему, файл SQL Server Express может быть блокирован каким-либо одним экземпляром в тот или иной момент, что приведет к сбою остальных экземпляров.

Приложение Photo Gallery также ограничено в том, как оно управляет состоянием сеанса пользователя. Сеанс определяется как серия запросов, выданных одним и тем же пользователем в течение некоего периода, и управляется сопоставлением идентификатора сеанса с каждым уникальным пользователем. Идентификатор используется для каждого последующего HTTP-запроса и предоставляется клиентом либо в файле cookie, либо как специальный фрагмент URL запроса. Данные сеанса хранятся на серверной стороне в одном из поддерживаемых хранилищ состояний сеансов, т. е. в памяти процесса, базе данных SQL Server или ASP.NET State Server.

Приложение Photo Gallery применяет WebMatrix-класс WebSecurity для управления логином пользователя и состоянием, а WebSecurity использует провайдер членства в группах ASP.NET. По умолчанию этот провайдер применяет внутрипроцессный (InProc) режим состояния сеансов. В этом режиме переменные и значения состояния сеанса хранятся в памяти локального экземпляра веб-сервера (VM). Это ограничивает возможности приложения в выполнении в нескольких экземплярах, поскольку последующие HTTP-запросы от одного пользователя могут завершаться в разных экземплярах веб-серверов. Так как каждый экземпляр веб-сервера хранит свою копию состояния в собственной локальной памяти, может получиться так, что у вас будут разные InProc-объекты состояния сеанса в разных экземплярах для одного и того же пользователя. А это может дать непредсказуемые результаты. Ниже показан класс WebSecurity, применяемый для управления состоянием пользователя:

_AppStart.cshtml

@{
  WebSecurity.InitializeDatabaseConnection
    ("PhotoGallery", "UserProfiles", "UserId", "Email", true);
}

Upload.cshtml

@{
  WebSecurity.RequireAuthenticatedUser();
    ...
...
}

Класс WebSecurity является вспомогательным; это компонент, который упрощает программирование в ASP.NET Web Pages. «За кулисами» класс WebSecurity взаимодействует с провайдером членства в группах ASP.NET, который в свою очередь делает более низкоуровневую работу, необходимую для выполнения связанных с безопасностью задач. Провайдер членства в группах по умолчанию в ASP.NET Web Pages — класс SimpleMembershipProvider, и по умолчанию его режим состояний сеансов — InProc.

Наконец, текущая версия веб-приложения Photo Gallery хранит фотографии в базе данных, где каждый снимок представлен массивом байтов. Поскольку приложение использует SQL Server Express, снимки сохраняются на локальном диске. Для приложения фотогалереи один из основных сценариев — просмотр снимков, поэтому приложению может понадобиться обрабатывать множество запросов и отображать массу фотографий. Чтение фотографий из базы данных — вариант, далекий от идеального. Даже применение более изощренной базы данных, такой как SQL Server или Azure SQL Database, не идеально главным образом потому, что извлечение фотографий является дорогостоящей операцией.

Итак, эта версия Photo Gallery — приложение с состояниями, а такие приложения плохо масштабируются в нескольких экземплярах.

Этап 2: модифицируем Photo Gallery в веб-приложение без состояний

Теперь, объяснив некоторые из ограничений приложения Photo Gallery в отношении масштабируемости, я устраню их по очереди, чтобы расширить возможности приложения в масштабировании. На этом этапе я внесу необходимые изменения для преобразования Photo Gallery из приложения с состояниями в приложение без состояний. В конце второго этапа обновленное приложение Photo Gallery будет безопасно масштабироваться и выполняться в нескольких экземплярах веб-сервера (VM).

Сначала я заменю SQL Server Express на более мощный сервер базы данных — на Azure SQL Database, облачный сервис от Microsoft. Azure SQL Database Standard и Premium предлагают расширенные средства для бизнеса, которые я задействую на этапе 4. А пока я просто перенесу базу данных с SQL Server Express в Azure SQL Database. Это легко сделать с помощью утилиты миграции базы данных WebMatrix или любой другой подходящей утилиты, которая преобразует SDF-файл в формат Azure SQL Database.

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

Первым делом я сменю тип столбца ID в некоторых таблицах (Galleries, Photos, UserProfiles и др.) с INT на GUID. Это изменение будет полезным на этапе 4, когда я обновлю приложение так, чтобы оно могло работать между несколькими регионами; в этом случае приложению потребуется поддерживать базу данных и контент в синхронизированном состоянии. Важно отметить, что такая модификация не требует изменений в самом коде приложения; все SQL-запросы в приложении останутся прежними.

Затем я откажусь от хранения снимков как байтовых массивов в базе данных. Это включает изменение как схемы, так и кода. Я удалю из таблицы Photos столбцы FileContents и FileSize, буду хранить снимки напрямую на диске и использовать идентификатор фотографии, каковым теперь является GUID, чтобы различать снимки.

В следующем фрагменте кода показано выражение INSERT до изменения (заметьте, что и fileBytes, и fileBytes.Length хранятся прямо в базе данных):

db.Execute(@"INSERT INTO Photos
  (Id, GalleryId, UserName, Description, FileTitle, FileExtension,
  ContentType, FileSize, UploadDate, FileContents, Likes)
  VALUES (@0, @1, @2, @3, @4, @5, @6, @7, @8, @9, @10)",
  guid.ToString(), galleryId, Request.GetCurrentUser(Response), "",
  fileTitle, fileExtension, fileUpload.ImageFormat, fileBytes.Length,
  DateTime.Now, fileBytes, 0);

А вот код после изменений в базе данных:

using (var db = Database.Open("PhotoGallery"))
{
  db.Execute(@"INSERT INTO Photos
  (Id, GalleryId, UserName, Description, FileTitle, FileExtension,
  UploadDate, Likes)
  VALUES (@0, @1, @2, @3, @4, @5, @6, @7)", imageId, galleryId,
  userName, "", imageId, extension,
  DateTime.UtcNow, 0);
}

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

Последнее изменение, которое я внесу на этапе 2, — откажусь от использования InProc-состояния сеанса. Как отмечалось ранее, WebSecurity является вспомогательным классом, который взаимодействует с провайдерами ASP.NET членства в группах. По умолчанию режим состояния сеанса ASP.NET SimpleMembership — InProc. Есть несколько внепроцессных вариантов, применимых с SimpleMembership, в том числе SQL Server и сервис ASP.NET State Server. Эти два варианта обеспечивают совместное использование состояния сеанса между несколькими экземплярами веб-сервера и предотвращают привязку к определенному серверу (server affinity), т. е. не требуют связывать сеанс с одним конкретным веб-сервером.

Мой подход также позволяет управлять состоянием вне процесса, а именно: использовать базу данных и файл cookie. Однако я полагаюсь на свою реализацию, а не на ASP.NET в основном потому, что мне нежелательно что-либо усложнять. Эта реализация оперирует с cookie и хранит идентификатор сеанса и его состояние в базе данных. При входе пользователя я назначаю новый GUID в качестве идентификатора сеанса, сохраняемый в базе данных. Этот GUID также возвращается пользователю в виде cookie. В следующем фрагменте кода показан метод CreateNewUser, вызываемый при каждом входе пользователя:

private static string CreateNewUser()
{
  var newUser = Guid.NewGuid();
  var db = Database.Open("PhotoGallery");
db.Execute(@"INSERT INTO GuidUsers (UserName, TotalLikes) VALUES (@0, @1)",
  newUser.ToString(), 0);
  return newUser.ToString();
}

При ответе на HTTP-запрос GUID встраивается в HTTP-ответ как cookie. Имя пользователя, переданное в метод AddUser, является результатом только что показанной функции CreateNewUser:

public static class ResponseExtensions
{
  public static void AddUser(this HttpResponseBase response,
    string userName)
  {
    var userCookie = new HttpCookie("GuidUser")
    {
      Value = userName,
      Expires = DateTime.UtcNow.AddYears(1)
    };
    response.Cookies.Add(userCookie);
  }
}

При обработке входящего HTTP-запроса я сначала пытаюсь извлечь идентификатор пользователя, представленный GUID, из GuidUser cookie. Затем я ищу такой userID (GUID) в базе данных и получаю любую информацию, специфичную для этого пользователя. На рис. 1 показана часть реализации GetCurrentUser.

Рис. 1. GetCurrentUser

public static string GetCurrentUser(this HttpRequestBase request,
   HttpResponseBase response = null)
  {
    string userName;
    try
    {
      if (request.Cookies["GuidUser"] != null)
        {
          userName = request.Cookies["GuidUser"].Value;
          var db = Database.Open("PhotoGallery");
          var guidUser = db.QuerySingle(
            "SELECT * FROM GuidUsers WHERE UserName = @0", userName);
          if (guidUser == null || guidUser.TotalLikes > 5)
            {
              userName = CreateNewUser();
            }
        }
      ...
      ...
}

CreateNewUser и GetCurrentUser являются частью класса RequestExtensions. Аналогично AddUser — часть класса ResponseExtensions. Оба класса включаются в конвейер ASP.NET, обрабатывая соответственно запросы и ответы.

Мой подход к управлению состоянием сеанса довольно наивный, так как он не обеспечивает защиту и не вводит никакой аутентификации. Однако он демонстрирует преимущество управления сеансами вне процесса и масштабируется. Реализуя собственное управление состоянием сеанса — на основе ASP.NET или без нее, убедитесь, что вы используете безопасное решение, которое включает аутентификацию и защищенный способ шифрования возвращаемых вами cookie.

В этот момент я могу спокойно заявить, что обновленное приложение Photo Gallery теперь является веб-приложением без состояний. Заменив локальную реализацию базы данных SQL Server Express на Azure SQL Database и сменив реализацию управления состоянием сеанса с InProc на внепроцессную, а также используя cookie и базу данных, я успешно преобразовал приложение с состояниями в приложение без состояний, как демонстрирует рис. 2.

*
Рис. 2. Логическое представление модифицированного приложения Photo Gallery

Web SiteВеб-сайт
SessionСеанс
DatabaseБаза данных
SQLSQL

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

Этап 3: дополнительные усовершенствования, имеющие важное значение

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

Одно из изменений, рассмотренных на этапе 2, заключалось в сохранении фотографий не в базе данных, а в централизованном месте, таком как общий (сетевой) диск, к которому могли бы обращаться все экземпляры веб-сервера. Архитектура Azure Web Sites гарантирует, что все экземпляры веб-приложения, выполняемые на нескольких веб-серверах, смогут использовать один и тот же диск, как иллюстрирует рис. 3.

*
Рис. 3. В Microsoft Azure Web Sites все экземпляры веб-приложения видят общий сетевой диск

‘Shared Disk’Общий сетевой диск

С точки зрения веб-приложения Photo Gallery, общий сетевой диск — то место, где фотографии, выкладываемые пользователем, сохраняются в папку …/uploaded, которая выглядит как локальная. Однако, когда изображение записывается на диск, оно сохраняется не «локально» на каком-то конкретном веб-сервере, который обрабатывает данный HTTP-запрос, а в централизованное место, доступное всем веб-серверам. Поэтому любой сервер может записывать любую фотографию на общий диск, и все остальные веб-серверы могут считывать это изображение. Метаданные фотографии хранятся в базе данных и используются приложением для чтения идентификатора фотографии (GUID); URL изображения возвращается как часть HTML-ответа. Следующий фрагмент кода является частью view.cshtml — страницы, используемой для просмотра изображений:

<img class="large-photo" src="@ImagePathHelper.GetFullImageUrl(photoId,
  photo.FileExtension.ToString())" alt="@Html.AttributeEncode(photo.FileTitle)" />

Источник HTML-элемента image заполняется значением, возвращаемым вспомогательной функцией GetFullImageUrl, которая принимает идентификатор снимка и расширение файла (.jpg, .png и т. д.) и возвращает строковое представление URL изображения.

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

Первое изменение, которое нужно внести, — перейти на использование Azure Blob Storage (bit.ly/TOK3yb) для хранения и обслуживания фотографий. Когда пользователь запрашивает просмотр какого-либо изображения, URL, возвращаемый обновленным GetFullImageUrl, указывает на Azure Blob. Результат выглядит примерно, как следующий HTML, где URL изображения указывает на Blob Storage:

<img class="large-photo" alt="764beb6b-1988-42d7-9900-03ee8a60749b"
  src="http://photogalcontentwestus.blob.core.windows.net/
  full/764beb6b-1988-42d7-9900-03ee8a60749b.jpg">

Это означает, что изображение обслуживается напрямую из Blob Storage, а не с веб-серверов, выполняющих веб-приложение.

В противоположность предыдущему примеру следующий код показывает снимки, сохраненные на общем диске в Azure Web Sites:

<img class="large-photo" alt="764beb6b-1988-42d7-9900-03ee8a60749b"
  src="http:// builddemophotogal2014.websites.net/
  full/764beb6b-1988-42d7-9900-03ee8a60749b.jpg">

Веб-приложение Photo Gallery использует два контейнера: полный (full) и эскизное представление (thumbnail). Как и следовало ожидать, первый сохраняет снимки исходного размера, а второй — уменьшенные изображения, показываемые в режиме просмотра галереи:

public static string GetFullImageUrl(string imageId,
  string imageExtension)
{
  return String.Format("{0}/full/{1}{2}",
    Environment.ExpandEnvironmentVariables("%AZURE_STORAGE_BASE_URL%"),
    imageId, imageExtension);
}

AZURE_STORAGE_BASE_URL — это переменная окружения, которая содержит базовый URL для Azure Blob, в данном случае http://photogalcontentwestus.blob.core.windows.net. Эту переменную можно задать на Azure Portal на вкладке Site Config или через web.config приложения. Однако задание переменных окружения на Azure Portal дает больше гибкости, так как изменения вносить легче без повторного развертывания.

Azure Storage используется во многом так же, как и сеть доставки контента (CDN), главным образом из-за того, что HTTP-запросы на изображения обслуживаются не веб-серверами приложения, а напрямую из контейнера Azure Storage. Это существенно сокращает трафик HTTP-запросов на статический контент для ваших веб-серверов, позволяя им обрабатывать больше динамических запросов. Заметьте, что Azure Storage способно обрабатывать куда больше трафика, чем средний веб-сервер — один контейнер можно масштабировать на обслуживание многих десятков тысяч запросов в секунду.

Кроме использования Blob Storage для статического контента, вы можете добавить и Microsoft Azure CDN. Добавление CDN поверх вашего веб-приложения еще больше увеличивает производительность, так как CDN будет обслуживать весь статический контент. Запросы на фотографии, уже кешированные в CDN, не будут направляться в Blob Storage. Более того, CDN улучшает и субъективно оцениваемую работу приложения, поскольку CDN обычно имеет пограничный сервер (edge server), находящийся неподалеку от конечного пользователя. Подробное описание добавления CDN к приложению-примеру выходит за рамки этой статьи, поскольку соответствующие изменения в основном связаны с регистрацией и настройкой DNS. Но при модификации крупномасштабного производственного приложения вам следует подумать о применении CDN.

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

Следующее изменение, которое я внесу в веб-приложение Photo Gallery, — добавление Azure Storage Queue как способа отделения клиентской части приложения (веб-сайта) от серверной бизнес-логики (WebJob + база данных). Без этой очереди код Photo Gallery обрабатывал как клиентскую часть, так и серверную, поскольку код загрузки сохранял полноразмерное изображение в хранилище, создавал его эскизное представление и сохранял в хранилище, а затем обновлял базу данных SQL Server. Все это время пользователь ожидал ответа. Однако с введением Azure Storage Queue клиентская часть просто записывает сообщение в очередь и тут же возвращает ответ пользователю. Фоновый процесс, WebJob (bit.ly/1mw0A3w), извлекает сообщение из очереди и выполняет необходимую серверную бизнес-логику. В случае Photo Gallery она охватывает манипуляции изображениями, их сохранение в нужном месте и обновление базы данных. Рис. 4 иллюстрирует изменения, внесенные на этапе 3, включая использование Azure Storage и добавление Queue.

*
Рис. 4. Логическое представление Photo Gallery после этапа 3

Web SiteВеб-сайт
Storage QueueStorage Queue
WebJobWebJob
Storage QueueStorage Queue
DatabaseБаза данных
SQLSQL

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

var file = Request.Files[i];
var fileExtension = Path.GetExtension(file.FileName).Trim();
guid = Guid.NewGuid();
using
var fileStream = new FileStream(
 Path.Combine( HostingEnvironment.MapPath("~/App_Data/Upload/"),
 guid + fileExtension), FileMode.Create))
{
  file.InputStream.CopyTo(fileStream);
  StorageHelper.EnqueueUploadAsync(
    Request.GetCurrentUser(Response),
     galleryId, guid.ToString(), fileExtension);
}

StorageHelper.EnqueueUploadAsync просто создает CloudQueueMessage и асинхронно загружает его в Azure Storage Queue:

public static Task EnqueueUploadAsync
  (string userName, string galleryId, string imageId,
    string imageExtension)
{
  return UploadQueue.AddMessageAsync(
    new CloudQueueMessage(String.Format("{0}, {1}, {2}, {3}",
    userName, galleryId, imageId,
    imageExtension)));
}

WebJob теперь отвечает за серверную бизнес-логику. Новая особенность процессов WebJob в Azure Web Sites позволяет легко запускать такие программы, как сервисы или фоновые задачи, на веб-сайте. WebJob прослушивает изменения в очереди и извлекает любое новое сообщение. Метод ProcessUploadQueueMessages (рис. 5) вызывается всякий раз, когда в очереди есть минимум одно сообщение. Атрибут QueueInput является частью Microsoft Azure WebJobs SDK (bit.ly/1cN9eCx) — инфраструктуры, которая упрощает добавление фоновой обработки в Azure Web Sites. Рассмотрение WebJobs SDK выходит за рамки этой статьи, но на самом деле вам достаточно знать, что WebJobs SDK обеспечивает простую привязку к очереди, в моем случае к uploadqueue, и прослушивание входящий сообщений.

Рис. 5. Чтение сообщения из очереди и обновление базы данных

public static void ProcessUploadQueueMessages
  ([QueueInput(“uploadqueue”)] string queueMessage, IBinder binder)
{
  var splited = queueMessage
    .Split(‘,’).Select(m => m.Trim()).ToArray();
  var userName = splited[0];
  var galleryId = splited[1];
  var imageId = splited[2];
  var extension = splited[3];
  var filePath = Path.Combine(ImageFolderPath,
    imageId + extension);
  UploadFullImage(filePath, imageId + extension, binder);
  UploadThumbnail(filePath, imageId + extension, binder);
  SafeGuard(() => File.Delete(filePath));
  using (var db = Database.Open(“PhotoGallery”))
  {
    db.Execute(@”INSERT INTO Photos Id, GalleryId, UserName,
      Description, FileTitle, FileExtension, UploadDate, Likes)
      VALUES @0, @1, @2, @3, @4, @5, @6, @7)”, imageId,
      galleryId, userName, “”, imageId, extension, DateTime.UtcNow, 0);
  }
}

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

К этому моменту обновленное веб-приложение Photo Gallery способно обрабатывать многие миллионы HTTP-запросов в сутки.

Этап 4: глобальный охват

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

На этом этапе я внесу такие изменения в приложения, чтобы оно могло выполняться в нескольких информационных центрах. Для целей данной статьи я сосредоточусь на выполнении в двух местах в режиме «активный-активный», где приложение в обоих информационных центрах позволяет просматривать фотографии, а также загружать их и писать к ним комментарии.

Учтите: мне известен контекст веб-приложения Photo Gallery, и я знаю, что подавляющее большинство операций пользователей являются операциями чтения, в результате которых показываются снимки. Лишь небольшое количество запросов включает загрузку новых снимков или обновление комментариев. В случае Photo Gallery можно с уверенностью заявить, что доля операций чтения минимум 95%. Это позволяет сделать некоторые предположения, например, что для моего приложения приемлема согласованность в конечном счете (eventual consistency) в рамках системы и что допустим более медленный ответ на операции записи.

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

Как ни удивительно, но модификация Photo Gallery на выполнение из двух разных мест потребовала минимальных усилий, так как основная работа была проделана на этапах 2 и 3. На рис. 6 показана высокоуровневая блок-схема топологии приложения, выполняемого в двух информационных центрах. Приложение в западной части США является «основным» и фактически является результатом этапа 3. Приложение в восточной части США — «дополнительный» сайт, и поверх обоих приложений размещается Azure Traffic Manager. Последний предлагает несколько вариантов конфигурации. Я задействовал вариант Performance, который заставляет Traffic Manager отслеживать оба сайта на наличие задержек в соответствующих регионах и перенаправляет трафик туда, где задержка меньше. В данном случае клиенты из Нью-Йорка (восточной побережье) будут направляться на сайт в восточной части США, а клиенты из Сан-Франциско (западное побережье) — на сайт в западной части США. Оба сайта одновременно активны и обслуживают трафик. Если приложение в одном регионе по какой-то причине испытывает проблемы с производительностью, Traffic Manager будет направлять трафик другом приложению. Поскольку данные синхронизированы, никаких потерь данных не будет.

*
Увеличить

Рис. 6. Логическое представление Photo Gallery после этапа 4

Traffic ManagerДиспетчер трафика
East United StatesВосток США
West United StatesЗапад США
Web SiteВеб-сайт
Storage QueueStorage Queue
WebJobWebJob
DatabaseБаза данных
SQLSQL

Рассмотрим изменения в приложении для западной части США. Единственное изменение в коде — WebJob прослушивает сообщения в очереди. Вместо сохранения снимков в одном Blob WebJob сохраняет снимки в локальном и «удаленном» хранилищах Blob. На рис. 5 UploadFullImage — вспомогательный метод, сохраняющий снимки в Blob Storage. Чтобы обеспечить копирование снимка в удаленный Blob, равно как и в локальный, я добавил вспомогательную функцию ReplicateBlob в конец UploadFullImage:

private static void UploadFullImage(
  string imagePath, string blobName, IBinder binder)
{
  using (var fileStream = new FileStream(imagePath, FileMode.Open))
  {
    using (var outputStream =
      binder.Bind<Stream>(new BlobOutputAttribute(
      String.Format("full/{0}", blobName))))
    {
      fileStream.CopyTo(outputStream);
    }
  }
  RemoteStorageManager.ReplicateBlob("full", blobName);
}

В методе ReplicateBlob в следующем коде имеется одна важная строка — в последней строке вызывается метод StartCopyFromBlob, который запрашивает сервис скопировать весь контент, свойства и метаданные Blob в новый Blob (в остальном я полагаюсь на Azure SDK и сервис хранилищ):

public static void ReplicateBlob(string container, string blob)
{
  if (sourceBlobClient == null || targetBlobClient == null)
    return;
  var sourceContainer =
    sourceBlobClient.GetContainerReference(container);
  var targetContainer =
    targetBlobClient.GetContainerReference(container);
  if (targetContainer.CreateIfNotExists())
  {
    targetContainer.SetPermissions(sourceContainer.GetPermissions());
  }
  var targetBlob = targetContainer.GetBlockBlobReference(blob);
  targetBlob.StartCopyFromBlob(sourceContainer.GetBlockBlobReference(blob));
}

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

Синхронизация базы данных — последний фрагмент головоломки. Для этой операции я буду использовать предварительную версию функциональности Active Geo-Replication (непрерывное копирование) в Azure SQL Database. С ее помощью вы можете получать дополнительные (только для чтения) реплики главной базы данных. Данные, записываемые в главную базу данных, автоматически копируются в дополнительную. Главная база данных конфигурируется для чтения и записи, а все дополнительные базы данных — только для чтения. Это и есть причина, по которой в моем сценарии сообщения передаются из очереди в восточной части США в очередь, расположенную в западной части США. Как только вы сконфигурируете Active Geo-Replication (через портал), базы данных будут синхронизироваться. И никакого кода, кроме уже показанного, для этого не требуется.

Заключение

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

Автор: Йочай Кириати  •  Иcточник: msdn.microsoft.com  •  Опубликована: 12.11.2014
Нашли ошибку в тексте? Сообщите о ней автору: выделите мышкой и нажмите CTRL + ENTER
Теги:   Azure Web Sites.


Оценить статью:
Вверх
Комментарии посетителей
Комментарии отключены. С вопросами по статьям обращайтесь в форум.