Pull to refresh

CRON в облаке: полное руководство по новому сервису планировщика задач Windows Azure Scheduler

Reading time 14 min
Views 6.3K
Original author: Sandrino Di Mattia
image

Выполнение задач по расписанию в Windows Azure всегда было интересной темой с рядом готовых решений, которые помогали сделать работу правильно: планировщик мобильных сервисов, Quartz Scheduler, FluentScheduler, WebBackgrounder, ScheduledEnqueueTimeUtc и т.д. Хотя вы могли успешно использовать эти варианты, на самом деле они не были предназначены специально для ваших приложений Windows Azure, работающих на базе ролей облачных сервисов или веб-сайтов.

Именно по этой причине, почти год назад, компания Aditi анонсировала свой новый сервис – масштабируемый планировщик задач, который был построен специально для удовлетворения потребностей приложений запущенных в Windows Azure. Aditi Scheduler был создан Ryan Dunn – легендой из команды Windows Azure, несколько месяцев назад сервис отпраздновал выполнение 500000 задачи и добавил поддержку очередей хранилища Windows Azure Storage Queues.

Но недавно Microsoft анонсировала прекрасную альтернативу Aditi Scheduler – новый сервис Windows Azure Scheduler:

Windows Azure Scheduler позволит вам осуществлять различные действия, такие как HTTP/S-запросы или отправка сообщений в очередь хранилища, по расписанию. С помощью планировщика вы можете создавать задачи в облаке, которые гарантированно вызовут сервисы как внутри облачной инфраструктуры, так и снаружи нее. Вы можете выполнять эти задачи по требованию или на регулярной основе по расписанию, а так же назначить исполнение на какую-то дату в будущем.

Давайте познакомимся с новым сервисом поближе.

Активация превью-сервиса


Сервис Windows Azure Scheduler в настоящий момент доступен в качестве превью, так что для его использования вы должны подписаться на него на странице предварительных сервисов: http://www.windowsazure.com/en-us/services/preview/.



Управление сервисом пока недоступно в портале администрирования Windows Azure. Единственным способом взаимодействия с планировщиком является открытый REST API или использование готового SDK. API сервиса детально описаны на MSDN в разделе Scheduler REST API Reference.

Установка SDK


Scheduler SDK доступен в качестве NuGet-пакета (в первью-версии) и является частью новых библиотек управления Windows Azure Management Libraries, выпущенных некоторое время назад. Для начала вам необходимо установить пакет:
PM> Install-Package Microsoft.WindowsAzure.Management.Scheduler –Pre

Начинаем работу


Для того чтобы быстро протестировать новые функции я использовал файл настроек публикации Windows Azure (который вы можете загрузить отсюда). Следующий класс извлекает данные из файла и создает на их базе объект CertificateCloudCredentials, который вы можете использовать для аутентификации при работе с библиотекой управления:

public static class CertificateCloudCredentialsFactory 
{ 
     public static CertificateCloudCredentials FromPublishSettingsFile(string path, string subscriptionName) 
     { 
         var profile = XDocument.Load(path); 
         var subscriptionId = profile.Descendants("Subscription") 
             .First(element => element.Attribute("Name").Value == subscriptionName) 
             .Attribute("Id").Value; 
         var certificate = new X509Certificate2( 
             Convert.FromBase64String(profile.Descendants("PublishProfile").Descendants("Subscription").Single().Attribute("ManagementCertificate").Value)); 
         return new CertificateCloudCredentials(subscriptionId, certificate); 
     } 
}

Использование этого класса – очень простая задача:

var publishSettingsFilePath = @"D:\\azdem.publishsettings"; 
var subscriptionName = "Azdem194D92901Y"; 
var credentials = CertificateCloudCredentialsFactory 
         .FromPublishSettingsFile(publishSettingsFilePath, subscriptionName);

Сервис планировщика задач исполняется в облачном сервисе (Cloud Service), именно поэтому мы начали с создания облачного сервиса. В настоящий момент вы должны учитывать один момент: созданный облачный сервис должен располагаться в регионе, в котором поддерживается сервис Windows Azure Scheduler (на этапе превью поддержка ограничена лишь некоторыми из регионов, но остальные будут добавлены позднее с выходом сервиса в коммерческую эксплуатацию).

var cloudServiceClient = new CloudServiceManagementClient(credentials); 
var result = cloudServiceClient.CloudServices.Create("sandrino-cs1", new CloudServiceCreateParameters() 
{ 
     Description = "sandrino-cs1", 
     GeoRegion = "north europe", 
     Label = "sandrino-cs1" 
}); 
  
