Historia architektury Dodo IS: wczesny monolit

Albo każda nieszczęśliwa firma z monolitem jest nieszczęśliwa na swój sposób.

Rozwój systemu Dodo IS rozpoczął się od razu, podobnie jak biznes Dodo Pizza, w 2011 roku. Został on oparty na idei pełnej i totalnej cyfryzacji procesów biznesowych oraz na własną rękę, co już wtedy w 2011 roku budziło wiele pytań i sceptycyzmu. Ale już od 9 lat podążamy tą drogą - z własnym rozwojem, który zaczął się od monolitu.

Ten artykuł jest „odpowiedzią” na pytania „Po co przepisywać architekturę i wprowadzać tak duże i długoterminowe zmiany?” wróć do poprzedniego artykułu „Historia architektury Dodo IS: Droga Back Office”. Zacznę od tego, jak zaczął się rozwój Dodo IS, jak wyglądała pierwotna architektura, jak pojawiły się nowe moduły iz powodu jakich problemów musiały zostać wprowadzone zmiany na dużą skalę.

Historia architektury Dodo IS: wczesny monolit

Seria artykułów „Czym jest Dodo IS?” opowiada o:

  1. Wczesny monolit w Dodo IS (2011-2015). (jesteś tutaj)

  2. Ścieżka Back Office: oddzielne bazy i magistrala.

  3. Ścieżka od strony klienta: elewacja nad bazą (2016-2017). (W trakcie...)

  4. Historia prawdziwych mikroserwisów. (2018-2019). (W trakcie…)

  5. Zakończono piłowanie monolitu i stabilizację architektury. (W trakcie…)

Początkowa architektura

W 2011 roku architektura Dodo IS wyglądała tak:

Historia architektury Dodo IS: wczesny monolit

Pierwszym modułem w architekturze jest akceptacja zleceń. Proces biznesowy wyglądał następująco:

  • klient dzwoni do pizzerii;

  • kierownik odbiera telefon;

  • przyjmuje zamówienie telefonicznie;

  • wypełnia go równolegle w interfejsie akceptacji zamówienia: uwzględnia informacje o kliencie, dane dotyczące szczegółów zamówienia, adres dostawy. 

Interfejs systemu informatycznego wyglądał mniej więcej tak...

Pierwsza wersja z października 2011:

Nieznaczna poprawa w styczniu 2012 r

Dodo Pizza Information System Delivery Pizza Restaurant

Zasoby na rozwój modułu przyjmowania pierwszych zamówień były ograniczone. Musieliśmy zrobić dużo, szybko i małym zespołem. Mały zespół to 2 programistów, którzy położyli podwaliny pod cały przyszły system.

Ich pierwsza decyzja zadecydowała o losie stosu technologii:

  • Backend w ASP.NET MVC, język C#. Deweloperzy byli dotnetchiki, ten stos był dla nich znajomy i przyjemny.

  • Frontend na Bootstrap i JQuery: interfejsy użytkownika oparte na samodzielnie napisanych stylach i skryptach. 

  • Baza danych MySQL: brak kosztów licencji, łatwa w użyciu.

  • Serwery na Windows Server, bo wtedy .NET mógłby być tylko pod Windows (nie będziemy omawiać Mono).

Fizycznie wszystko to zostało wyrażone w „poświęceniu u gospodarza”. 

Zamów architekturę aplikacji do przyjmowania

Wtedy wszyscy mówili już o mikroserwisach, a SOA było używane w dużych projektach przez 5 lat, na przykład WCF został wydany w 2006 roku. Ale potem wybrali niezawodne i sprawdzone rozwiązanie.

Oto jest.

Historia architektury Dodo IS: wczesny monolit

Asp.Net MVC to Razor, który na żądanie formularza lub klienta renderuje stronę HTML z renderowaniem serwerowym. Na kliencie skrypty CSS i JS wyświetlają już informacje iw razie potrzeby wykonują żądania AJAX przez JQuery.

