Эффективное управление облачными очередями (Azure Queue)

в 10:00, , рубрики: .net, AtContent, CPlase, windows azure, Облачные вычисления, метки: , ,

В этой, уже четвертой, статье из цикла «Внутреннее устройство и архитектура сервиса AtContent.com» я предлагаю познакомиться с фоновой обработкой заданий с использованием экземпляров сервиса Azure (Worker Role).

В качестве основного канала коммуникации между экземплярами рекомендуется использовать очереди Azure (Azure Queue). Но использование только этого канала не позволяет максимально эффективно использовать экземпляры сервиса. Итак, в этой статье вы узнаете как

  • минимизировать задержку между отправкой задания на экземпляр и началом его обработки
  • минимизировать количество транзакций к Azure Queue
  • повысить эффективность обработки заданий

Эффективное управление облачными очередями (Azure Queue)

Как мы видим из схемы – стандартные средства SDK для Azure предполагают сценарий взаимодействия исключительно через очередь (Azure Queue). При этом экземпляр сервиса Azure не знает в какой момент экземпляр приложения отправит ему задание через очередь. Поэтому ему приходится периодически проверять наличие заданий в очереди, что порождает некоторые проблемы и неудобства.

Одна из проблем – это задержка в обработке заданий. Возникает она по следующей причине. Экземпляр сервиса вынужден периодически проверять наличие заданий в очереди. При этом если делать такую проверку слишком часто – это будет порождать большое количество транзакций к Azure Queue. Если при этом такие задания будут попадать в очередь достаточно редко – мы получаем большое количество «холостых» транзакций. Если же делать проверку наличия заданий в очереди реже – то интервал между такими проверками будет больше. Таким образом если задание попадет в очередь сразу после её проверки экземпляром сервиса – то оно будет обработано только в начале следующего интервала. Это вносит существенные задержки в обработку сообщений.

Ещё одна проблема – это «холостые» транзакции. То есть даже если в очереди нет сообщений – экземпляру сервиса все равно приходится обращаться к ней, чтобы проверить наличие этих самых сообщений. Это порождает накладные расходы.

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

Решить эти поблемы можно с помощью механизма обмена сообщениями между экземплярами и ролями. Он был описан в предыдущей статье серии (http://habrahabr.ru/post/140461/). Несколько слов об этом механизме. Он позволяет передавать сообщения от одного экзмепляра другому, либо же всем экземплярам роли. С помощью него можно запускать различные обработчики на экземплярах, что позволяет, например, синхронизировать состояния экземпляров. В применении к поставленной задаче он позволяет запускать обработку очереди сразу же после добавления задания в очередь. При этом не требуется постоянно проверять наличие заданий в очереди, что исключает «холостые» транзакции.

Эффективное управление облачными очередями (Azure Queue)
Если рассмотреть этот механизм более подробно, то мы увидим следующее:
Эффективное управление облачными очередями (Azure Queue)

Экземпляр приложения Azure добавляет задачу в очередь Azure Queue и отправляет сообщение на экземпляр сервиса Azure. Сообщение, в свою очередь, активирует обработчик. Дальнейшие действия обработчика зависят от настроек. Если в настройках присутсвует запись о том, что обработка очереди уже запущена – то повторно её запускать нет смысла. Иначе обработчик запустит обработку и добавит в настройки соответсвующую запись. Он будет выбирать задания из очереди по мере выполнения и продолжать до тех пор, пока они не закончатся.

Настройки обработчика могут хранться различными способами. Например, в памяти экземпляра (что является не самым надежным способом), в хранилище экземпляра или в хранилище BLOB’ов (Azure Blob Storage).

В библиотеке CPlase для обработки очередей есть специальный класс Queue, который позволяет создавать обработчики очередей. Для этого в библиотеке также есть интерфейс IWorkerQueueHandler и расширения для него, которые позволяют сделать удобной работу с очередями:

public interface IWorkerQueueHandler
{
    bool HandleQueue(string Message);
}

public static class WorkerQueueHandlerExtensions
{
    private static string CleanUpQueueName(string DirtyQueueName)
    {
        return DirtyQueueName.Substring(0, DirtyQueueName.IndexOf(",")).ToLowerInvariant().Replace(".", "-");
    }

    public static string GetQueueName(this IWorkerQueueHandler Handler)
    {
        return CleanUpQueueName(Handler.GetType().AssemblyQualifiedName);
    }

    public static string GetQueueName(Type HandlerType)
    {
        return CleanUpQueueName(HandlerType.AssemblyQualifiedName);
    }
}

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

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

public static CloudQueue GetQueue(string QueueName)
{
    CreateOnceQueue(QueueName);
    return queueStorage.GetQueueReference(QueueName);
}

public static bool AddToQueue<QueueHandlerType>(string Task) where QueueHandlerType : IWorkerQueueHandler
{
    var Queue = GetQueue(WorkerQueueHandlerExtensions.GetQueueName(typeof(QueueHandlerType)));
    return AddToQueue<QueueHandlerType>(Queue, Task);
}

public static bool AddToQueue<QueueHandlerType>(CloudQueue Queue, string Task)
{
    try
    {
        var Message = new CloudQueueMessage(Task);
        Queue.AddMessage(Message);
        Internal.RoleCommunicatior.WorkerRoleCommand(typeof(QueueHandlerType));
        return true;
    }
    catch { return false; }
}

Легко посчитать выгоду такого решения, если приблизительно знать объем задач, которые поступают в очередь и примерную структуру потока. Так, например, если у вас задачи возникают в основном в вечернее время с 20 до 23 часов, то большую часть суток проверка задач с интервалом будет работать «в холостую». И нетрудно посчитать, что 21 час она будет выбирать задачи из очереди берзезультатно. При этом, если такая проверка будет выполняться раз в секунду – то это повлечет за собой около 75 000 «холостых» транзакций в день. При стоимости транзакций в $0.01 за 10 000 – эта сумма будет составлять $0.075 в день или $2.25 в месяц. Но если у вас будет 100 различных очередей – то дополнительные затраты в месяц составят уже $225.

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

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

Также отмечу, что механизм работы с очередями входит в состав Open Source библиотеки CPlase, которая в скором времени будет опубликована и доступна для всех желающих.

Читайте в серии:

Автор: VadNov

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js