Zespół LMAX przedstawił prezentację na temat tego, w jaki sposób byli w stanie wykonać 100 000 TPS przy opóźnieniu krótszym niż 1 ms . Utworzyli kopię zapasową tej prezentacji za pomocą bloga , artykułu technicznego (PDF) i samego kodu źródłowego .
Niedawno Martin Fowler opublikował doskonały artykuł na temat architektury LMAX i wspomina, że są w stanie obsłużyć sześć milionów zamówień na sekundę, i podkreśla kilka kroków, które zespół podjął, aby podnieść wydajność o kolejny rząd wielkości.
Jak dotąd wyjaśniłem, że kluczem do szybkości procesora logiki biznesowej jest robienie wszystkiego sekwencyjnie, w pamięci. Samo zrobienie tego (i nic naprawdę głupiego) pozwala programistom napisać kod, który może przetwarzać 10K TPS.
Następnie odkryli, że skoncentrowanie się na prostych elementach dobrego kodu może zwiększyć to do zakresu 100 000 TPS. Wymaga to tylko dobrze skonstruowanego kodu i małych metod - w gruncie rzeczy pozwala to Hotspotowi lepiej wykonać optymalizację, a procesory są bardziej wydajne w buforowaniu kodu podczas działania.
Trzeba było nieco sprytniejszego, aby przejść o kolejny rząd wielkości. Zespół LMAX uznał, że jest tam kilka pomocnych rzeczy. Jednym z nich było napisanie niestandardowych implementacji kolekcji Java, które zostały zaprojektowane tak, aby były przyjazne dla pamięci podręcznej i ostrożne w przypadku śmieci.
Inną techniką osiągnięcia tego najwyższego poziomu wydajności jest zwrócenie uwagi na testy wydajności. Od dawna zauważyłem, że ludzie dużo mówią o technikach poprawy wydajności, ale jedyną rzeczą, która naprawdę robi różnicę, jest jej przetestowanie
Fowler wspomniał, że znaleziono kilka rzeczy, ale wspomniał tylko o kilku.
Czy istnieją inne architektury, biblioteki, techniki lub „rzeczy” pomocne w osiągnięciu takiego poziomu wydajności?
źródło
Odpowiedzi:
Istnieją wszelkiego rodzaju techniki przetwarzania transakcji o wysokiej wydajności, a ta w artykule Fowlera jest tylko jedną z wielu najnowocześniejszych. Zamiast wymieniać kilka technik, które mogą, ale nie muszą odnosić się do czyjejś sytuacji, myślę, że lepiej jest omówić podstawowe zasady i sposób, w jaki LMAX odnosi się do wielu z nich.
W przypadku systemu przetwarzania transakcji na dużą skalę chcesz wykonać jak najwięcej następujących czynności:
Minimalizuj czas spędzany na najwolniejszych poziomach pamięci. Od najszybszego do najwolniejszego na nowoczesnym serwerze masz: CPU / L1 -> L2 -> L3 -> RAM -> Dysk / LAN -> WAN. Skok z nawet najszybszego współczesnego dysku magnetycznego do najwolniejszej pamięci RAM jest ponad 1000x dla sekwencyjnego dostępu; losowy dostęp jest jeszcze gorszy.
Zminimalizuj lub wyeliminuj czas oczekiwania . Oznacza to współdzielenie jak najmniejszej liczby stanów, a jeśli stan musi być współużytkowany, w miarę możliwości unikaj jawnych blokad.
Rozłóż obciążenie. Procesory nie dostał się znacznie szybciej w ciągu ostatnich kilku lat, ale nie dostał mniejsze, a 8 rdzeni jest dość powszechne na serwerze. Poza tym możesz nawet rozłożyć pracę na wiele komputerów, co jest podejściem Google; wielką zaletą jest to, że skaluje wszystko, łącznie z I / O.
Według Fowler, LMAX stosuje następujące podejście do każdego z nich:
Zachowaj cały stan w pamięci przez cały czas. Większość silników baz danych i tak to zrobi, jeśli cała baza danych zmieści się w pamięci, ale nie chcą pozostawić niczego przypadkowi, co jest zrozumiałe na platformie transakcyjnej w czasie rzeczywistym. Aby to zrobić bez dodawania mnóstwa ryzyka, musieli zbudować zestaw lekkiej infrastruktury do tworzenia kopii zapasowych i przełączania awaryjnego.
Użyj kolejki bez blokady („disruptor”) dla strumienia zdarzeń wejściowych. W przeciwieństwie do tradycyjnych trwałych kolejek wiadomości, które definitywnie nie blokują się i w rzeczywistości zwykle wiążą się z boleśnie powolnymi rozproszonymi transakcjami .
Niewiele. LMAX rzuca to pod magistralę na tej podstawie, że obciążenia są współzależne; wynik jednego zmienia parametry dla innych. Jest to krytyczne zastrzeżenie, które Fowler wyraźnie wzywa. Oni robią jakieś zastosowanie współbieżności w celu zapewnienia możliwości przełączania awaryjnego, ale wszystkie logiki biznesowej jest przetwarzany na pojedynczym wątku .
LMAX to nie jedyne podejście do OLTP na dużą skalę. I chociaż jest to całkiem genialne samo w sobie, nie musisz używać najnowocześniejszych technik, aby osiągnąć ten poziom wydajności.
Ze wszystkich powyższych zasad nr 3 jest prawdopodobnie najważniejszy i najbardziej skuteczny, ponieważ, szczerze mówiąc, sprzęt jest tani. Jeśli potrafisz właściwie podzielić obciążenie pracą na pół tuzina rdzeni i kilkadziesiąt maszyn, to niebo jest granicą dla konwencjonalnych technik obliczeń równoległych . Zdziwiłbyś się, ile przepustowości możesz osiągnąć, korzystając tylko z szeregu kolejek wiadomości i dystrybutora działającego w trybie round-robin. Oczywiście nie jest tak wydajny jak LMAX - w rzeczywistości nie jest nawet bliski - ale przepustowość, opóźnienia i opłacalność to osobne problemy, a tutaj mówimy konkretnie o przepustowości.
Jeśli masz te same specjalne potrzeby, co LMAX - w szczególności stan współdzielony, który odpowiada rzeczywistości biznesowej w przeciwieństwie do pochopnego wyboru projektu - sugeruję wypróbowanie ich komponentu, ponieważ nie widziałem zbyt wiele w innym przypadku jest to dostosowane do tych wymagań. Ale jeśli mówimy po prostu o wysokiej skalowalności, zachęcam do dalszych badań systemów rozproszonych, ponieważ są one kanonicznym podejściem stosowanym przez większość organizacji (Hadoop i powiązane projekty, ESB i powiązane architektury, CQRS, które Fowler również wzmianki i tak dalej).
Dyski SSD również staną się przełomem; zapewne już są. Możesz teraz mieć stałe miejsce do przechowywania o podobnych czasach dostępu do pamięci RAM, i chociaż dyski SSD klasy serwerowej są nadal strasznie drogie, ostatecznie spadną w cenie, gdy wzrośnie współczynnik adopcji. Został on dogłębnie zbadany, a wyniki są dość zadziwiające i z czasem będą się poprawiać, więc cała koncepcja „zachowaj wszystko w pamięci” jest o wiele mniej ważna niż kiedyś. Więc jeszcze raz staram się skupić na współbieżności, gdy tylko jest to możliwe.
źródło
Myślę, że największą lekcją, jaką można się z tego nauczyć, jest to, że musisz zacząć od podstaw:
Podczas testowania wydajności profilujesz swój kod, znajdujesz wąskie gardła i naprawiasz je jeden po drugim.
Zbyt wiele osób przeskakuje do części „napraw je jeden po drugim”. Spędzają dużo czasu, pisząc „niestandardowe implementacje kolekcji java”, ponieważ po prostu wiedzą, że cały powód, dla którego ich system jest wolny, wynika z braków pamięci podręcznej. Może to być czynnikiem przyczyniającym się, ale jeśli przejdziesz do poprawiania kodu niskiego poziomu, prawdopodobnie przegapisz większy problem korzystania z ArrayList, gdy powinieneś używać LinkedList lub że prawdziwy powód, dla którego twój system jest powolne, ponieważ ORM leniwie ładuje dzieci encji, a zatem wykonuje 400 oddzielnych podróży do bazy danych dla każdego żądania.
źródło
Nie będę specjalnie komentować kodu LMAX, ponieważ uważam, że jest on obszernie opisany, ale oto kilka przykładów rzeczy, które zrobiłem, które doprowadziły do znacznej wymiernej poprawy wydajności.
Jak zawsze są to techniki, które należy zastosować, gdy wiesz, że masz problem i potrzebujesz poprawić wydajność - w przeciwnym razie prawdopodobnie przedwcześnie przeprowadzisz optymalizację.
Pomóż kompilatorowi JIT w tworzeniu ostatecznych pól, metod i klas umożliwia określone optymalizacje, które naprawdę pomagają kompilatorowi JIT. Konkretne przykłady:
Zastąp klasy kolekcji tablicami - skutkuje to mniej czytelnym kodem i jest trudniejsze w utrzymaniu, ale prawie zawsze jest szybsze, ponieważ usuwa warstwę pośrednią i korzysta z wielu fajnych optymalizacji dostępu do tablicy. Zwykle dobry pomysł w wewnętrznych pętlach / kodzie wrażliwym na wydajność po zidentyfikowaniu go jako wąskiego gardła, ale unikaj inaczej ze względu na czytelność!
Używaj prymitywów tam, gdzie to możliwe - prymitywy są zasadniczo szybsze niż ich obiektowe odpowiedniki. W szczególności boks powoduje ogromne obciążenie i może powodować nieprzyjemne przerwy w GC. Nie zezwalaj na zapakowanie żadnych prymitywów, jeśli zależy Ci na wydajności / opóźnieniu.
Zminimalizuj blokowanie niskiego poziomu - zamki są bardzo drogie na niskim poziomie. Znajdź sposoby, aby całkowicie uniknąć blokowania lub zablokować na poziomie zgrubnym, abyś tylko musiał blokować sporadycznie duże bloki danych, a kod niskiego poziomu może kontynuować bez martwienia się o problemy z blokowaniem lub współbieżnością.
źródło
final
niektóre zespoły JIT, mogą to rozgryźć , inne mogą nie. Jest to zależne od implementacji (podobnie jak wiele porad dotyczących strojenia wydajności). Zgadzam się na przydziały - musisz to porównać. Zwykle uważam, że lepiej jest wyeliminować przydziały, ale YMMV.Poza tym, co już zostało podane w doskonałej odpowiedzi Aaronaught , chciałbym zauważyć, że taki kod może być trudny do opracowania, zrozumienia i debugowania. „Chociaż jest bardzo wydajny ... bardzo łatwo go spieprzyć ...”, jak wspomniał jeden z ich facetów na blogu LMAX .
Biorąc pod uwagę powyższe, uważam, że osoby wybierające Disruptor i podobne podejścia lepiej upewniają się, że dysponują zasobami programistycznymi wystarczającymi do utrzymania rozwiązania.
Ogólnie rzecz biorąc, podejście Disruptor wydaje mi się dość obiecujące. Nawet jeśli Twoja firma nie może sobie pozwolić na wykorzystanie go, np. Z wyżej wymienionych powodów, rozważ przekonanie swojego kierownictwa do „zainwestowania” wysiłku w jego badanie (i ogólnie SEDA ) - ponieważ jeśli tego nie zrobi, jest szansa, że któregoś dnia klienci zostawią ich na korzyść bardziej konkurencyjnego rozwiązania wymagającego 4x, 8x itp. mniej serwerów.
źródło