
Graceful Shutdown jest mechanizmem, który pozwala na zamknięcie aplikacji w “poprawny” sposób. Ale co tak naprawdę oznacza, że zamykamy aplikację w “poprawny” sposób? Odpowiedzi na to pytanie będziemy szukać w dzisiejszym artykule. Implementację mechanizmu Graceful Shutdown oprzemy na przykładzie aplikacji napisanej przy wykorzystaniu Spring Boot 2.
Graceful Shutdown
Wyobraźmy sobie sytuację, w której na naszym klastrze mamy trzy instancje aplikacji w wersji 1.0.0. Po pewnym czasie wydajemy nową wersję 1.1.0, więc pora na migrację działających instancji. Istnieje wiele mechanizmów przełączania ruchu i zmiany wersji, jednakże zawsze musimy pamiętać o tym, aby przed zamknięciem zakończyć wszystkie rozpoczęte procesy. Poprawna obsługa tych rozpoczętych procesów określana jest jako mechanizm Graceful Shutdown.
Najczęściej realizuje się to w ten sposób, że w momencie otrzymania sygnału o zamknięciu aplikacji (na przykład SIGTERM), nasza aplikacja przestaje przyjmować nowy ruch i czeka na zakończenie wszystkich trwających procesów. Należy uwzględnić tutaj także sytuacje wyjątkowe, w których, pomimo odroczonego zamknięcia, jakiś proces nadal się wykonuje. W takim przypadku najlepiej zapisać to przetwarzanie w bazie danych i wykonać operację jeszcze raz na nowej wersji. Poniżej znajduje się przykład realizacji Graceful Shutdown z wykorzystaniem Spring Boota.
Długie zadanie
Na początku dodajmy endpoint /long symulujący długo wykonującą się pracę oraz /veryLong, który przekroczy czas naszego Graceful Shutdown:
1 |
|
Connector
Następnie musimy zaimplementować ConnectorCustomizer. Będzie on wywoływany wtedy, gdy będziemy chcieli zamknąć kontener serwletów:
1 |
|
Factory Customizer
Kolejny krok to rejestracja Beana TomcatGracefulShutdownConnector w kontenerze serwletów. Możemy to zrealizować za pomocą WebServerFactoryCustomizer, który jest parametryzowany odpowiednim factory. Właściwe factory zależy od tego, na jakim serwerze uruchamiamy aplikację. Będą to odpowiednio:
TomcatServletWebServerFactory- dla TomcataJettyServletWebServerFactory- dla JettyNettyReactiveWebServerFactory- dla NettyUndertowServletWebServerFactorydla Undertow
W tym przykładzie wykorzystamy TomcatServletWebServerFactory, w którym nadpiszemy metodę customize. W metodzie tej dodamy nasz ConnectorCustomizer:
1 |
|
Obsługa zdarzeń
Na sam koniec dodamy obsługę mechanizmu Graceful Shutdown. Reaguje on na zdarzenie ContextClosedEvent, które występuje w momencie zgłoszenia zamknięcia aplikacji. Następnie pobieramy pulę wątków z naszego kontenera serwletów i ją zamykamy. Na zamknięcie puli czekamy maksymalnie trzydzieści sekund (ten czas zależy od nas), dzięki czemu mamy możliwość dokończenia zadań:
1 |
|
Testowanie
Teraz pora na uruchomienie aplikacji i sprawdzenie naszego mechanizmu. Po uruchomieniu udajemy się na adres /long i w terminalu przerywamy proces (przykładowo pod systemem Windows jest to CTRL + C):
1 | 2019-04-23 20:31:31.617 : Start |
Jak widzicie powyżej, sygnał o zamknięciu aplikacji został wysłany, a mimo to została ona zamknięta dopiero po wykonaniu wszystkich zadań (lub po upływie trzydziestu sekund). Sprawdźmy teraz działanie dla /veryLong:
1 | 2019-04-23 20:35:02.656 : Start |
Jak widzicie, dodanie wsparcia dla Graceful Shutdown jest bardzo proste. Jednakże, moim zdaniem przydałoby się jakieś natywne wsparcie dla tego rozwiązania (na przykład w projekcie Actuator). Niestety, na razie musi wystarczyć nam taka “rzeźba”.
Github
Całość jak zawsze na GitHubie.