Console.WriteLine(result.Status); 
Console.WriteLine(result.HttpStatusCode);

Регистрация провайдера ресурсов планировщика задач


Известно, что облачный сервис может содержать в себе как виртуальные машины (IaaS), так и веб- или рабочие роли (PaaS). Однако, сейчас мы можем убедиться в том, что облачный сервис может содержать так же и "Провайдер Ресурсов” (Resource Provider), такой как Windows Azure Scheduler. Для того чтобы использовать подобный провайдер ресурсов в своем облачном сервисе сначала вам необходимо разрешить его для своей подписки. Если не сделать этого, то вы столкнетесь со следующей ошибкой:
An unhandled exception of type ‘Microsoft.WindowsAzure.CloudException’ occurred in Microsoft.WindowsAzure.Management.Scheduler.dll

Additional information: ForbiddenError: The subscription is not entitled to use the resource
Теперь давайте зарегистрируем провайдер планировщика задач (регистрация действует для всей подписки):

var schedulerServiceClient = new SchedulerManagementClient(credentials); 
var result = schedulerServiceClient.RegisterResourceProvider(); 
  
Console.WriteLine(result.RequestId); 
Console.WriteLine(result.StatusCode); 
Console.ReadLine();

После регистрации провайдера ресурсов появляется возможность запрашивать его свойства:

var schedulerServiceClient = new SchedulerManagementClient(credentials); 
var result = schedulerServiceClient.GetResourceProviderProperties(); 
foreach (var prop in result.Properties) 
{ 
     Console.WriteLine(prop.Key + ": " + prop.Value); 
} 
Console.ReadLine();

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



Коллекции задач (Job Collections)


Следующим шагом будет создание “Коллекции Задач” (Job Collection), которая является контейнером для хранения ваших задач и механизмом применения к ним различных квот. Кроме того, это то место, где вам необходимо выбрать бесплатный или платный уровни использования сервиса:
Коллекция задач содержит группу задач и управляет настройками, квотами и ограничениями, которые являются общими для всех задач в коллекции. Коллекция задач создается владельцем подписки, она объединяет задачи вместе, основываясь на потреблении или границах приложения. Коллекция задач ограничена пределами одного региона. Она так же позволяет установить квоты MaxJobs и MaxRecurrence на метрики потребления всех задач в коллекции.
Больше информации о квотах, которые могут быть использованы на обоих планах, вы можете найти по этой ссылке: http://msdn.microsoft.com/en-us/library/windowsazure/dn479786.aspx.

var schedulerServiceClient = new SchedulerManagementClient(credentials); 
var result = schedulerServiceClient.JobCollections.Create("sandrino-cs2", "jobcoll001", new JobCollectionCreateParameters() 
{ 
     Label =  "jobcoll001", 
     IntrinsicSettings = new JobCollectionIntrinsicSettings() 
     { 
         Plan = JobCollectionPlan.Standard, 
         Quota = new JobCollectionQuota() 
         { 
             MaxJobCount = 100, 
             MaxJobOccurrence = 100, 
             MaxRecurrence = new JobCollectionMaxRecurrence() 
             { 
                 Frequency = JobCollectionRecurrenceFrequency.Minute, 
                 Interval = 1 
             } 
         } 
     } 
}); 
  
Console.WriteLine(result.RequestId); 
Console.WriteLine(result.StatusCode); 
Console.ReadLine();

Задачи HTTP(S) и очередей хранилища


Теперь, обладая коллекцией задач, мы можем начать создавать задачи для планировщика. Планировщик задач в настоящий момент поддерживает три типа задач: HTTP, HTTPS и очередей хранилища. Давайте сначала посмотрим на поддержку http:

var schedulerClient = new SchedulerClient(credentials, "sandrino-cs2", "jobcoll001"); 
var result = schedulerClient.Jobs.Create(new JobCreateParameters() 
{ 
     Action = new JobAction() 
     { 
         Type = JobActionType.Http, 
         Request = new JobHttpRequest() 
         { 
             Body = "customer=sandrino&command=sendnewsletter", 
             Headers = new Dictionary() 
             { 
                 { "Content-Type", "application/x-www-form-urlencoded" }, 
                 { "x-something", "value123" } 
             }, 
             Method = "POST", 
             Uri = new Uri("http://postcatcher.in/catchers/527af9acfe325802000001cb") 
         } 
     }, 
     StartTime = DateTime.UtcNow, 
     Recurrence = new JobRecurrence() 
    { 
         Frequency = JobRecurrenceFrequency.Minute, 
         Interval = 1, 
         Count = 5 
     } 
}); 
  
