Jak radzić sobie z ograniczeniami klucza obcego podczas migracji z monolitu do mikrousług?


18

Mój zespół migruje z monolitycznej aplikacji ASP.NET do .NET Core i Kubernetes. Wydaje się, że zmiany w kodzie idą tak dobrze, jak można się spodziewać, ale w miejscu, w którym mój zespół napotyka wiele niezgody, wokół bazy danych.

Obecnie mamy dość dużą bazę danych SQL Server, która zawiera wszystkie dane dla całej naszej firmy. Proponuję, abyśmy podzielili bazę danych w podobny sposób, jak podział kodu - dane katalogowe w jednej (logicznej) bazie danych, dane inwentaryzacyjne w innej, zamówienia w innej itd. - a każda mikrousługa byłaby strażnikiem bazy danych .

Oznacza to, że klucze obce przekraczające granice mikrousług musiałyby zostać usunięte, a sproki i widoki przekraczające granice byłyby zabronione. Wszystkie modele danych mogą, ale nie muszą znajdować się w tej samej fizycznej bazie danych, ale nawet jeśli tak, to nie powinny bezpośrednio ze sobą oddziaływać. Zamówienia mogą nadal odwoływać się do pozycji katalogu według Id, ale integralność danych nie byłaby ściśle egzekwowana na poziomie bazy danych i dane muszą być połączone w kodzie, a nie w SQL.

Widzę ich utratę jako konieczny kompromis w przejściu do mikrousług i uzyskiwaniu korzyści skalowalności. Tak długo, jak mądrze wybieramy nasze szwy i rozwijamy się wokół nich, powinno być OK. Inni członkowie zespołu są przekonani, że wszystko musi pozostać w tej samej monolitycznej bazie danych, aby wszystko mogło być ACID i mieć wszędzie zachowaną integralność referencyjną.

To prowadzi mnie do mojego pytania. Po pierwsze, czy moje stanowisko w sprawie ograniczeń klucza obcego i przyłączenia się jest prawdopodobne? Jeśli tak, to czy ktoś wie o jakichkolwiek wiarygodnych materiałach do czytania, które mógłbym zaoferować moim kolegom? Ich pozycja jest niemal religijna i nie wydaje się, aby zachwiało ich cokolwiek innego niż sam Martin Fowler, który powiedziałby im, że się mylą.


5
Integralność referencyjna jest niezwykle cenna. Czy skala bazy danych jest naprawdę wąskim gardłem? Czy naprawdę potrzebujesz skalowalności w stylu mikrousług? Wiesz lepiej ode mnie, czy ta zmiana w architekturze jest odpowiednia dla Twojej organizacji, ale proszę wziąć pod uwagę, że po prostu nie pasuje ona do wielu przypadków użycia. Mogą istnieć inne sposoby skalowania z bardziej atrakcyjnymi kompromisami. Np. Jeśli zapytania do bazy danych na sekundę są zbyt wysokie, być może wystarczy replikacja bazy danych. I możesz skalować w poziomie serwery sieciowe bez konieczności korzystania z mikrousług.
amon

Słuszne uwagi. Analizujemy niektóre z tych opcji w celu uzyskania krótkoterminowych zysków. Przejście do mikrousług to jednak długa gra. Moim zdaniem pozwoli nam to skalować przez lata, a nie miesiące.
Raymond Saltrelli

3
Jestem pewien, że Twoi klienci będą zachwyceni, że zamówienie, które złożyli o 0,05 ms szybciej, zostanie anulowane, ponieważ ktoś inny zamówił ten sam produkt, gdy w magazynie pozostał tylko jeden.
Andy,

@amon Uczyń to odpowiedzią, a ja ją poprę. To dobre pytanie, a zalety i wady muszą być uczciwie reprezentowane.
mcottle,

@mcottle ok, gotowe!
amon

Odpowiedzi:


19

Nie ma jednoznacznego rozwiązania, ponieważ zależy to całkowicie od kontekstu - w szczególności, od których wymiarów system ma się skalować i jakie są twoje rzeczywiste problemy. Czy baza danych jest naprawdę twoim wąskim gardłem?

Ta (niestety dość długa) odpowiedź brzmi trochę jak „mikrousług są złe, monolity na całe życie!”, Ale to nie jest moja intencja. Chodzi mi o to, że mikrousługi i rozproszone bazy danych mogą rozwiązać różne problemy, ale nie bez własnych problemów. Aby uzasadnić swoją architekturę, musisz pokazać, że te problemy nie mają zastosowania, można je złagodzić i że architektura ta jest najlepszym wyborem dla potrzeb Twojej firmy.

Rozproszone dane są trudne.

