Rozproszony rejestr zestawów kołowych: doświadczenie z Hyperledger Fabric

Witam, pracuję w zespole projektu DRD KP (rozproszony rejestr danych do monitorowania cyklu życia zestawów kołowych). W tym miejscu chcę podzielić się doświadczeniem naszego zespołu w opracowywaniu korporacyjnego łańcucha bloków dla tego projektu w ramach ograniczeń narzuconych przez technologię. W większości będę mówił o Hyperledger Fabric, ale opisane tutaj podejście można ekstrapolować na dowolny łańcuch bloków z zezwoleniem. Ostatecznym celem naszych badań jest przygotowanie korporacyjnych rozwiązań typu blockchain w taki sposób, aby produkt końcowy był przyjemny w użytkowaniu i niezbyt trudny w utrzymaniu.

Nie będzie tu żadnych odkryć, nieoczekiwanych rozwiązań i żadnych wyjątkowych wydarzeń (bo ich nie mam). Chcę tylko podzielić się moim skromnym doświadczeniem, pokazać, że „było to możliwe” i być może przeczytać w komentarzach o czyimś doświadczeniu w podejmowaniu dobrych i niezbyt dobrych decyzji.

Problem: łańcuchy bloków nie są jeszcze skalowalne

Dziś wysiłki wielu programistów mają na celu uczynienie z blockchainu naprawdę wygodnej technologii, a nie tykającej bomby zegarowej w pięknym opakowaniu. Kanały stanowe, optymistyczne podsumowanie, plazma i sharding mogą stać się powszechne. Pewnego dnia. A może TON ponownie przesunie start o pół roku i kolejna Plazma Group przestanie istnieć. Możemy wierzyć w inną mapę drogową i czytać genialne białe księgi w nocy, ale tu i teraz musimy coś zrobić z tym, co mamy. Zajmij się gównem.

Zadanie przydzielone naszemu zespołowi w obecnym projekcie wygląda w ogólnym zarysie tak: jest wiele podmiotów, sięgających kilku tysięcy, które nie chcą budować relacji na zaufaniu; konieczne jest zbudowanie na DLT rozwiązania, które będzie działać na zwykłych komputerach PC bez specjalnych wymagań wydajnościowych i zapewni użytkownikowi doświadczenie nie gorsze niż jakiekolwiek scentralizowane systemy księgowe. Technologia stojąca za rozwiązaniem powinna minimalizować możliwość złośliwej manipulacji danymi – dlatego właśnie istnieje blockchain.

Slogany z oficjalnych gazet i mediów obiecują nam, że następny rozwój umożliwi miliony transakcji na sekundę. Co to jest naprawdę?

Mainnet Ethereum działa obecnie z prędkością ~30 tps. Już z tego powodu trudno postrzegać go jako blockchain, który w jakikolwiek sposób odpowiada potrzebom korporacyjnym. Wśród dozwolonych rozwiązań znane są benchmarki pokazujące 2000 tps (Kworum) lub 3000 ton/s (Tkanina Hyperledger, w publikacji jest trochę mniej, ale należy pamiętać, że benchmark został przeprowadzony na starym silniku konsensusu). Był próba radykalnej przeróbki Fabric, co dało nie najgorsze wyniki, 20000 tps, ale na razie są to tylko opracowania akademickie czekające na ich stabilną realizację. Jest mało prawdopodobne, aby korporacja, która może sobie pozwolić na utrzymanie działu programistów blockchain, zniosła takie wskaźniki. Ale problem dotyczy nie tylko przepustowości, ale także opóźnień.

Utajenie

Opóźnienie od momentu zainicjowania transakcji do jej ostatecznego zatwierdzenia przez system zależy nie tylko od szybkości przechodzenia komunikatu przez wszystkie etapy walidacji i zamawiania, ale także od parametrów formowania bloku. Nawet jeśli nasz blockchain pozwala nam na zatwierdzenie 1000000 10 488 tps, ale utworzenie bloku XNUMX MB zajmuje XNUMX minut, czy stanie się to dla nas łatwiejsze?

Przyjrzyjmy się bliżej cyklowi życia transakcji w Hyperledger Fabric, aby zrozumieć, co wymaga czasu i jak ma się to do parametrów tworzenia bloków.

Rozproszony rejestr zestawów kołowych: doświadczenie z Hyperledger Fabric
wzięty stąd: hyperledger-fabric.readthedocs.io/en/release-1.4/arch-deep-dive.html#swimlane

(1) Klient tworzy transakcję, wysyła ją do zatwierdzających peerów, ci drudzy symulują transakcję (stosują zmiany wprowadzone przez chaincode do bieżącego stanu, ale nie zatwierdzają w księdze) i otrzymują RWSet - nazwy kluczy, wersje i wartości pobierane z kolekcji w CouchDB, (2) endorserzy odsyłają podpisany RWSet z powrotem do klienta, (3) klient albo sprawdza podpisy wszystkich wymaganych peerów (endorserów), a następnie przesyła transakcję do serwisu zamawiającego lub wysyła bez weryfikacji (weryfikacja będzie miała miejsce później), serwis zamawiający tworzy blok i (4) odsyła do wszystkich peerów, a nie tylko do polecających; peery sprawdzają, czy wersje kluczy w zestawie do odczytu są zgodne z wersjami w bazie danych, podpisami wszystkich indosantów i ostatecznie zatwierdzają blok.