Żądania na serwerze trafiają do klas *Controller, gdzie w metodzie odbywa się przetwarzanie i generowanie końcowej strony HTML. Kontrolery wysyłają żądania do warstwy logiki o nazwie *Usługi. Każda z usług odpowiadała jakiemuś aspektowi działalności:

  • Na przykład DepartmentStructureService podawał informacje o pizzeriach, o działach. Oddział to grupa pizzerii prowadzona przez jednego franczyzobiorcę.

  • ReceivingOrdersService przyjął i obliczył skład zamówienia.

  • A SmsService wysłał SMS-a, dzwoniąc do usług API w celu wysłania SMS-a.

Usługi przetwarzały dane z bazy danych, przechowywały logikę biznesową. Każda usługa miała jedno lub więcej *Repozytoriów o odpowiedniej nazwie. Zawierały już zapytania do procedur składowanych w bazie danych i warstwę maperów. W magazynach była logika biznesowa, szczególnie dużo w tych, które wydawały dane raportowe. ORM nie był używany, wszyscy polegali na ręcznie pisanym sql. 

Istniała również warstwa modelu domenowego i wspólne klasy pomocnicze, na przykład klasa Order przechowująca zamówienie. W tym samym miejscu, w warstwie, znajdował się pomocnik do przeliczania wyświetlanego tekstu według wybranej waluty.

Wszystko to można przedstawić za pomocą takiego modelu:

Historia architektury Dodo IS: wczesny monolit

Zamów sposób

Rozważ uproszczony początkowy sposób tworzenia takiego zamówienia.

Historia architektury Dodo IS: wczesny monolit

Początkowo strona była statyczna. Były na niej ceny, a na górze numer telefonu i napis „Chcesz pizzę – zadzwoń pod numer i zamów”. Aby zamówić, musimy zaimplementować prosty przepływ: 

  • Klient odwiedza statyczną stronę z cenami, wybiera produkty i dzwoni pod podany na stronie numer.

  • Klient nazywa produkty, które chce dodać do zamówienia.

  • Podaje swój adres i nazwisko.

  • Operator przyjmuje zamówienie.

  • Zamówienie jest wyświetlane w interfejsie zaakceptowanych zamówień.

Wszystko zaczyna się od wyświetlenia menu. Zalogowany operator-użytkownik przyjmuje jednorazowo tylko jedno zlecenie. W związku z tym wózek roboczy może być przechowywany w jego sesji (sesja użytkownika jest przechowywana w pamięci). Istnieje obiekt Koszyk zawierający produkty i informacje o kliencie.

Klient nazywa produkt, operator klika + obok produktu, a żądanie jest wysyłane do serwera. Informacja o produkcie jest pobierana z bazy danych i informacja o produkcie jest dodawana do koszyka.

Historia architektury Dodo IS: wczesny monolit

Operacja. Tak, tutaj nie możesz wyciągnąć produktu z bazy, ale przenieść go z frontendu. Ale dla jasności pokazałem dokładnie ścieżkę z bazy danych. 

Następnie wprowadź adres i nazwę klienta. 

Historia architektury Dodo IS: wczesny monolit

Po kliknięciu „Utwórz zamówienie”:

  • Żądanie jest wysyłane do OrderController.SaveOrder().

  • Dostajemy Koszyk z sesji, są produkty w ilości jakiej potrzebujemy.

  • Uzupełniamy Koszyk o informację o kliencie i przekazujemy ją do metody AddOrder klasy ReceivingOrderService, gdzie zostaje ona zapisana do bazy danych. 

  • Baza danych zawiera tabele ze zleceniem, składem zlecenia, klientem i wszystkie są ze sobą połączone.

  • Interfejs wyświetlania zamówień idzie i wyciąga najnowsze zamówienia i wyświetla je.

Nowe moduły

Przyjęcie zamówienia było ważne i konieczne. Nie możesz robić pizzerii, jeśli nie masz zamówienia na sprzedaż. Dlatego system zaczął nabierać funkcjonalności - mniej więcej od 2012 do 2015 roku. W tym czasie pojawiło się wiele różnych bloków systemu, które będę nazywać moduły, w przeciwieństwie do pojęcia usługi lub produktu. 

Moduł to zestaw funkcji, które łączy wspólny cel biznesowy. Jednocześnie fizycznie znajdują się w tej samej aplikacji.

Moduły można nazwać blokami systemowymi. Na przykład jest to moduł raportowania, interfejsy administratora, lokalizator żywności w kuchni, autoryzacja. Są to różne interfejsy użytkownika, niektóre mają nawet różne style wizualne. Jednocześnie wszystko odbywa się w ramach jednej aplikacji, jednego działającego procesu. 