Ta sama elastyczność, która umożliwia lepsze skalowanie, to druga strona słabszych gwarancji. Warto zauważyć, że systemy rozproszone są znacznie trudniejsze do uzasadnienia.

Aktualizacje atomowe, transakcje, spójność / integralność referencyjna i trwałość są niezwykle cenne i nie należy z nich rezygnować pochopnie. Nie ma sensu mieć danych, jeśli są niekompletne, nieaktualne lub całkowicie błędne. Jeśli masz ACID jako wymaganie biznesowe, ale korzystasz z technologii baz danych, która nie jest w stanie zaoferować go od razu (np. Wiele baz danych NoSQL lub architektura DB-per-microservice), Twoja aplikacja musi wypełnić lukę i zapewnić te gwarancje.

  • Nie jest to niemożliwe, ale trudne do zrobienia. Bardzo trudne. Zwłaszcza w ustawieniu rozproszonym, w którym do każdej bazy danych jest wielu autorów. Ta trudność przekłada się na dużą szansę na błędy, w tym prawdopodobnie upuszczone dane, niespójne dane i tak dalej.

    Rozważ na przykład przeczytanie analiz Jepsen dobrze znanych rozproszonych systemów baz danych , być może zaczynając od analizy Cassandry . Nie rozumiem połowy tej analizy, ale TL; DR jest taki, że systemy rozproszone są tak trudne, że nawet wiodące w branży projekty czasami mylą się, w sposób, który może wydawać się oczywisty z perspektywy czasu.

  • Systemy rozproszone wymagają również większego wysiłku rozwojowego. Do pewnego stopnia istnieje bezpośredni kompromis między kosztami programowania lub spadaniem pieniędzy na mocniejszy sprzęt.

Przykład: wiszące odniesienia

W praktyce nie powinieneś patrzeć na informatykę, ale na wymagania biznesowe, aby sprawdzić, czy i jak można rozluźnić ACID. Np. Wiele relacji z kluczem obcym może nie być tak ważne, jak się wydaje. Rozważ związek produkt - kategoria n: m. W RDBMS możemy zastosować ograniczenie klucza obcego, aby tylko istniejące produkty i istniejące kategorie mogły być częścią tego związku. Co się stanie, jeśli wprowadzimy oddzielne usługi dotyczące produktów i kategorii, a produkt lub kategoria zostaną usunięte?

W takim przypadku może to nie stanowić większego problemu i możemy napisać naszą aplikację, aby odfiltrowała wszelkie produkty lub kategorie, które już nie istnieją. Ale są kompromisy!

  • Należy pamiętać, że może to wymagać poziomu aplikacji JOINw wielu bazach danych / mikrousługach, co jedynie przenosi przetwarzanie z serwera bazy danych do aplikacji. Zwiększa to całkowite obciążenie i musi przenosić dodatkowe dane przez sieć.

  • Może to zepsuć podział na strony. Np. Zamawiasz kolejne 25 produktów z kategorii i odfiltrowujesz niedostępne produkty z tej odpowiedzi. Teraz Twoja aplikacja wyświetla 23 produkty. Teoretycznie możliwa byłaby również strona z zerowymi produktami!

  • Od czasu do czasu będziesz chciał uruchomić skrypt, który czyści zwisające odwołania, po każdej odpowiedniej zmianie lub w regularnych odstępach czasu. Zauważ, że takie skrypty są dość drogie, ponieważ muszą żądać każdego produktu / kategorii z bazy danych / mikrousługi, aby sprawdzić, czy nadal istnieje.

  • Powinno to być oczywiste, ale dla jasności: nie używaj ponownie identyfikatorów. Identyfikatory w stylu autoinkrementacji mogą, ale nie muszą być w porządku. Identyfikatory GUID lub skróty zapewniają większą elastyczność, np. Dzięki możliwości przypisania identyfikatora przed wstawieniem elementu do bazy danych.

Przykład: współbieżne zamówienia

Zamiast tego rozważ stosunek produktu do zamówienia. Co stanie się z zamówieniem, jeśli produkt zostanie usunięty lub zmieniony? Ok, możemy po prostu skopiować odpowiednie dane produktu do pozycji zamówienia, aby były dostępne - aby uprościć wymianę miejsca na dysku. Ale co jeśli cena produktu ulegnie zmianie lub produkt stanie się niedostępny tuż przed złożeniem zamówienia na ten produkt? W systemie rozproszonym rozprzestrzenianie się efektów zajmuje trochę czasu, a kolejność prawdopodobnie przejdzie przez nieaktualne dane.

Ponownie, jak podejść do tego, zależy od wymagań biznesowych. Być może nieaktualne zamówienie jest akceptowalne i możesz później anulować zamówienie, jeśli nie może zostać zrealizowane.