Console.WriteLine(result.RequestId); 
Console.WriteLine(result.StatusCode); 
Console.ReadLine();

Эта задача создана в моей коллекции задач “jobcoll001”, она будет отправлять POST-запрос с определенным телом и заголовками запроса. Обратите внимание, что я указываю тип контента для запроса, так как это требуется для выполнения задач типа HTTP(S). Так как я использую платный план планировщика, мне позволено создавать задачи с повторением каждую минуту. Для этой демонстрации я ограничил задачу пятью исполнениями.

Наконец, если вы обратите внимание на URI, то заметите, что я использую сервис http://postcatcher.in. Это бесплатный сервис, который позволяет вам отлаживать POST-запросы, как раз именно то, чем я и занимаюсь. Давайте посмотрим на то, что делает планировщик задач:



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

{ 
     "connection": "close", 
     "content-length": "40", 
     "content-type": "application/x-www-form-urlencoded", 
     "host": "postcatcher.in", 
     "x-forwarded-for": "137.116.241.137", 
     "x-ms-client-request-id": "988c7a64-55e1-41e4-8cf0-ce1eeca240ac", 
     "x-ms-execution-tag": "0726fa245447c91674c75db3f3564d63", 
     "x-ms-scheduler-execution-region": "North Europe", 
     "x-ms-scheduler-expected-execution-time": "2013-11-07T02:39:27", 
     "x-ms-scheduler-jobcollectionid": "jobcoll001", 
     "x-ms-scheduler-jobid": "7ce6971c-5aa1-4701-b6bd-02f63ee82d17", 
     "x-real-ip": "137.116.241.137", 
     "x-request-start": "1383791968800", 
     "x-something": "value123" 
}

Теперь давайте изменим тип задачи на очередь хранилища.

var storageAccount = new CloudStorageAccount(new StorageCredentials("labdrino", ""), true); 
var queueClient = storageAccount.CreateCloudQueueClient(); 
var queue = queueClient.GetQueueReference("scheduled-tasks"); 
queue.CreateIfNotExists(); 
  
var perm = new QueuePermissions(); 
var policy = new SharedAccessQueuePolicy { SharedAccessExpiryTime = DateTime.MaxValue, Permissions = SharedAccessQueuePermissions.Add }; 
perm.SharedAccessPolicies.Add("jobcoll001policy", policy); 
  
queue.SetPermissions(perm); 
var sas = queue.GetSharedAccessSignature(new SharedAccessQueuePolicy(), "jobcoll001policy"); 
  
var schedulerClient = new SchedulerClient(credentials, "sandrino-cs2", "jobcoll001"); 
var result = schedulerClient.Jobs.Create(new JobCreateParameters() 
{ 
     Action = new JobAction() 
     { 
         Type = JobActionType.StorageQueue, 
         QueueMessage = new JobQueueMessage() 
         { 
             Message = "hello there!", 
             QueueName = "scheduled-tasks", 
             SasToken = sas, 
             StorageAccountName = "labdrino" 
         } 
     }, 
     StartTime = DateTime.UtcNow, 
     Recurrence = new JobRecurrence() 
     { 
         Frequency = JobRecurrenceFrequency.Minute, 
         Interval = 1, 
         Count = 5 
     } 
}); 
  
Console.WriteLine(result.RequestId); 
Console.WriteLine(result.StatusCode); 
Console.ReadLine();

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

Результатом выполнения кода будет сообщение в очереди, которое содержит информацию о задаче вместе с текстом сообщения “hello there!”:



История задач


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

var schedulerClient = new SchedulerClient(credentials, "sandrino-cs2", "jobcoll001"); 
   foreach (var job in schedulerClient.Jobs.List(new JobListParameters() { State = JobState.Enabled })) 
   { 
       Console.WriteLine("Job: {0} - Action: {1} - State: {2} - Status: {3}", job.Id, job.Action, job.State, job.Status); 
  
       foreach (var history in schedulerClient.Jobs.GetHistory(job.Id, new JobGetHistoryParameters())) 
       { 
           Console.WriteLine(" > {0} - {1}: {2}", history.StartTime, history.EndTime, history.Message); 
      } 
   } 
  
   Console.ReadLine();