Technicznie rzecz biorąc, moduły zostały zaprojektowane jako Area (taki pomysł pozostał nawet w rdzeń asp.net). Były osobne pliki dla frontendu, modeli, a także własnych klas kontrolerów. W rezultacie system został przekształcony z tego ...

Historia architektury Dodo IS: wczesny monolit

...zaangażowany w to:

Historia architektury Dodo IS: wczesny monolit

Niektóre moduły są wdrażane przez oddzielne witryny (projekt wykonywalny), ze względu na całkowicie oddzielną funkcjonalność, a częściowo z powodu nieco oddzielnego, bardziej ukierunkowanego rozwoju. Ten:

  • teren - pierwsza wersja strona dodopizza.ru.

  • Export: przesyłanie raportów z Dodo IS dla 1C. 

  • personel - konto osobiste pracownika. Został opracowany oddzielnie i ma własny punkt wejścia oraz oddzielny projekt.

  • fs — projekt do hostingu statyki. Później odeszliśmy od tego, przenosząc całą statykę do Akamai CDN. 

Reszta bloków była w aplikacji BackOffice. 

Historia architektury Dodo IS: wczesny monolit

Wyjaśnienie nazwy:

  • Kasjer – kasjer w restauracji.

  • ShiftManager - interfejsy dla roli "Shift Manager": statystyki operacyjne dotyczące sprzedaży pizzerii, możliwość umieszczania produktów na liście stop, zmiana kolejności.

  • OfficeManager - interfejsy dla ról "Pizzeria Manager" i "Franczyzobiorca". Oto zebrane funkcje do zakładania pizzerii, jej promocji bonusowych, przyjmowania i pracy z pracownikami, raportów.

  • PublicScreens - interfejsy do telewizorów i tabletów wiszących w pizzeriach. Telewizory wyświetlają menu, informacje reklamowe, status zamówienia przy dostawie. 

Wykorzystali wspólną warstwę usług, wspólny blok klas domeny Dodo.Core i wspólną bazę. Czasami nadal mogli prowadzić wzdłuż przejść do siebie. W tym poszczególne witryny, takie jak dodopizza.ru lub personal.dodopizza.ru, trafiły do ​​\uXNUMXb\uXNUMXbusług ogólnych.

Kiedy pojawiły się nowe moduły, staraliśmy się maksymalnie wykorzystać już utworzony kod usług, procedur składowanych i tabel w bazie danych. 

Dla lepszego zrozumienia skali modułów wykonanych w systemie, poniżej schemat z 2012 roku wraz z planami rozwojowymi:

Historia architektury Dodo IS: wczesny monolit

Do 2015 roku wszystko było na mapie, a jeszcze więcej było w produkcji.

  • Przyjmowanie zamówień rozrosło się do osobnego bloku Contact Center, gdzie zamówienie jest przyjmowane przez operatora.

  • W pizzeriach wisiały ogólnodostępne ekrany z menu i informacjami.

  • Kuchnia posiada moduł, który automatycznie odtwarza komunikat głosowy „Nowa Pizza” w momencie nadejścia nowego zamówienia, a także drukuje fakturę dla kuriera. To znacznie upraszcza procesy w kuchni, pozwala pracownikom nie rozpraszać się dużą liczbą prostych operacji.

  • Jednostka dostawcza stała się osobną Kasą Dostawy, gdzie zamówienie wydawane było kurierowi, który wcześniej pełnił dyżur. Przy obliczaniu wynagrodzenia uwzględniono jego czas pracy. 

Równolegle w latach 2012-2015 pojawiło się ponad 10 deweloperów, otwarto 35 pizzerii, wdrożono system w Rumunii i przygotowywano się do otwarcia placówek w Stanach Zjednoczonych. Deweloperzy nie zajmowali się już wszystkimi zadaniami, ale zostali podzieleni na zespoły. każdy specjalizował się we własnej części systemu. 

Problemy

Między innymi ze względu na architekturę (ale nie tylko).

Chaos w bazie

Jedna baza jest wygodna. Można w nim osiągnąć spójność, i to kosztem narzędzi wbudowanych w relacyjne bazy danych. Praca z nim jest znana i wygodna, zwłaszcza jeśli jest mało tabel i mało danych.