Ale może nie jest to opcja, np. W przypadku wysoce współbieżnych ustawień. Weź pod uwagę 3000 osób, które spieszą się, aby kupić bilety na koncert w ciągu pierwszych 10 sekund, i załóżmy, że zmiana dostępności będzie wymagała 10 ms do rozpowszechnienia. Jakie jest prawdopodobieństwo sprzedania ostatniego biletu wielu osobom? Zależy jak te kolizje są obsługiwane, ale stosując rozkład Poissona z λ = 3000 / (10s / 10ms) = 3otrzymujemy P(k > 1) = 1 - P(k = 0) - P(k = 1) = 80%szansę zderzenia na 10ms interwał. To, czy sprzedaż, a później anulowanie większości zamówień jest możliwe bez popełniania oszustwa, może prowadzić do interesującej rozmowy z działem prawnym.

Pragmatyzm oznacza wybieranie najlepszych funkcji.

Dobrą wiadomością jest to, że nie musisz przechodzić na model rozproszonej bazy danych, jeśli nie jest to wymagane w inny sposób. Nikt nie cofnie członkostwa w Microservice Club, jeśli nie wykonasz mikrousług „odpowiednio”, ponieważ nie ma takiego klubu - i nie ma jednego prawdziwego sposobu na zbudowanie mikrousług.

Pragmatyzm wygrywa za każdym razem, więc mieszaj i dopasowuj różne podejścia, gdy rozwiążą twój problem. Może to nawet oznaczać mikrousługi ze scentralizowaną bazą danych. Naprawdę, nie przechodź przez ból rozproszonych baz danych, jeśli nie musisz.

Możesz skalować bez mikrousług.

Mikrousługi mają dwie główne zalety:

  • Korzyści organizacyjne związane z tym, że można je opracowywać i wdrażać niezależnie przez oddzielne zespoły (co z kolei wymaga, aby usługi oferowały stabilny interfejs).
  • Korzyści operacyjne wynikające z tego, że każdą mikrousługę można skalować niezależnie .

Jeśli niezależne skalowanie nie jest wymagane, mikrousługi są znacznie mniej atrakcyjne.

Serwer bazy danych jest już rodzajem usługi, którą można skalować (nieco) niezależnie, np. Dodając repliki odczytu. Wspominasz o procedurach przechowywanych. Zmniejszenie ich może mieć tak duży efekt, że wszelkie inne dyskusje na temat skalowalności są dyskusyjne.

I jest całkowicie możliwe posiadanie skalowalnego monolitu, który obejmuje wszystkie usługi jako biblioteki. Następnie można skalować, uruchamiając więcej instancji monolitu, co oczywiście wymaga, aby każda instancja była bezstanowa.

Zwykle działa to dobrze, dopóki monolit nie będzie zbyt duży, aby można go było rozsądnie wdrożyć, lub jeśli niektóre usługi mają specjalne wymagania dotyczące zasobów, aby można było skalować je niezależnie. Domeny problemowe wymagające dodatkowych zasobów mogą nie obejmować osobnego modelu danych.

Czy masz mocne uzasadnienie biznesowe?

Zdajesz sobie sprawę z potrzeb biznesowych Twojej organizacji i dlatego możesz stworzyć argument oparty na architekturze bazy danych dla mikrousług na podstawie analizy:

  • że wymagana jest pewna skala, a ta architektura jest najbardziej opłacalnym podejściem do uzyskania tej skalowalności, biorąc pod uwagę wzmożony wysiłek na rzecz opracowania takiej konfiguracji i alternatywnych rozwiązań; i
  • że wymagania biznesowe pozwalają na złagodzenie odpowiednich gwarancji ACID, bez powodowania różnych problemów, takich jak te omówione powyżej.

I odwrotnie, jeśli nie jesteś w stanie tego wykazać, w szczególności jeśli obecny projekt bazy danych jest w stanie obsłużyć wystarczającą skalę w przyszłości (jak sądzą koledzy), to również masz odpowiedź.

Istnieje również duży składnik YAGNI do skalowalności. W obliczu niepewności jest to strategiczna decyzja biznesowa dotycząca budowania skalowalności (niższe całkowite koszty, ale wiążą się z kosztami alternatywnymi i mogą nie być potrzebne) w porównaniu z odroczeniem prac nad skalowalnością (w razie potrzeby wyższe całkowite koszty, ale masz lepsze idea rzeczywistej skali). Nie jest to przede wszystkim decyzja techniczna.


Doskonała odpowiedź, dziękuję. Czy możesz rozwinąć to oświadczenie? Czy masz na myśli zmniejszenie liczby procedur, które mogą mieć duży wpływ na wydajność? Wspominasz o procedurach przechowywanych. Zmniejszenie ich może mieć tak duży efekt, że wszelkie inne dyskusje na temat skalowalności są dyskusyjne.
alan

