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


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

Хранилище в облаке Windows Azure Storage

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

Разработчики обычно привыкают к какой-то физической инфраструктуре и всеми силами цепляются за нее. Им известно, как пользоваться ею, они знают, как она работает, а когда что-то идет не так, могут быстро понять, в чем дело. Это часто создает барьер, который замедляет принятие разработчиками более новых технологий, в частности вычислений в облаке (cloud computing).

Один из наиболее частых вопросов, задаваемых скептически настроенными разработчиками, — как выполнять фоновые процессы в облаке и как будут работать их подсистемы. Цель этой статьи заключается в том, чтобы развеять некоторые мифы насчет отсутствия фоновой обработки в облаке, показав, как создать подсистему приложения, а также реализовать обмен асинхронными сообщениями и обработку с применением Windows Azure Storage.

Для доказательства того, что разработчики могут отбросить защитный слой своей физической инфраструктуры и разместить подсистемы приложения в облаке, мы подробно разберем пример реализации малого подмножества приложения электронной коммерции — Hollywood Hackers, где вы сможете купить все магические штучки, используемые Голливудом, чтобы напрочь игнорировать законы физики и остатки здравого смысла.

В статье рассматриваются два основных варианта:

  • отправка асинхронных текстовых сообщений ("тостов") пользователям приложения для уведомления о важных событиях, например о передаче корзины покупателя в обработку, или для передачи сообщений между сотрудниками. В этом варианте используются Windows Azure Queue, Windows Azure Table и Windows Azure Worker Role;
  • передача корзины покупателя подсистеме обработки с применением Windows Azure Queue и Windows Azure Worker Role.

Обмен сообщениями внутри приложения с помощью хранилища очередей

Прежде чем перейти в рассмотрению конкретных сценариев, нужно обсудить некоторые основы Windows Azure Queue. Очереди (queues) в облаке работают не совсем так, как очереди в ваших обычных .NET-приложениях. Когда вы имеете дело с данными в каком-то AppDomain (домене приложения), вы знаете, что эти данные существуют только в одном экземпляре и находятся в рамках единственного управляемого процесса.

В облаке одна часть ваших данных может быть расположена в Калифорнии, а другая — в Нью-Йорке, причем у вас могут быть одна рабочая роль (Worker role) для обработки этих данных в Техасе и еще одна — в Северной Дакоте.

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

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

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

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

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

Фрагмент кода на рис. 1 показывает, как создать и передать сообщение в Windows Azure Queue, используя библиотеку StorageClient, поставляемую с Windows Azure SDK. Библиотека StorageClient — на самом деле просто оболочка HTTP-интерфейса Windows Azure Storage.

Рис. 1 Создание и передача сообщения в Windows Azure Queue

string accountName;
string accountSharedKey;
string queueBaseUri;
string StorageCredentialsAccountAndKey credentials;

if (RoleEnvironment.IsAvailable)
{
// We are running in a cloud - INCLUDING LOCAL!
  accountName =
  RoleEnvironment.GetConfigurationSettingValue("AccountName");
  accountSharedKey =
  RoleEnvironment.GetConfigurationSettingValue("AccountSharedKey");
  queueBaseUri = RoleEnvironment.GetConfigurationSettingValue
 ("QueueStorageEndpoint");
}
else
{
  accountName = ConfigurationManager.AppSettings["AccountName"];
  accountSharedKey =
  ConfigurationManager.AppSettings["AccountSharedKey"];
  queueBaseUri =
  ConfigurationManager.AppSettings["QueueStorageEndpoint"];
}
credentials =
new StorageCredentialsAccountAndKey(accountName, accountSharedKey);
CloudQueueClient client =
new CloudQueueClient(queueBaseUri, credentials);
CloudQueue queue = client.GetQueueReference(queueName);
CloudQueueMessage m = new CloudQueueMessage(
  /* string or byte[] representing message to enqueue */);
Queue.AddMessage(m);

В остальных примерах в этой статье мы использовали некоторые классы-оболочки, доступные на сайте CodePlex для Hollywood Hackers (hollywoodhackers.codeplex.com/SourceControl/ListDownloadableCommits.aspx); они упрощают этот процесс.

Асинхронные сообщения (тосты)

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