Ale to nie wszystko. Za napisem „zleceniodawca tworzy blok” kryje się nie tylko zlecanie transakcji, ale także 3 kolejne żądania sieciowe od lidera do obserwujących i z powrotem: lider dodaje wiadomość do dziennika, wysyła do obserwujących, ci drudzy dodają do ich dziennik, wyślij potwierdzenie pomyślnej replikacji do lidera, lider zatwierdza wiadomość, wysyła potwierdzenie zatwierdzenia do obserwujących, obserwujący zatwierdzają. Im mniejszy rozmiar i czas bloku, tym częściej serwis zamawiający będzie musiał ustalić konsensus. Hyperledger Fabric posiada dwa parametry tworzenia bloku: BatchTimeout – czas tworzenia bloku oraz BatchSize – rozmiar bloku (liczba transakcji i rozmiar samego bloku w bajtach). Gdy tylko jeden z parametrów osiągnie limit, wydawany jest nowy blok. Im więcej węzłów porządkujących, tym dłużej to potrwa. Dlatego musisz zwiększyć BatchTimeout i BatchSize. Ale ponieważ zestawy RWS są wersjonowane, im większy blok, tym większe prawdopodobieństwo konfliktów MVCC. Ponadto wraz ze wzrostem BatchTimeout UX ulega katastrofalnej degradacji. Rozsądny i oczywisty wydaje mi się następujący schemat rozwiązania tych problemów.

Jak uniknąć czekania na finalizację bloku i nie stracić kontroli nad statusem transakcji

Im dłuższy czas formowania i rozmiar bloku, tym większa przepustowość łańcucha bloków. Jedno nie wynika bezpośrednio z drugiego, ale należy pamiętać, że osiągnięcie konsensusu w RAFT wymaga trzech zapytań sieciowych od lidera do obserwujących iz powrotem. Im więcej węzłów porządku, tym dłużej to potrwa. Im mniejszy rozmiar i czas formowania się bloków, tym więcej takich oddziaływań. Jak zwiększyć czas formowania i rozmiar bloku bez zwiększania czasu odpowiedzi systemu dla użytkownika końcowego?

Po pierwsze, musisz jakoś rozwiązać konflikty MVCC spowodowane dużym rozmiarem bloku, który może zawierać różne zestawy RWS z tą samą wersją. Oczywiście po stronie klienta (w odniesieniu do sieci blockchain równie dobrze może to być backend i mam to na myśli) Obsługa konfliktów MVCC, która może być oddzielną usługą lub zwykłym dekoratorem w wywołaniu inicjującym transakcję z logiką ponawiania.

Ponowną próbę można wdrożyć za pomocą strategii wykładniczej, ale wtedy opóźnienie również spadnie wykładniczo. Powinieneś więc użyć losowej ponownej próby w pewnych małych granicach lub stałej. Z myślą o możliwych kolizjach w pierwszym wariancie.

Kolejnym krokiem jest sprawienie, aby interakcja klienta z systemem była asynchroniczna, aby nie czekał 15, 30 lub 10000000 XNUMX XNUMX sekund, które ustawimy jako BatchTimeout. Ale jednocześnie konieczne jest zachowanie możliwości upewnienia się, że zmiany inicjowane przez transakcję są rejestrowane/nie rejestrowane w blockchainie.
Baza danych może służyć do przechowywania statusu transakcji. Najłatwiejszą opcją jest CouchDB ze względu na łatwość użycia: baza danych ma gotowy interfejs użytkownika, interfejs API REST i można łatwo skonfigurować dla niej replikację i sharding. Możesz po prostu utworzyć oddzielną kolekcję w tej samej instancji CouchDB, której Fabric używa do przechowywania swojego stanu świata. Musimy przechowywać tego rodzaju dokumenty.

{
 Status string // Статус транзакции: "pending", "done", "failed"
 TxID: string // ID транзакции
 Error: string // optional, сообщение об ошибке
}

Ten dokument jest zapisywany w bazie danych przed wysłaniem transakcji do peerów, identyfikator podmiotu jest zwracany użytkownikowi (ten sam identyfikator jest używany jako klucz), jeśli jest to operacja tworzenia, a następnie pola Status, TxID i Error są aktualizowane w miarę uzyskiwania odpowiednich informacji od rówieśników.

Rozproszony rejestr zestawów kołowych: doświadczenie z Hyperledger Fabric

W tym schemacie użytkownik nie czeka na ostateczne uformowanie się bloku, obserwując obracające się koło na ekranie przez 10 sekund, otrzymuje natychmiastową odpowiedź z systemu i kontynuuje pracę.