Ale w ciągu 4 lat rozwoju baza danych okazała się mieć około 600 tabel, 1500 procedur przechowywanych, z których wiele miało również logikę. Niestety, procedury składowane nie przynoszą większych korzyści podczas pracy z MySQL. Nie są buforowane przez bazę, a przechowywanie w nich logiki komplikuje programowanie i debugowanie. Ponowne użycie kodu jest również trudne.

Wiele tabel nie miało odpowiednich indeksów, gdzieś wręcz przeciwnie, było dużo indeksów, co utrudniało wstawianie. Trzeba było zmodyfikować około 20 tabel - transakcja tworząca zlecenie mogła zająć około 3-5 sekund. 

Dane w tabelach nie zawsze były w najbardziej odpowiedniej formie. Gdzieś trzeba było zrobić denormalizację. Część regularnie otrzymywanych danych znajdowała się w kolumnie w postaci struktury XML, co wydłużało czas wykonania, wydłużało zapytania i komplikowało rozwój.

Do tych samych stołów wyprodukowano bardzo żądania heterogeniczne. Szczególnie ucierpiały popularne stoły, takie jak wspomniany wyżej. Zlecenia lub stoły pizzeria. Służyły do ​​wyświetlania interfejsów operacyjnych w kuchni, analityki. Inna witryna skontaktowała się z nimi (dodopizza.ru), gdzie w dowolnym momencie mogło nagle pojawić się wiele próśb. 

Dane nie były agregowane a wiele obliczeń odbywało się na bieżąco z wykorzystaniem bazy. Spowodowało to niepotrzebne obliczenia i dodatkowe obciążenie. 

Często kod trafiał do bazy danych, gdy nie mógł tego zrobić. Gdzieś nie było wystarczającej liczby operacji masowych, gdzieś konieczne byłoby rozłożenie jednego żądania na kilka w kodzie, aby przyspieszyć i zwiększyć niezawodność. 

Spójność i zaciemnianie kodu

Moduły, które miały odpowiadać za swoją część biznesu, nie zrobiły tego uczciwie. Niektóre z nich miały powielanie funkcji dla ról. Na przykład lokalny marketer, który odpowiada za działania marketingowe sieci w swoim mieście, musiał używać zarówno interfejsu „Administrator” (do tworzenia promocji), jak i interfejsu „Menedżer biura” (do przeglądania wpływu promocji na biznes). Oczywiście wewnątrz obu modułów zastosowano tę samą usługę, która działała z promocjami bonusowymi.

Usługi (klasy w ramach jednego monolitycznego dużego projektu) mogą nawiązywać połączenia w celu wzbogacenia swoich danych.

Z samymi klasami modeli, które przechowują dane, praca w kodzie przebiegała inaczej. Gdzieś były konstruktory, za pomocą których można było określić wymagane pola. Gdzieś odbywało się to za pośrednictwem właściwości publicznych. Oczywiście pobieranie i przekształcanie danych z bazy było zróżnicowane. 

Logika znajdowała się albo w kontrolerach, albo w klasach usług. 

Wydaje się, że to drobne problemy, ale znacznie spowolniły rozwój i obniżyły jakość, prowadząc do niestabilności i błędów. 

Złożoność dużego rozwoju

Trudności pojawiły się w samym rozwoju. Konieczne było wykonanie różnych bloków systemu i równolegle. Dopasowanie potrzeb każdego komponentu do jednego kodu stawało się coraz trudniejsze. Nie było łatwo dojść do porozumienia i zadowolić jednocześnie wszystkie komponenty. Do tego doszły ograniczenia technologiczne, zwłaszcza w odniesieniu do bazy i frontendu. Konieczna była rezygnacja z jQuery na rzecz frameworków wysokiego poziomu, szczególnie w zakresie obsługi klienta (strona www).

W niektórych częściach systemu można zastosować bardziej odpowiednie do tego bazy danych.. Na przykład później mieliśmy przypadek użycia przejścia z Redis do CosmosDB w celu przechowywania koszyka zamówień. 