Для этого мы задействуем механизмы Windows Azure Queue и Table Storage, на основе которых создадим инфраструктуру доставки сообщений. На клиентской стороне будет использоваться jQuery в сочетании с плагином jQuery Gritter для отображения уведомлений в браузере пользователя как тост — по аналогии с сообщениями, проявляющимися над системным лотком Windows при получении нового почтового сообщения Outlook, уведомления от средства мгновенного обмена сообщениями или твита (микро-сообщения в социальной сети twitter).

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

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

Как только пользователь обращается на веб-сайт, наш jQuery-сценарий асинхронно извлекает любые сообщения из таблицы и отображает их в браузере, вызывая метод контроллера, возвращающий JSON (JavaScript Object Notation) с корректным синтаксисом. Хотя очередь принимает только строки или байтовые массивы, в ней можно сохранить любой тип структурированных данных сериализацией в двоичную форму, а при необходимости последующего использования их всегда можно преобразовать к исходному виду. Это очень важно для передачи в очередь строго типизированных объектов. Такую возможность мы встроим в базовый класс для наших сообщений, помещаемых в очереди (рис. 2).

Рис. 2 Хранение структурированных данных в очереди

namespace HollywoodHackers.Storage.Queue
{
    [Serializable]
    public class QueueMessageBase
    {
        public byte[] ToBinary()
        {
            BinaryFormatter bf = new BinaryFormatter();
            MemoryStream ms = new MemoryStream();
            ms.Position = 0;
            bf.Serialize(ms, this);
            byte[] output = ms.GetBuffer();
            ms.Close();
            return output;
        }
        public static T FromMessage<T>(CloudQueueMessage m)
        {
            byte[] buffer = m.AsBytes();
            MemoryStream ms = new MemoryStream(buffer);
            ms.Position = 0;
            BinaryFormatter bf = new BinaryFormatter();
            return (T)bf.Deserialize(ms);
        }
    }
    [Serializable]
    public class ToastQueueMessage : QueueMessageBase
    {
	  public ToastQueueMessage()
            : base()
        {
        }
        public string TargetUserName { get; set; }
        public string MessageText { get; set; }
        public string Title { get; set; }
        public DateTime CreatedOn { get; set; }
   }

Учтите, что для использования класса BinaryFormatter ваша рабочая роль Windows Azure должна выполняться в режиме полного доверия (это можно разрешить через конфигурационный файл сервиса).

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

Рис. 3 Взаимодействие класса-оболочки с очередью

namespace HollywoodHackers.Storage.Queue
{
    public class StdQueue<T> :
    StorageBase where T : QueueMessageBase, new()
    {
        protected CloudQueue queue;
        protected CloudQueueClient client;

        public StdQueue(string queueName)
        {
            client = new CloudQueueClient
            (StorageBase.QueueBaseUri, StorageBase.Credentials);
            queue = client.GetQueueReference(queueName);
            queue.CreateIfNotExist();
        }
        public void AddMessage(T message)
        {
            CloudQueueMessage msg =
            new CloudQueueMessage(message.ToBinary());
            queue.AddMessage(msg);
        }
        public void DeleteMessage(CloudQueueMessage msg)
        {
            queue.DeleteMessage(msg);
        }
        public CloudQueueMessage GetMessage()
        {
            return queue.GetMessage(TimeSpan.FromSeconds(60));
        }
    }
    public class ToastQueue : StdQueue<ToastQueueMessage>
    {
        public ToastQueue()
            : base("toasts")
        {
        }
    }
}

Также понадобится создать оболочку для хранилища таблиц (Table Storage), чтобы пользовательские уведомления могли храниться до тех пор, пока пользователи не войдут на сайт. Данные таблицы организуются с помощью PartitionKey (идентификатора набора строк) и RowKey (уникального идентификатора каждой индивидуальной строки в определенном разделе). Выбор данных, используемых для PartitionKey и RowKey, — одно из важнейших решений в проектировании приложения, работающего с хранилищем таблиц.

Эти средства позволяют балансировать нагрузку между узлами хранилища и обеспечивают масштабируемость вашего приложения. Независимо от привязки ваших данных к конкретному информационному центру строки в хранилище таблиц с одинаковым ключом раздела будут храниться в одном физическом хранилище данных. Поскольку сообщения сохраняются индивидуально для каждого пользователя, ключом раздела будет UserName, а RowKey будет GUID, который идентифицирует каждую строку (рис. 4).

Рис. 4 Класс-оболочка для хранилища таблиц

namespace HollywoodHackers.Storage.Repositories
{
    public class UserTextNotificationRepository : StorageBase
    {
        public const string EntitySetName =
        "UserTextNotifications";
        CloudTableClient tableClient;
        UserTextNotificationContext notificationContext;
        public UserTextNotificationRepository()
            : base()
        {
            tableClient = new CloudTableClient
            (StorageBase.TableBaseUri, StorageBase.Credentials);
            notificationContext = new UserTextNotificationContext
            (StorageBase.TableBaseUri,StorageBase.Credentials);

            tableClient.CreateTableIfNotExist(EntitySetName);
        }
        public UserTextNotification[]
        GetNotificationsForUser(string userName)
        {
            var q = from notification in
                    notificationContext.UserNotifications
                    where notification.TargetUserName ==
                    userName select notification;
            return q.ToArray();
        }
        public void AddNotification
       (UserTextNotification notification)
        {
            notification.RowKey = Guid.NewGuid().ToString();
            notificationContext.AddObject
           (EntitySetName, notification);
            notificationContext.SaveChanges();
        }
    }
}

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

Рис. 5 Рабочая роль в роли подсистемы

public class WorkerRole : RoleEntryPoint
{
    ShoppingCartQueue cartQueue;
    ToastQueue toastQueue;
    UserTextNotificationRepository toastRepository;

