Udostępnij za pośrednictwem


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łuje E3_GetIsClear funkcję. Wywołuje metodę E3_SendGoodWeatherAlert if E3_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:

  1. Pobiera element MonitorRequest składający się z lokalizacji do monitorowania i numeru telefonu, do którego wysyła powiadomienie SMS.
  2. Określa czas wygaśnięcia monitora. W przykładzie użyto zakodowanej wartości w celu zwięzłości.
  3. Wzywa E3_GetIsClear , aby ustalić, czy w żądanej lokalizacji znajdują się jasne niebo.
  4. Jeśli pogoda jest jasna, połączenia E3_SendGoodWeatherAlert wysłać powiadomienie SMS do żądanego numeru telefonu.
  5. 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.
  6. 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.