Zespoły i programiści zaangażowani w swoją dziedzinę wyraźnie chcieli większej autonomii dla swoich usług, zarówno pod względem rozwoju, jak i wdrażania. Konflikty scalania, problemy z wydaniami. Jeśli dla 5 deweloperów ten problem jest nieistotny, to przy 10, a tym bardziej przy planowanym wzroście, wszystko stanie się poważniejsze. A naprzód miał być rozwój aplikacji mobilnej (rozpoczął się w 2017 r., a w 2018 r. duży upadek). 

Różne części systemu wymagały różnych poziomów stabilności, ale ze względu na silną łączność systemu nie mogliśmy tego zapewnić. Równie dobrze mogło dojść do błędu w opracowaniu nowej funkcji w panelu administracyjnym przy przyjmowaniu zamówienia na stronie, ponieważ kod jest wspólny i wielokrotnego użytku, baza danych i dane też są takie same.

Prawdopodobnie dałoby się uniknąć tych błędów i problemów w ramach takiej monolityczno-modułowej architektury: dokonać podziału odpowiedzialności, refaktoryzować zarówno kod, jak i bazę danych, wyraźnie oddzielić warstwy od siebie i codziennie monitorować jakość . Jednak wybrane rozwiązania architektoniczne i nastawienie na szybką rozbudowę funkcjonalności systemu doprowadziły do ​​problemów ze stabilnością.

Jak blog Power of the Mind umieścił kasy fiskalne w restauracjach

Gdyby wzrost sieci pizzerii (i obciążenia) postępował w tym samym tempie, to po jakimś czasie spadki byłyby takie, że system by się nie podniósł. Dobrze ilustruje problemy, z którymi zaczęliśmy się borykać do 2015 roku, oto taka historia. 

na blogu"Siła umysłu” był widżetem, który pokazywał dane o przychodach za rok całej sieci. Widżet uzyskał dostęp do publicznego API Dodo, które udostępnia te dane. Ta statystyka jest obecnie dostępna pod adresem http://dodopizzastory.com/. Widżet był wyświetlany na każdej stronie i wysyłał żądania do timera co 20 sekund. Żądanie trafiło do api.dodopizza.ru i zażądało:

  • liczba pizzerii w sieci;

  • łączne przychody sieci od początku roku;

  • przychód na dzień dzisiejszy.

Żądanie statystyk przychodów trafiło prosto do bazy danych i zaczęło pobierać dane o zamówieniach, agregować dane w locie i podawać kwotę. 

Kasy w restauracjach trafiały do ​​tej samej tabeli zamówień, wyładowywały listę zamówień przyjętych na dziś, a do niej dodawane były nowe zamówienia. Kasy fiskalne wysyłały żądania co 5 sekund lub przy odświeżaniu strony.

Schemat wyglądał tak:

Historia architektury Dodo IS: wczesny monolit

Pewnej jesieni Fiodor Owczinnikow napisał na swoim blogu długi i popularny artykuł. Na bloga weszło bardzo dużo osób i zaczęło wszystko dokładnie czytać. Podczas gdy każda z przybyłych osób czytała artykuł, widżet przychodów działał poprawnie i co 20 sekund żądał API.

API wywołało procedurę składowaną w celu obliczenia sumy wszystkich zamówień od początku roku dla wszystkich pizzerii w sieci. Agregacja została oparta na bardzo popularnej tabeli zamówień. Idą do niego wszystkie kasy wszystkich otwartych w tym czasie restauracji. Kasy przestały odpowiadać, zamówienia nie były przyjmowane. Nie były też akceptowane z serwisu, nie pojawiały się na trackerze, kierownik zmiany nie mógł ich zobaczyć w swoim interfejsie. 

To nie jedyna historia. Jesienią 2015 roku w każdy piątek obciążenie systemu było krytyczne. Kilka razy wyłączaliśmy publiczne API, a raz nawet musieliśmy wyłączyć stronę, bo nic nie pomagało. Była nawet lista usług z nakazem wyłączenia pod dużym obciążeniem.

Od teraz zaczyna się nasza walka z obciążeniami i stabilizacją systemu (od jesieni 2015 do jesieni 2018). Wtedy to się stało"wielki upadek". Co więcej, czasami zdarzały się również awarie, niektóre były bardzo wrażliwe, ale ogólny okres niestabilności można teraz uznać za zaliczony.

Szybki rozwój biznesu