Выполнив этот код вы получите результат похожий на следующий:
Job: 34851054-f576-48b8-8c77-73b62b502022 – Action: Microsoft.WindowsAzure.Scheduler.Models.JobAction – State: Faulted – Status: Microsoft.WindowsAzure.Scheduler.Models.JobStatus
> 7/11/2013 2:52:18 – 7/11/2013 2:52:19: StorageQueue Action – The provided queue: ‘scheduled-tasks’ does not exist or the Sas Token does not have permission to add a message to the given queue
> 7/11/2013 2:52:48 – 7/11/2013 2:52:50: StorageQueue Action – The provided queue: ‘scheduled-tasks’ does not exist or the Sas Token does not have permission to add a message to the given queue
> 7/11/2013 2:53:19 – 7/11/2013 2:53:19: StorageQueue Action – The provided queue: ‘scheduled-tasks’ does not exist or the Sas Token does not have permission to add a message to the given queue
> 7/11/2013 2:53:48 – 7/11/2013 2:53:50: StorageQueue Action – The provided queue: ‘scheduled-tasks’ does not exist or the Sas Token does not have permission to add a message to the given queue
> 7/11/2013 2:54:20 – 7/11/2013 2:54:20: StorageQueue Action – The provided queue: ‘scheduled-tasks’ does not exist or the Sas Token does not have permission to add a message to the given queue
> 7/11/2013 3:05:19 – 7/11/2013 3:05:19: StorageQueue Action – The provided queue: ‘scheduled-tasks’ does not exist or the Sas Token does not have permission to add a message to the given queue
> 7/11/2013 3:05:49 – 7/11/2013 3:05:49: StorageQueue Action – The provided queue: ‘scheduled-tasks’ does not exist or the Sas Token does not have permission to add a message to the given queue
> 7/11/2013 3:06:18 – 7/11/2013 3:06:19: StorageQueue Action – The provided queue: ‘scheduled-tasks’ does not exist or the Sas Token does not have permission to add a message to the given queue

Job: 4db6da21-af4a-4703-b988-671cbb6d5fd5 – Action: Microsoft.WindowsAzure.Scheduler.Models.JobAction – State: Completed – Status: Microsoft.WindowsAzure.Scheduler.Models.JobStatus
> 7/11/2013 2:32:13 – 7/11/2013 2:32:15: Http Action – Response from host ‘postcatcher.in’: ‘Created’ Response Headers: Connection: keep-alive
X-Response-Time: 6ms
Date: Thu, 07 Nov 2013 02:32:14 GMT
Set-Cookie: connect.sid=8SxhjZXandfZQc158Ng2tiYs.kyW9OSZGymzcIJW1eTJJ2MIACyhSyK6mfHVVqqj2r0E; path=/; expires=Thu, 07 Nov 2013 06:32:14 GMT; httpOnly
Server: nginx
X-Powered-By: Express
Body: Created
> 7/11/2013 2:33:14 – 7/11/2013 2:33:15: Http Action – Response from host ‘postcatcher.in’: ‘Created’ Response Headers: Connection: keep-alive
X-Response-Time: 18ms
Date: Thu, 07 Nov 2013 02:33:15 GMT
Set-Cookie: connect.sid=BJYkjeu3m26wBfr6G2SDgXZl.nhXEo24T3AVHEMYe4xJIm7gjDmhZvj69edIv4bui%2Bzs; path=/; expires=Thu, 07 Nov 2013 06:33:15 GMT; httpOnly
Server: nginx
X-Powered-By: Express
Body: Created
История задач может предоставить интересную информацию о исполнении задач. Если что-то пойдет не так во время выполнения задачи, история – это первое место откуда вы должны начать искать источник проблемы.

Повторения в случае ошибок (Retries)


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

var schedulerClient = new SchedulerClient(credentials, "sandrino-cs2", "jobcoll001"); 
var result = schedulerClient.Jobs.Create(new JobCreateParameters() 
{ 
     Action = new JobAction() 
     { 
         Type = JobActionType.Http, 
         Request = ..., 
         RetryPolicy = new RetryPolicy() 
         { 
             RetryCount = 5, 
             RetryInterval = TimeSpan.FromMinutes(1), 
             RetryType = RetryType.Fixed 
         } 
     }, 
     StartTime = DateTime.UtcNow, 
     Recurrence = ... 
});

В этом примере я указываю выполнять максимум пять повторов с периодичностью раз в минуту между ними.

Обработка ошибок


В случае когда что-то пошло не так, вы можете захотеть получить некий сигнал, например сообщение отправленное в другую очередь (либо в другой датацентр) или вызов другого URL. Все это возможно с помощью указания параметров специальной секции Error при создании задачи. В следующем примере я создаю задачу для очереди хранилища StorageQueue, но указываю ошибочную сигнатуру SAS. Это приведет к тому, что планировщик не сможет выполнить задачу отправки сообщения в очередь и будет вызвана секция ErrorAction (код в которой отправляет на мою страницу в postcatcher.in сообщение об ошибке).