1
@alan Procedury przechowywane mogą być używane na dobre, ale powodują dwa problemy z wydajnością: (1) Optymalizacja bazy danych jest trudniejsza dla bazy danych. (2) Korzystanie z sproców oznacza więcej pracy na serwerze DB. OP chce podzielić DB w celu dalszego skalowania, ale unikanie skomplikowanych sprocków może już zapewnić ten zapas. Oczywiście, sproki i złożone zapytania mogą być również dobre dla wydajności, np. Gdy minimalizują ilość danych, które muszą zostać przeniesione z bazy danych do odpowiedzi na zapytanie. Podział bazy danych pogorszyłby ten problem, gdy potrzebne są JOINY między serwerami.
amon

0

Uważam, że oba podejścia są prawdopodobne. Możesz zyskać skalowalność poświęcając zalety ACID i monolitycznych baz danych, a także zachować aktualną architekturę i poświęcić skalowalność i zwinność bardziej rozproszonej architektury. Właściwa decyzja będzie wynikać z obecnego modelu biznesowego i strategii buz na kolejne lata. Z czysto technologicznego punktu widzenia istnieją bóle, które utrzymują monolityczność, a także przechodzą na bardziej rozproszone podejście. Przeanalizuję system i zobaczę, które aplikacje / moduły / procesy biznesowe są bardziej krytyczne do skalowania i oceny ryzyka, kosztów i korzyści, aby zdecydować, które powinny poczekać lub kontynuować w architekturze monolitycznej.


-1

Twoja postawa jest wiarygodna i poprawna.

Jak przekonać barwionych w wełnę nałogowców dbających, to kolejne pytanie. Powiedziałbym, że masz dwie opcje.

  1. Znajdź konkretny przykład, w którym DB osiągnął swoje granice. Czy masz na przykład „tabele archiwizacji”? Dlaczego to jest w porządku? Jaką maksymalną liczbę zamówień na sekundę możesz przyjąć? itp. Pokaż, że baza danych nie spełnia wymagań, a Twoje rozwiązanie je rozwiązuje.

  2. Zatrudnij drogich kontrahentów, którzy podpowie najlepsze rozwiązanie. Ponieważ są kosztowne i mają blogi, wszyscy im uwierzą


1
Nie jestem -1, ale żeby była to dobra odpowiedź, zgubiłbym warunek z punktu 2 i rozwinąłbym się, kiedy byłoby potrzebne, aby potrzebować wielu baz danych. Nie sądzę, aby tabele archiwów były koniecznie anty-wzorcem w bazach danych, które nie obsługują partycjonowania. Baza danych, z której obecnie korzystam, zawiera około 130 GB danych z 26 tabelami> 10 mln wierszy, a wydajność nie jest wystarczająco odległym problemem, aby trzeba było podzielić bazę danych; więc jestem bardzo sceptyczny i chciałbym usłyszeć, dlaczego to dobry pomysł, a kiedy trzeba to zrobić - ta odpowiedź jest najbliższa, jaką do tej pory widziałem.
mcottle

dobrze. Wspominam o tabelach archiwów, ponieważ niwelują ograniczenia FK. jest to szczelina w zbroi. dzielenie według mikrousług nie jest wielkością db, jest to utrzymywanie mikrousług jako oddzielnej rzeczy. Jeśli nie możesz go wyłączyć i wyrzucić, to naprawdę nie jest to mikrousługa. w pkt 2. OP wspomina MF, mogliby dosłownie zatrudnić go / choćby pracowników, aby przyszli i powiedzieli im, aby podzielili db
Ewan

„Jeśli nie możesz go wyłączyć i wyrzucić, to nie jest to naprawdę mikrousługa”. Dotyczy to samej usługi, ale niekoniecznie argumentu, dlaczego usługa potrzebuje własnej bazy danych. Ostatecznie sama baza danych jest usługą wykorzystywaną przez mikrousługę. Mikrousługa tak naprawdę nie wie ani nie dba o to, czy wykorzystywane przez nią dane znajdują się w osobnej bazie danych, czy we wspólnej bazie danych. Możesz podkręcić kopie tej mikrousługi i nic tak naprawdę się nie zmienia.
Chris Pratt

Najlepszym argumentem za bazą danych dla usługi są limity połączeń. Często zdarza się, że używa się puli połączeń, więc każda mikrousługa wymaga już wielu połączeń z instancją bazy danych, wtedy można mieć wiele instancji każdej z tych mikrousług, każda z własną pulą. W końcu może dojść do głowy, że po prostu wyczerpałeś zdolność bazy danych do obsługi wszystkich otrzymywanych połączeń.
Chris Pratt
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.