Monitorowanie scenariusza w usłudze Durable Functions — przykład obserwatora pogody
Wzorzec monitora odnosi się do elastycznego cyklicznego procesu w przepływie pracy — na przykład sondowania do momentu spełnienia określonych warunków. W tym artykule wyjaśniono przykład, który używa rozszerzenia Durable Functions do implementowania monitorowania.
Wymagania wstępne
Omówienie scenariusza
Ten przykład monitoruje bieżące warunki pogodowe w lokalizacji i ostrzega użytkownika za pomocą wiadomości SMS, gdy niebo jest jasne. Możesz użyć regularnej funkcji wyzwalanej przez czasomierz, aby sprawdzić pogodę i wysłać alerty. Jednak jednym z problemów z tym podejściem jest zarządzanie okresem istnienia. Jeśli powinien zostać wysłany tylko jeden alert, monitor musi wyłączyć się po wykryciu jasnej pogody. Wzorzec monitorowania może zakończyć własne wykonywanie, między innymi:
- Monitory są uruchamiane w interwałach, a nie harmonogramy: wyzwalacz czasomierza jest uruchamiany co godzinę; monitor czeka godzinę między akcjami. Akcje monitora nie nakładają się, chyba że zostaną określone, co może być ważne w przypadku długotrwałych zadań.
- Monitory mogą mieć interwały dynamiczne: czas oczekiwania może ulec zmianie na podstawie pewnego warunku.
- Monitory mogą zakończyć się, gdy jakiś warunek zostanie spełniony lub zostanie zakończony przez inny proces.
- Monitory mogą przyjmować parametry. W przykładzie pokazano, jak można zastosować ten sam proces monitorowania pogody do dowolnej żądanej lokalizacji i numeru telefonu.
- Monitory są skalowalne. Ponieważ każdy monitor jest wystąpieniem orkiestracji, można utworzyć wiele monitorów bez konieczności tworzenia nowych funkcji lub definiowania większej liczby kodu.
- Monitory łatwo integrują się z większymi przepływami pracy. Monitor może być jedną z bardziej złożonych funkcji aranżacji lub podaranżacji.
Konfigurowanie
Konfigurowanie integracji z usługą Twilio
Ten przykład obejmuje użycie usługi Twilio do wysyłania wiadomości SMS na telefon komórkowy. Usługa Azure Functions obsługuje już usługę Twilio za pośrednictwem powiązania usługi Twilio, a przykład używa tej funkcji.
Pierwszą rzeczą, której potrzebujesz, jest konto usługi Twilio. Możesz utworzyć jedną bezpłatnie na stronie https://www.twilio.com/try-twilio. Po utworzeniu konta dodaj następujące trzy ustawienia aplikacji do aplikacji funkcji.
Nazwa ustawienia aplikacji | Opis wartości |
---|---|
TwilioAccountSid | Identyfikator SID konta usługi Twilio |
TwilioAuthToken | Token uwierzytelniania dla konta usługi Twilio |
TwilioPhoneNumber | Numer telefonu skojarzony z kontem usługi Twilio. Służy do wysyłania wiadomości SMS. |
Konfigurowanie integracji z platformą Weather Underground
Ten przykład obejmuje użycie interfejsu API Weather Underground w celu sprawdzenia bieżących warunków pogodowych dla lokalizacji.
Pierwszą rzeczą, której potrzebujesz, jest konto Weather Underground. Możesz utworzyć go bezpłatnie na stronie https://www.wunderground.com/signup. Po utworzeniu konta musisz uzyskać klucz interfejsu API. Możesz to zrobić, odwiedzając stronę https://www.wunderground.com/weather/api, a następnie wybierając pozycję Ustawienia klucza. Plan Stratus Developer jest bezpłatny i wystarczający do uruchomienia tego przykładu.
Po utworzeniu klucza interfejsu API dodaj następujące ustawienie aplikacji do aplikacji funkcji.
Nazwa ustawienia aplikacji | Opis wartości |
---|---|
WeatherUndergroundApiKey | Klucz interfejsu API Weather Underground. |
Funkcje
W tym artykule opisano następujące funkcje w przykładowej aplikacji:
E3_Monitor
: Funkcja orkiestratora, która okresowo wywołujeE3_GetIsClear
funkcję. Wywołuje metodęE3_SendGoodWeatherAlert
ifE3_GetIsClear
zwraca wartość true.E3_GetIsClear
: funkcja działania, która sprawdza bieżące warunki pogodowe dla lokalizacji.E3_SendGoodWeatherAlert
: funkcja działania, która wysyła wiadomość SMS za pośrednictwem usługi Twilio.
E3_Monitor funkcja orkiestratora
[FunctionName("E3_Monitor")]
public static async Task Run([OrchestrationTrigger] IDurableOrchestrationContext monitorContext, ILogger log)
{
MonitorRequest input = monitorContext.GetInput<MonitorRequest>();
if (!monitorContext.IsReplaying) { log.LogInformation($"Received monitor request. Location: {input?.Location}. Phone: {input?.Phone}."); }
VerifyRequest(input);
DateTime endTime = monitorContext.CurrentUtcDateTime.AddHours(6);
if (!monitorContext.IsReplaying) { log.LogInformation($"Instantiating monitor for {input.Location}. Expires: {endTime}."); }
while (monitorContext.CurrentUtcDateTime < endTime)
{
// Check the weather
if (!monitorContext.IsReplaying) { log.LogInformation($"Checking current weather conditions for {input.Location} at {monitorContext.CurrentUtcDateTime}."); }
bool isClear = await monitorContext.CallActivityAsync<bool>("E3_GetIsClear", input.Location);
if (isClear)
{
// It's not raining! Or snowing. Or misting. Tell our user to take advantage of it.
if (!monitorContext.IsReplaying) { log.LogInformation($"Detected clear weather for {input.Location}. Notifying {input.Phone}."); }
await monitorContext.CallActivityAsync("E3_SendGoodWeatherAlert", input.Phone);
break;
}
else
{
// Wait for the next checkpoint
var nextCheckpoint = monitorContext.CurrentUtcDateTime.AddMinutes(30);
if (!monitorContext.IsReplaying) { log.LogInformation($"Next check for {input.Location} at {nextCheckpoint}."); }
await monitorContext.CreateTimer(nextCheckpoint, CancellationToken.None);
}
}
log.LogInformation($"Monitor expiring.");
}
[Deterministic]
private static void VerifyRequest(MonitorRequest request)
{
if (request == null)
{
throw new ArgumentNullException(nameof(request), "An input object is required.");
}
if (request.Location == null)
{
throw new ArgumentNullException(nameof(request.Location), "A location input is required.");
}
if (string.IsNullOrEmpty(request.Phone))
{
throw new ArgumentNullException(nameof(request.Phone), "A phone number input is required.");
}
}
Orkiestrator wymaga lokalizacji do monitorowania i numeru telefonu, aby wysłać wiadomość, gdy pogoda stanie się jasna w lokalizacji. Te dane są przekazywane do orkiestratora jako silnie typizowanego MonitorRequest
obiektu.
Ta funkcja orkiestratora wykonuje następujące akcje:
- Pobiera element MonitorRequest składający się z lokalizacji do monitorowania i numeru telefonu, do którego wysyła powiadomienie SMS.
- Określa czas wygaśnięcia monitora. W przykładzie użyto zakodowanej wartości w celu zwięzłości.
- Wzywa E3_GetIsClear , aby ustalić, czy w żądanej lokalizacji znajdują się jasne niebo.
- Jeśli pogoda jest jasna, połączenia E3_SendGoodWeatherAlert wysłać powiadomienie SMS do żądanego numeru telefonu.
- Tworzy trwały czasomierz w celu wznowienia aranżacji w następnym interwale sondowania. W przykładzie użyto zakodowanej wartości w celu zwięzłości.
- Trwa działanie do czasu wygaśnięcia bieżącego czasu UTC lub wysłania alertu SMS.
Wiele wystąpień orkiestratora może być uruchamianych jednocześnie, wywołując funkcję orkiestratora wiele razy. Można określić lokalizację monitorowania i numer telefonu do wysłania alertu SMS. Na koniec należy pamiętać, że funkcja orkiestratora nie jest uruchomiona* podczas oczekiwania na czasomierz, więc nie zostanie naliczona opłata.
funkcja działania E3_GetIsClear
Podobnie jak w przypadku innych przykładów, funkcje działania pomocnika są zwykłymi funkcjami korzystającymi z activityTrigger
powiązania wyzwalacza. Funkcja E3_GetIsClear pobiera bieżące warunki pogodowe przy użyciu interfejsu API Weather Underground i określa, czy niebo jest jasne.
[FunctionName("E3_GetIsClear")]
public static async Task<bool> GetIsClear([ActivityTrigger] Location location)
{
var currentConditions = await WeatherUnderground.GetCurrentConditionsAsync(location);
return currentConditions.Equals(WeatherCondition.Clear);
}
funkcja działania E3_SendGoodWeatherAlert
Funkcja E3_SendGoodWeatherAlert używa powiązania usługi Twilio do wysyłania wiadomości SMS z powiadomieniem użytkownika końcowego, że jest to dobry czas na spacer.
[FunctionName("E3_SendGoodWeatherAlert")]
public static void SendGoodWeatherAlert(
[ActivityTrigger] string phoneNumber,
ILogger log,
[TwilioSms(AccountSidSetting = "TwilioAccountSid", AuthTokenSetting = "TwilioAuthToken", From = "%TwilioPhoneNumber%")]
out CreateMessageOptions message)
{
message = new CreateMessageOptions(new PhoneNumber(phoneNumber));
message.Body = $"The weather's clear outside! Go take a walk!";
}
internal class WeatherUnderground
{
private static readonly HttpClient httpClient = new HttpClient();
private static IReadOnlyDictionary<string, WeatherCondition> weatherMapping = new Dictionary<string, WeatherCondition>()
{
{ "Clear", WeatherCondition.Clear },
{ "Overcast", WeatherCondition.Clear },
{ "Cloudy", WeatherCondition.Clear },
{ "Clouds", WeatherCondition.Clear },
{ "Drizzle", WeatherCondition.Precipitation },
{ "Hail", WeatherCondition.Precipitation },
{ "Ice", WeatherCondition.Precipitation },
{ "Mist", WeatherCondition.Precipitation },
{ "Precipitation", WeatherCondition.Precipitation },
{ "Rain", WeatherCondition.Precipitation },
{ "Showers", WeatherCondition.Precipitation },
{ "Snow", WeatherCondition.Precipitation },
{ "Spray", WeatherCondition.Precipitation },
{ "Squall", WeatherCondition.Precipitation },
{ "Thunderstorm", WeatherCondition.Precipitation },
};
internal static async Task<WeatherCondition> GetCurrentConditionsAsync(Location location)
{
var apiKey = Environment.GetEnvironmentVariable("WeatherUndergroundApiKey");
if (string.IsNullOrEmpty(apiKey))
{
throw new InvalidOperationException("The WeatherUndergroundApiKey environment variable was not set.");
}
var callString = string.Format("http://api.wunderground.com/api/{0}/conditions/q/{1}/{2}.json", apiKey, location.State, location.City);
var response = await httpClient.GetAsync(callString);
var conditions = await response.Content.ReadAsAsync<JObject>();
JToken currentObservation;
if (!conditions.TryGetValue("current_observation", out currentObservation))
{
JToken error = conditions.SelectToken("response.error");
if (error != null)
{
throw new InvalidOperationException($"API returned an error: {error}.");
}
else
{
throw new ArgumentException("Could not find weather for this location. Try being more specific.");
}
}
return MapToWeatherCondition((string)(currentObservation as JObject).GetValue("weather"));
}
private static WeatherCondition MapToWeatherCondition(string weather)
{
foreach (var pair in weatherMapping)
{
if (weather.Contains(pair.Key))
{
return pair.Value;
}
}
return WeatherCondition.Other;
}
}
Uwaga
Aby uruchomić przykładowy kod, należy zainstalować Microsoft.Azure.WebJobs.Extensions.Twilio
pakiet Nuget.
Uruchamianie aplikacji przykładowej
Korzystając z funkcji wyzwalanych przez protokół HTTP zawartych w przykładzie, możesz uruchomić aranżację, wysyłając następujące żądanie HTTP POST:
POST https://{host}/orchestrators/E3_Monitor
Content-Length: 77
Content-Type: application/json
{ "location": { "city": "Redmond", "state": "WA" }, "phone": "+1425XXXXXXX" }
HTTP/1.1 202 Accepted
Content-Type: application/json; charset=utf-8
Location: https://{host}/runtime/webhooks/durabletask/instances/f6893f25acf64df2ab53a35c09d52635?taskHub=SampleHubVS&connection=Storage&code={SystemKey}
RetryAfter: 10
{"id": "f6893f25acf64df2ab53a35c09d52635", "statusQueryGetUri": "https://{host}/runtime/webhooks/durabletask/instances/f6893f25acf64df2ab53a35c09d52635?taskHub=SampleHubVS&connection=Storage&code={systemKey}", "sendEventPostUri": "https://{host}/runtime/webhooks/durabletask/instances/f6893f25acf64df2ab53a35c09d52635/raiseEvent/{eventName}?taskHub=SampleHubVS&connection=Storage&code={systemKey}", "terminatePostUri": "https://{host}/runtime/webhooks/durabletask/instances/f6893f25acf64df2ab53a35c09d52635/terminate?reason={text}&taskHub=SampleHubVS&connection=Storage&code={systemKey}"}
Wystąpienie E3_Monitor uruchamia się i wykonuje zapytanie dotyczące bieżących warunków pogodowych dla żądanej lokalizacji. Jeśli pogoda jest jasna, wywołuje funkcję działania w celu wysłania alertu; w przeciwnym razie ustawia czasomierz. Po wygaśnięciu czasomierza orkiestracja zostanie wznowione.
Działanie orkiestracji można wyświetlić, przeglądając dzienniki funkcji w witrynie Azure Functions Portal.
2018-03-01T01:14:41.649 Function started (Id=2d5fcadf-275b-4226-a174-f9f943c90cd1)
2018-03-01T01:14:42.741 Started orchestration with ID = '1608200bb2ce4b7face5fc3b8e674f2e'.
2018-03-01T01:14:42.780 Function completed (Success, Id=2d5fcadf-275b-4226-a174-f9f943c90cd1, Duration=1111ms)
2018-03-01T01:14:52.765 Function started (Id=b1b7eb4a-96d3-4f11-a0ff-893e08dd4cfb)
2018-03-01T01:14:52.890 Received monitor request. Location: Redmond, WA. Phone: +1425XXXXXXX.
2018-03-01T01:14:52.895 Instantiating monitor for Redmond, WA. Expires: 3/1/2018 7:14:52 AM.
2018-03-01T01:14:52.909 Checking current weather conditions for Redmond, WA at 3/1/2018 1:14:52 AM.
2018-03-01T01:14:52.954 Function completed (Success, Id=b1b7eb4a-96d3-4f11-a0ff-893e08dd4cfb, Duration=189ms)
2018-03-01T01:14:53.226 Function started (Id=80a4cb26-c4be-46ba-85c8-ea0c6d07d859)
2018-03-01T01:14:53.808 Function completed (Success, Id=80a4cb26-c4be-46ba-85c8-ea0c6d07d859, Duration=582ms)
2018-03-01T01:14:53.967 Function started (Id=561d0c78-ee6e-46cb-b6db-39ef639c9a2c)
2018-03-01T01:14:53.996 Next check for Redmond, WA at 3/1/2018 1:44:53 AM.
2018-03-01T01:14:54.030 Function completed (Success, Id=561d0c78-ee6e-46cb-b6db-39ef639c9a2c, Duration=62ms)
Orkiestracja kończy się po osiągnięciu limitu czasu lub wykryciu jasnego nieba. Możesz również użyć interfejsu terminate
API wewnątrz innej funkcji lub wywołać element webhook HTTP POST terminatePostUri , do którego odwołuje się poprzednia odpowiedź 202. Aby użyć elementu webhook, zastąp {text}
element przyczyną wczesnego zakończenia. Adres URL HTTP POST wygląda mniej więcej w następujący sposób:
POST https://{host}/runtime/webhooks/durabletask/instances/f6893f25acf64df2ab53a35c09d52635/terminate?reason=Because&taskHub=SampleHubVS&connection=Storage&code={systemKey}
Następne kroki
W tym przykładzie pokazano, jak używać rozszerzenia Durable Functions do monitorowania stanu źródła zewnętrznego przy użyciu trwałych czasomierzy i logiki warunkowej. W następnym przykładzie pokazano, jak używać zdarzeń zewnętrznych i trwałych czasomierzy do obsługi interakcji człowieka.