var schedulerClient = new SchedulerClient(credentials, "sandrino-cs2", "jobcoll001"); 
var result = schedulerClient.Jobs.Create(new JobCreateParameters() 
{ 
     Action = new JobAction() 
     { 
         Type = JobActionType.StorageQueue, 
         QueueMessage = new JobQueueMessage() 
         { 
             Message = "hello there!", 
             QueueName = "scheduled-tasks", 
             SasToken = "not working", 
             StorageAccountName = "labdrino" 
        }, 
         ErrorAction = new JobErrorAction() 
         { 
            Type = JobActionType.Http, 
             Request = new JobHttpRequest() 
             { 
                 Uri = new Uri("http://postcatcher.in/catchers/527b0b75fe325802000002b6"), 
                 Body = "type=somethingiswrong", 
                 Headers = new Dictionary() 
                 { 
                     { "Content-Type", "application/x-www-form-urlencoded" }, 
                     { "x-something", "value123" } 
                 }, 
                 Method = "POST" 
             } 
         } 
     }, 
     StartTime = DateTime.UtcNow, 
     Recurrence = new JobRecurrence() 
     { 
         Frequency = JobRecurrenceFrequency.Minute, 
         Interval = 1, 
         Count = 5 
     } 
}); 
  
Console.WriteLine(result.RequestId); 
Console.WriteLine(result.StatusCode); 
Console.ReadLine();

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



Периодическое повторение задачи


Планировщик позволяет вам конфигурировать несколько типов периодического повторения задачи, например, запускать задачу каждый день, но максимум 10 раз (свойство Count):

var schedulerClient = new SchedulerClient(credentials, "sandrino-cs2", "jobcoll001"); 
var result = schedulerClient.Jobs.Create(new JobCreateParameters() 
{ 
     Action = ..., 
     Recurrence = new JobRecurrence() 
     { 
         Frequency = JobRecurrenceFrequency.Day, 
         Interval = 1, 
         Count = 10 
     } 
});

Вы можете указать, что задача должна выполняться каждый день до определенной даты:

var schedulerClient = new SchedulerClient(credentials, "sandrino-cs2", "jobcoll001"); 
var result = schedulerClient.Jobs.Create(new JobCreateParameters() 
{ 
     Action = ..., 
     Recurrence = new JobRecurrence() 
     { 
         Frequency = JobRecurrenceFrequency.Day, 
         Interval = 1, 
         EndTime = new DateTime(2013, 12, 31) 
     } 
});

Вместе с этим вы можете указывать и более сложные параметры периодического повторения задач. Например, для почтовой рассылки, вы можете указать выполнение задачи еженедельно в понедельник в 11:00 утра.

var schedulerClient = new SchedulerClient(credentials, "sandrino-cs2", "jobcoll001"); 
var result = schedulerClient.Jobs.Create(new JobCreateParameters() 
{ 
     Action = new JobAction() 
     { 
         Type = JobActionType.Http, 
         Request = new JobHttpRequest() 
         { 
             Body = "customers=Europe-West", 
             Headers = new Dictionary() 
             { 
                 { "Content-Type", "application/x-www-form-urlencoded" }, 
            }, 
             Method = "POST", 
             Uri = new Uri("http://postcatcher.in/catchers/527af9acfe325802000001cb") 
         } 
     }, 
     StartTime = DateTime.UtcNow, 
     Recurrence = new JobRecurrence() 
     { 
         // Frequency = JobRecurrenceFrequency.None, 
         Schedule = new JobRecurrenceSchedule() 
         { 
             Days = new List() { JobScheduleDay.Monday }, 
             Hours = new List() {  9 }, 
             Minutes =  new List() { 11 } 
         } 
     } 
});

Примечание. На сегодняшний день библиотека управления содержит небольшой баг, который не позволяет вам установить частоту (Frequency) значение None (или Schedule). Из-за этого, у вас не получится создать задачи с таким типом частоты.

Заключение


Windows Azure Scheduler обладает простым API, поддержкой очередей хранилища, политик повтора, обработки ошибок. Это делает новый сервис прекрасным инструментом для выполнения ваших задач по расписанию. Кроме того, новый сервис, открывает массу сценариев: планирование задач для вашего веб-сайта Windows Azure Web Site (без необходимости поднимать рабочие роли); отправка данных в вашу рабочую роль; использование планировщика в ваших приложениях на PHP, Node.js и т.д.; построение собственного агента для баз данных Windows Azure SQL Database и т.д.

Больше информации вы можете найти по следующим ссылкам:


Наслаждайтесь!
Tags:
Hubs:
+8
Comments 2
Comments Comments 2

Articles