    public override void Run()
    {
        // This is a sample worker implementation.
        //Replace with your logic.
        Trace.WriteLine("WorkerRole1 entry point called",
        "Information");
        toastRepository = new UserTextNotificationRepository();
        InitQueue();
        while (true)
        {
            Thread.Sleep(10000);
            Trace.WriteLine("Working", "Information");

            ProcessNewTextNotifications();
            ProcessShoppingCarts();
        }
    }
    private void InitQueue()
    {
        cartQueue = new ShoppingCartQueue();
        toastQueue = new ToastQueue();
    }
    private void ProcessNewTextNotifications()
    {
        CloudQueueMessage cqm = toastQueue.GetMessage();
        while (cqm != null)
        {
            ToastQueueMessage message =
            QueueMessageBase.FromMessage<ToastQueueMessage>(cqm);

            toastRepository.AddNotification(new
            UserTextNotification()
            {
                MessageText = message.MessageText,
                MessageDate = DateTime.Now,
                TargetUserName = message.TargetUserName,
                Title = message.Title
            });
            toastQueue.DeleteMessage(cqm);
            cqm = toastQueue.GetMessage();
        }
    }
    private void ProcessShoppingCarts()
    {
        // We will add this later in the article!
    }
    public override bool OnStart()
    {
        // Set the maximum number of concurrent connections
        ServicePointManager.DefaultConnectionLimit = 12;

        DiagnosticMonitor.Start("DiagnosticsConnectionString");
        // For information on handling configuration changes
        // see the MSDN topic at
        //http://go.microsoft.com/fwlink/?LinkId=166357.
        RoleEnvironment.Changing += RoleEnvironmentChanging;
        return base.OnStart();
    }
    private void RoleEnvironmentChanging(object sender, RoleEnvironmentChangingEventArgs e)
    {
        // If a configuration setting is changing
        if (e.Changes.Any(change => change is RoleEnvironmentConfigurationSettingChange))
        {
            // Set e.Cancel to true to restart this role instance
            e.Cancel = true;
        }
    }
}

Давайте пробежимся по коду рабочей роли. После инициализации и подготовки очереди и хранилища таблиц код входит в цикл. Через каждые 10 секунд он обрабатывает сообщения в очереди. На каждой итерации цикла обработки мы получаем сообщения из очереди до тех пор, пока мы не возвращаем null, указывая, что очередь пуста.

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

На клиентской стороне нужна возможность возвращать сообщения как JSON, чтобы jQuery мог асинхронно запрашивать и отображать новые пользовательские уведомления. Для этого мы добавим свой код в контроллер сообщений, чтобы можно было обращаться к уведомлениям (рис. 6).

Рис. 6 Возврат сообщения в виде JSON

public JsonResult GetMessages()
{
     if (User.Identity.IsAuthenticated)
     {
UserTextNotification[] userToasts =
        toastRepository.GetNotifications(User.Identity.Name);
object[] data =
(from UserTextNotification toast in userToasts
           	select new { title = toast.Title ?? "Notification",
 text = toast.MessageText }).ToArray();
            return Json(data, JsonRequestBehavior.AllowGet);
     }
     else
         return Json(null);
}

ASP.NET MVC 2 в Visual Studio 2010 beta 2 (в этой среде мы писали код для статьи) не позволяет возвращать JSON-данные в jQuery или любой другой клиент без JsonRequestBehavior.AllowGet. В ASP.NET MVC 1 это было необязательно. Теперь мы можем написать код на JavaScript, который будет вызывать метод GetMessages через каждые 15 секунд и отображать уведомления как сообщения в стиле тостов (toast-style messages) (рис. 7).

Рис. 7 Уведомления как сообщения в стиле тостов

$(document).ready(function() {

    setInterval(function() {
        $.ajax({
            contentType: "application/json; charset=utf-8",
            dataType: "json",
            url: "/SystemMessage/GetMessages",
            success: function(data) {
                for (msg in data) {
                    $.gritter.add({
                        title: data[msg].title,
                        text: data[msg].text,
                        sticky: false
                    });
                }
            }
        })
    }, 15000)
});

Передача и обработка корзины покупателя

Еще один ключевой момент для нашего приложения-примера, ради которого вводилась поддержка хранилища очередей, — передача корзин покупателей подсистеме обработки. В Hollywood Hackers используется сторонняя система выполнения заказов (не могут же они хранить все эти гаджеты в своей подсобке), поэтому соответствующая подсистема должна выполнять какую-то обработку корзины покупателя. Как только она заканчивает обработку, в очередь пользовательских уведомлений ставится сообщение, чтобы оповестить пользователя об обработке его корзины (или о том, что возникла какая-либо проблема). Если пользователь находится в сети, когда корзина обработана, он получит всплывающее сообщение от системы. А если он отключен от сети — всплывающее сообщение появится при следующем его входе на сайт; сообщение показано на рис. 8.

Рис. 8 Пример пользовательского уведомления

*
Увеличить

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

В отличие от стандартного хранилища CRUD (create, read, update, delete) операции чтения из очереди не являются такими простыми. Вспомните, что всякий раз, когда вы извлекаете сообщение из очереди, вам отводится весьма ограниченное время, в течение которого вы должны либо обработать сообщение и удалить его из очереди, либо уведомить о неудаче. Этот шаблон трудно подогнать под шаблон хранилища CRUD, поэтому мы не стали реализовать эту абстракцию в классе-оболочке.

Теперь, когда у нас есть код для взаимодействия с очередью корзин покупателей, можно поместить код в контроллер корзин для передачи их содержимого в очередь (рис. 9).

Рис. 9 Передача корзины покупателя в очередь

public ActionResult Submit()
    {
        ShoppingCartMessage cart = new ShoppingCartMessage();
        cart.UserName = User.Identity.Name;
        cart.Discounts = 12.50f;
        cart.CartID = Guid.NewGuid().ToString();
        List<ShoppingCartItem> items = new List<ShoppingCartItem>();
        items.Add(new ShoppingCartItem()
             { Quantity = 12, SKU = "10000101010",
             UnitPrice = 15.75f });
        items.Add(new ShoppingCartItem()
             { Quantity = 27, SKU = "12390123j213",
             UnitPrice = 99.92f });
        cart.CartItems = items.ToArray();
        cartQueue.AddMessage(cart);
        return View();
    }

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

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

Рис. 10 Проверка очереди на наличие ждущих корзин покупателей

private void ProcessShoppingCarts()
{
    CloudQueueMessage cqm = cartQueue.GetMessage();

    while (cqm != null)
    {
        ShoppingCartMessage cart =
        QueueMessageBase.FromMessage<ShoppingCartMessage>(cqm);

        toastRepository.AddNotification(new UserTextNotification()
        {
            MessageText = String.Format
            ("Your shopping cart containing {0} items has been processed.",
            cart.CartItems.Length),
            MessageDate = DateTime.Now,
            TargetUserName = cart.UserName
        });
        cartQueue.DeleteMessage(cqm);
         cqm = cartQueue.GetMessage();
    }
}

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

Заключение

Цель этой статьи — заставить разработчиков отбросить защитное "одеяло" своих физических информационных центров и показать им, что с помощью Windows Azure можно создавать не только простые веб-сайты типа "Hello World". Применяя очереди и хранилище таблиц Windows Azure и используя их возможности для обмена асинхронными сообщениями между приложением и его рабочими ролями, вы сможете задействовать в своем приложении всю мощь Windows Azure.

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

Автор: Кевин Хоффман и Натан Дудек  •  Иcточник: Журнал MSDN  •  Опубликована: 02.12.2010
Нашли ошибку в тексте? Сообщите о ней автору: выделите мышкой и нажмите CTRL + ENTER


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