Przechowywanie wersji w Durable Functions (Azure Functions)
Nieuniknione jest, że funkcje zostaną dodane, usunięte i zmienione w okresie istnienia aplikacji. Durable Functions umożliwia łączenie funkcji łańcuchowych w sposób, który nie był wcześniej możliwy, a to łańcuch ma wpływ na sposób obsługi obsługi wersji.
Jak obsługiwać zmiany powodujące niezgodność
Istnieje kilka przykładów zmian powodujących niezgodność. W tym artykule omówiono najbardziej typowe. Głównym motywem wszystkich z nich jest to, że zarówno nowe, jak i istniejące aranżacje funkcji mają wpływ na zmiany w kodzie funkcji.
Zmienianie podpisów funkcji działania lub jednostki
Zmiana podpisu odnosi się do zmiany nazwy, danych wejściowych lub wyjściowych funkcji. Jeśli tego rodzaju zmiana zostanie wprowadzona do działania lub funkcji jednostki, może to spowodować przerwanie dowolnej funkcji orkiestratora, która jest od niej zależna. Jest to szczególnie istotne w przypadku języków bezpiecznych pod kątem typów. Jeśli zaktualizujesz funkcję orkiestratora, aby uwzględnić tę zmianę, możesz przerwać istniejące wystąpienia w locie.
Załóżmy na przykład, że mamy następującą funkcję orkiestratora.
[FunctionName("FooBar")]
public static Task Run([OrchestrationTrigger] IDurableOrchestrationContext context)
{
bool result = await context.CallActivityAsync<bool>("Foo");
await context.CallActivityAsync("Bar", result);
}
Ta uproszczona funkcja pobiera wyniki foo i przekazuje ją do paska. Załóżmy, że musimy zmienić wartość zwracaną wartości Foo z wartości logicznej na ciąg w celu obsługi szerszej gamy wartości wyników. Wynik wygląda następująco:
[FunctionName("FooBar")]
public static Task Run([OrchestrationTrigger] IDurableOrchestrationContext context)
{
string result = await context.CallActivityAsync<string>("Foo");
await context.CallActivityAsync("Bar", result);
}
Ta zmiana działa prawidłowo dla wszystkich nowych wystąpień funkcji orkiestratora, ale może przerwać wszystkie wystąpienia w locie. Rozważmy na przykład przypadek, w którym wystąpienie orkiestracji wywołuje funkcję o nazwie Foo
, pobiera wartość logiczną, a następnie punkty kontrolne. Jeśli zmiana podpisu zostanie wdrożona w tym momencie, wystąpienie punktu kontrolnego zakończy się niepowodzeniem natychmiast po wznowieniu i odtworzeniu wywołania do Foo
. Ten błąd występuje, ponieważ wynik w tabeli historii jest wartością logiczną, ale nowy kod próbuje wykonać deserializacji go do wartości Ciąg, co powoduje nieoczekiwane zachowanie, a nawet wyjątek środowiska uruchomieniowego dla języków bezpiecznych od typu.
W tym przykładzie jest tylko jeden z wielu różnych sposobów, na które zmiana podpisu funkcji może przerwać istniejące wystąpienia. Ogólnie rzecz biorąc, jeśli orkiestrator musi zmienić sposób, w jaki wywołuje funkcję, zmiana może być problematyczna.
Zmiana logiki orkiestratora
Inna klasa problemów z przechowywaniem wersji pochodzi od zmiany kodu funkcji orkiestratora w sposób, który zmienia ścieżkę wykonywania dla wystąpień w locie.
Rozważmy następującą funkcję orkiestratora:
[FunctionName("FooBar")]
public static Task Run([OrchestrationTrigger] IDurableOrchestrationContext context)
{
bool result = await context.CallActivityAsync<bool>("Foo");
await context.CallActivityAsync("Bar", result);
}
Teraz załóżmy, że chcesz wprowadzić zmianę, aby dodać nowe wywołanie funkcji między dwoma istniejącymi wywołaniami funkcji.
[FunctionName("FooBar")]
public static Task Run([OrchestrationTrigger] IDurableOrchestrationContext context)
{
bool result = await context.CallActivityAsync<bool>("Foo");
if (result)
{
await context.CallActivityAsync("SendNotification");
}
await context.CallActivityAsync("Bar", result);
}
Ta zmiana powoduje dodanie nowego wywołania funkcji do funkcji SendNotification między elementami Foo i Bar. Brak zmian podpisu. Problem pojawia się, gdy istniejące wystąpienie zostanie wznowione z wywołania do paska. Podczas odtwarzania, jeśli oryginalne wywołanie do Foo zwróciło true
, replay orkiestratora wywoła funkcję SendNotification, która nie znajduje się w historii wykonywania. Środowisko uruchomieniowe wykrywa tę niespójność i zgłasza błąd orkiestracji niedeterministycznej, ponieważ napotkał wywołanie funkcji SendNotification, gdy oczekuje się, że wywołanie paska. Ten sam typ problemu może wystąpić podczas dodawania wywołań interfejsu API do innych trwałych operacji, takich jak tworzenie trwałych czasomierzy, oczekiwanie na zdarzenia zewnętrzne, wywoływanie podarancji itp.
Strategie ograniczania ryzyka
Poniżej przedstawiono niektóre strategie radzenia sobie z wyzwaniami związanymi z przechowywaniem wersji:
- Nic nie rób (niezalecane)
- Zatrzymywanie wszystkich wystąpień w locie
- Wdrożenia równoległe
Nie rób nic
Naiwne podejście do przechowywania wersji polega na tym, że nic nie zrobi i niech wystąpienia orkiestracji w locie nie powiedzie się. W zależności od typu zmiany mogą wystąpić następujące typy błędów.
- Orkiestracje mogą zakończyć się niepowodzeniem z powodu błędu orkiestracji niedeterministycznej .
- Aranżacje mogą utknąć bezterminowo, zgłaszając
Running
stan. - Jeśli funkcja zostanie usunięta, każda funkcja, która próbuje wywołać funkcję, może zakończyć się niepowodzeniem z powodu błędu.
- Jeśli funkcja zostanie usunięta po zaplanowanym uruchomieniu, aplikacja może napotkać błędy środowiska uruchomieniowego niskiego poziomu w aplikacji Durable Task Framework, co może spowodować poważne obniżenie wydajności.
Ze względu na te potencjalne błędy strategia "nie rób nic" nie jest zalecana.
Zatrzymywanie wszystkich wystąpień w locie
Inną opcją jest zatrzymanie wszystkich wystąpień w locie. Jeśli używasz domyślnego dostawcy usługi Azure Storage dla Durable Functions, zatrzymanie wszystkich wystąpień można wykonać przez wyczyszczenie zawartości wewnętrznych kolejek kontroli i kolejek workitem-queue. Możesz też zatrzymać aplikację funkcji, usunąć te kolejki i ponownie uruchomić aplikację. Kolejki zostaną automatycznie ponownie tworzone po ponownym uruchomieniu aplikacji. Poprzednie wystąpienia orkiestracji mogą pozostać w stanie "Uruchomione" na czas nieokreślony, ale nie będą one zaśmiecać dzienników komunikatami o błędach ani spowodować żadnych szkód w aplikacji. Takie podejście jest idealne w szybkim tworzeniu prototypów, w tym w rozwoju lokalnym.
Uwaga
Takie podejście wymaga bezpośredniego dostępu do podstawowych zasobów magazynu i nie jest odpowiednie dla wszystkich dostawców magazynu obsługiwanych przez Durable Functions.
Wdrożenia równoległe
Najbardziej odpornym na awarię sposobem zapewnienia, że zmiany powodujące niezgodność są wdrażane bezpiecznie, jest wdrożenie ich równolegle ze starszymi wersjami. Można to zrobić przy użyciu dowolnej z następujących technik:
- Wdróż wszystkie aktualizacje jako całkowicie nowe funkcje, pozostawiając istniejące funkcje zgodnie z rzeczywistymi stanami. Zazwyczaj nie jest to zalecane ze względu na złożoność związaną z cyklicznego aktualizowania elementów wywołujących nowe wersje funkcji.
- Wdróż wszystkie aktualizacje jako nową aplikację funkcji przy użyciu innego konta magazynu.
- Wdróż nową kopię aplikacji funkcji z tym samym kontem magazynu, ale zaktualizowaną nazwą centrum zadań . Spowoduje to utworzenie nowych artefaktów magazynu, które mogą być używane przez nową wersję aplikacji. Stara wersja aplikacji będzie nadal wykonywana przy użyciu poprzedniego zestawu artefaktów magazynu.
Wdrożenie równoległe jest zalecaną techniką wdrażania nowych wersji aplikacji funkcji.
Uwaga
Te wskazówki dotyczące strategii wdrażania równoległego używają terminów specyficznych dla usługi Azure Storage, ale mają zastosowanie ogólnie do wszystkich obsługiwanych dostawców magazynu Durable Functions.
Miejsca wdrożenia
W przypadku równoległych wdrożeń w Azure Functions lub Azure App Service zalecamy wdrożenie nowej wersji aplikacji funkcji w nowym miejscu wdrożenia. Miejsca wdrożenia umożliwiają uruchamianie wielu kopii aplikacji funkcji obok siebie tylko jednego z nich jako aktywnego miejsca produkcyjnego . Gdy wszystko będzie gotowe do udostępnienia nowej logiki aranżacji istniejącej infrastruktury, może to być tak proste, jak zamiana nowej wersji na miejsce produkcyjne.
Uwaga
Ta strategia działa najlepiej, gdy używasz wyzwalaczy HTTP i webhook dla funkcji orkiestratora. W przypadku wyzwalaczy innych niż HTTP, takich jak kolejki lub usługa Event Hubs, definicja wyzwalacza powinna pochodzić z ustawienia aplikacji , które jest aktualizowane w ramach operacji zamiany.