Dlaczego nie można było tego zrobić od razu? Wystarczy spojrzeć na poniższe wykresy.

Historia architektury Dodo IS: wczesny monolit

Również w latach 2014-2015 miało miejsce otwarcie w Rumunii i przygotowywane było otwarcie w USA.

Sieć rozwijała się bardzo szybko, otwierały się nowe kraje, pojawiały się nowe formaty pizzerii, na przykład otwarto pizzerię przy food court. Wszystko to wymagało szczególnej uwagi w zakresie rozbudowy funkcji Dodo IS. Bez tych wszystkich funkcji, bez śledzenia w kuchni, rozliczania produktów i strat w systemie, wyświetlania wydania zamówienia w hali food court, trudno byłoby mówić o „poprawnej” architekturze i „poprawnym” podejściu do teraz rozwój.

Kolejną przeszkodą w terminowej rewizji architektury i ogólnie zwróceniu uwagi na problemy techniczne był kryzys 2014 roku. Takie rzeczy utrudniają rozwój zespołom, zwłaszcza w przypadku młodej firmy, takiej jak Dodo Pizza.

Szybkie rozwiązania, które pomogły

Problemy potrzebne rozwiązania. Tradycyjnie rozwiązania można podzielić na 2 grupy:

  • Szybkie, które gaszą pożar i dają mały margines bezpieczeństwa oraz dają nam czas na zmianę.

  • Systematyczny, a zatem długi. Reengineering wielu modułów, podział monolitycznej architektury na osobne usługi (większość z nich to wcale nie mikro, a raczej makro usługi i coś w tym jest Raport Andrieja Morewskiego). 

Sucha lista szybkich zmian wygląda następująco:

Zwiększ skalę głównego mistrza

Oczywiście pierwszą rzeczą, którą należy zrobić, aby poradzić sobie z obciążeniami, jest zwiększenie pojemności serwera. Dokonano tego dla głównej bazy danych i serwerów WWW. Niestety, jest to możliwe tylko do pewnego limitu, wtedy staje się to zbyt kosztowne.

Od 2014 roku przenieśliśmy się na Azure, o tym temacie pisaliśmy również w tamtym czasie w artykule „Jak Dodo Pizza dostarcza pizzę za pomocą chmury Microsoft Azure". Ale po serii podwyżek serwera dla bazy, napotkali koszty. 

Repliki bazowe do czytania

Do bazy wykonano dwie repliki:

CzytajReplika w przypadku próśb o referencje. Służy do odczytu katalogów, typu, miasta, ulicy, pizzerii, produktów (domena powoli zmieniana) oraz w tych interfejsach, gdzie małe opóźnienie jest dopuszczalne. Były 2 takie repliki, o ich dostępność zadbaliśmy tak samo jak o mistrzów.

Odczyt repliki żądań raportów. Ta baza danych miała mniejszą dostępność, ale wszystkie zgłoszenia trafiały do ​​niej. Pozwól im mieć duże żądania przeliczania ogromnych ilości danych, ale nie wpływają one na główną bazę danych i interfejsy operacyjne. 

Pamięć podręczna w kodzie

W kodzie (w ogóle) nie było żadnych pamięci podręcznych. Prowadziło to do dodatkowych, nie zawsze koniecznych, żądań do załadowanej bazy danych. Pamięci podręczne były początkowo zarówno w pamięci, jak i w zewnętrznej usłudze pamięci podręcznej, czyli Redis. Wszystko zostało unieważnione przez czas, ustawienia zostały określone w kodzie.

Wiele serwerów zaplecza

Zaplecze aplikacji również wymagało skalowania, aby obsłużyć zwiększone obciążenia. Konieczne było utworzenie klastra z jednego iis-server. Zmieniliśmy termin sesja aplikacji z pamięci do RedisCache, co umożliwiło stworzenie kilku serwerów za prostym load balancerem z round robin. Na początku używano tego samego Redisa, co do cache’ów, potem podzielono go na kilka. 

W efekcie architektura stała się bardziej skomplikowana...

Historia architektury Dodo IS: wczesny monolit

… ale część napięcia została usunięta.

A potem trzeba było przerobić załadowane komponenty, czego się podjęliśmy. Porozmawiamy o tym w następnej części.

Źródło: www.habr.com

Dodaj komentarz