Wybraliśmy BoltDB do przechowywania statusów transakcji, ponieważ potrzebujemy zaoszczędzić pamięć i nie chcemy tracić czasu na interakcję sieciową z samodzielnym serwerem bazy danych, zwłaszcza gdy ta interakcja odbywa się za pomocą protokołu zwykłego tekstu. Nawiasem mówiąc, niezależnie od tego, czy używasz CouchDB do implementacji schematu opisanego powyżej, czy tylko do przechowywania stanu świata, w każdym przypadku optymalizacja sposobu przechowywania danych w CouchDB ma sens. Domyślnie w CouchDB rozmiar węzłów b-drzewa wynosi 1279 bajtów, czyli znacznie mniej niż rozmiar sektora na dysku, co oznacza, że ​​zarówno odczyt, jak i ponowne zrównoważenie drzewa będzie wymagało większej liczby fizycznych dostępów do dysku. Optymalny rozmiar spełnia normę Zaawansowany format i wynosi 4 kilobajty. W celu optymalizacji musimy ustawić parametr btree_chunk_size równy 4096 w pliku konfiguracyjnym CouchDB. W przypadku BoltDB taka ręczna interwencja nie jest wymagane.

Przeciwciśnienie: strategia buforowa

Ale może być wiele wiadomości. Więcej niż może obsłużyć system, współdzielenie zasobów z kilkunastoma innymi usługami poza tymi pokazanymi na diagramie - a to wszystko powinno działać bez zarzutu nawet na maszynach, na których uruchomienie Intellij Idea byłoby wyjątkowo uciążliwe.

Problem różnej przepustowości komunikujących się systemów producenta i konsumenta jest rozwiązywany na różne sposoby. Zobaczmy, co możemy zrobić.

Rzut: możemy twierdzić, że jesteśmy w stanie przetworzyć co najwyżej X transakcji w T sekund. Wszystkie żądania przekraczające ten limit są odrzucane. To całkiem proste, ale wtedy możesz zapomnieć o UX.

Sterowanie: konsument musi mieć jakiś interfejs, przez który w zależności od obciążenia może sterować tps producenta. Nieźle, ale nakłada to na twórców klienta ładowania obowiązek zaimplementowania tego interfejsu. Dla nas jest to niedopuszczalne, ponieważ blockchain w przyszłości zostanie zintegrowany z dużą liczbą istniejących od dawna systemów.

Buforowanie: zamiast opierać się wejściowemu strumieniowi danych, możemy buforować ten strumień i przetwarzać go z wymaganą szybkością. Oczywiście jest to najlepsze rozwiązanie, jeśli zależy nam na dobrym doświadczeniu użytkownika. Zaimplementowaliśmy bufor za pomocą kolejki w RabbitMQ.

Rozproszony rejestr zestawów kołowych: doświadczenie z Hyperledger Fabric

Do schematu dodano dwie nowe akcje: (1) po otrzymaniu żądania API kolejkowana jest wiadomość z parametrami niezbędnymi do wywołania transakcji, a klient otrzymuje wiadomość, że transakcja została zaakceptowana przez system, ( 2) backend odczytuje dane z kolejki z prędkością określoną w konfiguracji; inicjuje transakcję i aktualizuje dane w magazynie statusów.
Teraz możesz wydłużyć czas kompilacji i przepustowość bloków tak bardzo, jak chcesz, ukrywając opóźnienia przed użytkownikiem.

Inne narzędzia

Nic nie zostało tu powiedziane o chaincode, bo zazwyczaj nie ma w nim nic do optymalizacji. Kod łańcuchowy powinien być tak prosty i bezpieczny, jak to możliwe - to wszystko, czego się od niego wymaga. Framework bardzo nam pomaga w prostym i bezpiecznym pisaniu kodu łańcuchowego. CSKit od S7 Techlab i analizator statyczny ożywić^CC.

Ponadto nasz zespół opracowuje zestaw narzędzi, dzięki którym praca z Fabric będzie prosta i przyjemna: eksplorator łańcucha bloków, narzędzie do automatyczna rekonfiguracja sieci (dodaj/usuń organizacje, węzły RAFT), narzędzie dla unieważnienie certyfikatu i usunięcie tożsamości. Jeśli chcesz dołożyć swoją cegiełkę, zapraszamy.

wniosek

Takie podejście ułatwia zastąpienie Hyperledger Fabric Quorum, innymi prywatnymi sieciami Ethereum (PoA czy nawet PoW), znacznie zmniejsza rzeczywistą przepustowość, ale jednocześnie zachowuje normalny UX (zarówno dla użytkowników w przeglądarce, jak i od strony zintegrowanych systemów) ). Podczas zamiany Fabric na Ethereum w schemacie, tylko logika usługi ponawiania/dekoratora będzie musiała zostać zmieniona z obsługi konfliktów MVCC na niepodzielny przyrost nonce i ponowne wysyłanie. Buforowanie i przechowywanie stanu umożliwiło oddzielenie czasu odpowiedzi od czasu tworzenia bloku. Teraz możesz dodawać tysiące węzłów zamówień i nie obawiać się, że bloki tworzą się zbyt często i ładują usługę zamawiania.

Ogólnie to wszystko, czym chciałem się podzielić. Będzie mi miło, jeśli komuś to pomoże w pracy.

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

Dodaj komentarz