Jak We Made Szybcy GitHub


Original: https://github.com/blog/530-how-we-made-github-fast
Copyright: Tom Preston-Werner

Teraz to wszystko się osiedlił z przeprowadzką do Rackspace, chciałem trochę czasu, aby przejść przez zmiany w architekturze, że zrobiliśmy, aby przynieść Ci szybsze, bardziej skalowalne GitHub.

W mojej pierwszej wersji tego artykułu spędziłem wiele czasu wyjaśniając, dlaczego zrobiliśmy każdego z wyborów technologicznych, które zrobiliśmy. Po chwili jednak, stało się trudne do oddzielenia architekturę z dyskusji i cała sprawa stała się mylące. Tak więc zdecydowałem się po prostu wyjaśnić architekturę i następnie napisać serię kontynuacji posty bardziej szczegółowych analiz, dlaczego dokonaliśmy wyborów my.

Istnieje wiele sposobów, do skali nowoczesnych aplikacji internetowych. Co będę tutaj opisuję jest metoda, która wybraliśmy. To powinno w żadnym wypadku uznać za jedyną drogę do skalowania aplikacji. Rozważmy to na przykładzie tego, co pracował dla nas zważywszy nasze unikalne wymagania.

Zrozumienie protokoły

Mamy trzy podstawowe protokoły wystawiać końcowym użytkownikom GitHub: HTTP, SSH, i git. Podczas przeglądania strony w Twojej ulubionej przeglądarce, używasz HTTP. Podczas klonowania, wyciągnąć lub wcisnąć do prywatnego URL podobny git@github.com: mojombo / jekyll.git robisz tak przez SSH. Podczas klonowania lub wyciągnąć z publicznego repozytorium za pośrednictwem adresu URL jak git :/ / github.com / mojombo / jekyll.git używasz protokołu git.

Najłatwiej zrozumieć architekturę jest śledzenie, jak każdy z tych wniosków propaguje za pośrednictwem systemu.

Tracing żądania HTTP

W tym przykładzie pokażę jak żądanie strony drzewa, takich jak http://github.com/mojombo/jekyll dzieje.

Pierwszą rzeczą, Twój wniosek trafi po schodzili z internetu jest aktywne równoważenia obciążenia. Do tego zadania użyjemy parę wystąpień uruchomionych ldirectord Xen. Są to tak zwane lb1a i lb1b. W każdej chwili jeden z nich jest aktywny, a drugi czeka przejęcia w przypadku awarii wzorca. Równoważenia obciążenia nie robi nic ozdobnego. To przekazuje pakiety TCP do różnych serwerów na podstawie żądanego adres IP i port i można usunąć serwery niewłaściwie z puli bilansowy w razie potrzeby. W przypadku, gdy nie są dostępne dla serwerów danej puli może służyć prostej statycznej strony zamiast połączeń odmawiających.

Na wnioski o stronie głównej, równoważenia obciążenia zwykle Twoje zapytanie off do jednego z czterech maszyn nakładka. Każdy z nich jest 8 core, 16GB RAM goły serwer metal. Ich nazwy są FE1, …, fe4. Nginx zaakceptuje połączenie i wysyła go do gniazda domenie Unix na której szesnaście procesy Unicorn worker zaznaczania. Jeden z tych pracowników chwyta żądanie i uruchamia Rails kod niezbędny do wypełnienia go.

Wiele stron wymaga wyszukiwań bazy danych. Nasza baza danych MySQL działa na dwóch 8 rdzeniu, 32GB RAM gołe serwery metalowe z 15K RPM dysków SAS. Ich nazwy są db1a i db1b. W danym momencie, jeden z nich jest master i jeden slave. Replikacja MySQL odbywa się poprzez DRBD.

Jeśli strona wymaga informacji o repozytorium Git i że dane nie są buforowane, a następnie będzie korzystać z naszej biblioteki Grit do pobierania danych. W celu dostosowania się do naszej konfiguracji Rackspace, mamy zmodyfikowany Grit zrobić coś specjalnego. Zaczynamy od abstrahowania z każdą rozmowę, która potrzebuje dostępu do systemu plików do Grit :: obiektu Git. Następnie wymień Grit :: Git z bolcem, który sprawia wywołań RPC do naszego serwisu dymu. Smoke ma bezpośredniego dostępu do dysku do repozytoriów i zasadniczo prezentuje Grit :: Git jako usługa. To się nazywa Pali bo dymu jest tylko Grit w chmurze. Łapiesz?

Zgaszone Grit czyni wywołań RPC do palenia, który jest obciążenie zrównoważone odwzorowana powrotem do fe maszyn. Każda nakładka prowadzi cztery ProxyMachine instancji za HAProxy które działają jako serwery proxy routing połączeń dymu. ProxyMachine jest mój świadomy treści (warstwa 7) routing TCP proxy, który pozwala nam pisać routingu logikę w Ruby. Proxy bada wniosek i wyodrębnia nazwę użytkownika repozytorium, które zostały określone. Następnie za pomocą opatentowanej biblioteki o nazwie Komin (to kieruje dym!) Do wyszukiwania trasy dla tego użytkownika. Użytkownika trasa jest po prostu nazwa serwera plików, na których ogłoszenia repozytoria są trzymane.

Komin znajduje trasę poprzez wywołanie Redis. Redis działa na serwerach baz danych. Używamy REDiS jako trwały klucz / wartość magazynu dla informacji routingu oraz wiele innych danych.

Po proxy Smoke ustalił użytkownika trasę, ustanawia transparent proxy do właściwego serwera plików. Mamy cztery pary archiwizowanych. Ich nazwy są fs1a, fs1b, …, fs4a, fs4b. Są to 8 core, 16GB RAM gołe metalowe serwery, każdy z sześciu 300GB 15K RPM SAS napędza umieszczony w RAID 10. W danym momencie jeden serwer w każdej parze jest aktywny, a drugi czeka na przejęcie powinno tam być błąd krytyczny w Masterze. Wszystkie dane repozytorium stale replikowane z Master na Slave za DRBD.

Każdy serwer plików działa dwa Ernie RPC serwerów za HAProxy. Każdy Ernie spawns 15 Ruby pracowników. Pracownicy ci podjąć RPC połączenia i odtwarzania i wykonać Grit połączenie. Odpowiedź jest wysyłana z powrotem przez pełnomocnika Smoke do Rails app gdzie Grit stub zwraca oczekiwanej reakcji nieśmiertelnych.

Kiedy Unicorn wykończona Rails działania, odpowiedź jest wysyłana z powrotem przez Nginx i bezpośrednio do klienta (wychodzące odpowiedzi nie wracać przez równoważenia obciążenia).

Wreszcie można zobaczyć ładną stronę internetową!

Powyżej przepływu, co się dzieje, gdy nie ma odsłon cache. W wielu przypadkach kod Rails używa Evan Tkacza Ruby memcached klienta do kwerendy serwerów memcache, które działają na każdym serwerze plików niewolnikami. Ponieważ maszyny te są bezczynny, kładziemy 12GB Memcache na każdym. Serwery te są aliasami jak

memcache1, …, memcache4.
BERT i BERT-RPC

Dla naszej serializacji danych i protokół RPC używamy BERT i Bert-RPC. Jeszcze nie słyszałem o nich wcześniej, bo są zupełnie nowe. Wymyśliłem je, ponieważ nie byłem zadowolony z żadnego z dostępnych opcji, że ocenianych i chciałem poeksperymentować z pomysłu, że miałem przez chwilę. Przed freak out o syndrom NIH (lub pomóc ulepszyć Freak Out), proszę przeczytać mój artykuł towarzyszący Wprowadzenie BERT i Bert-RPC, jak te technologie stała się i co zamierzam im rozwiązać.

Jeśli wolisz po prostu sprawdzić spec, zaglądamy http://bert-rpc.org.

Dla kodu głodny, sprawdź moją BERT Ruby serializacji biblioteki BERT, mój Ruby BERT-RPC BERTRPC i mojego Erlang / Ruby hybryda BERT-RPC serwer Ernie. Są dokładne biblioteki używamy na GitHub służyć do wszystkich danych w repozytorium.

Tracing żądanie SSH

Git używa SSH do szyfrowanej komunikacji między użytkownikiem a serwerem. Aby zrozumieć, w jaki sposób nasza architektura dotyczy połączeń SSH, jest pierwszym ważnym, aby zrozumieć, jak to działa w prostszej konfiguracji.

Git opiera się na fakcie, że SSH pozwala na wykonywanie poleceń na zdalnym serwerze. Na przykład, polecenie ssh tom @ frost ls-al działa ls-al w katalogu domowym mojego użytkownika na serwerze mrozu. Uzyskać wynik polecenia na moim lokalnym terminalu. SSH jest zasadniczo dołączeniem stdin, stdout i stderr, urządzenia zdalnego do lokalnego terminalu.

Jeśli uruchomić polecenie git clone jak tom @ mróz: mojombo / bert, co Git robi za kulisami jest SSHing na mróz, uwierzytelniania jako użytkownik tom, a następnie zdalne wykonanie git upload-Pack mojombo / bert. Teraz klient może rozmawiać z tego procesu na zdalnym serwerze, po prostu czytanie i pisanie za pomocą połączenia SSH. Neat, co?

Oczywiście, umożliwiając wykonanie dowolnego poleceń jest niebezpieczny, więc SSH zawiera możliwość ograniczenia jakie polecenia mogą być wykonywane. W przypadku bardzo prosty, można ograniczyć egzekucję do git-shell który jest dołączony do Git. Wszystko to nie jest skrypt zaznacz polecenie, które starasz się wykonać i zapewnić, że jest to jedna z git załadować-pack, git otrzyma-pack lub git upload-archiwum. Jeśli jest jednym z tych, w istocie, że używa exec zastąpić obecny proces z tego nowego procesu. Po tym, to tak, jakbyś właśnie wykonywane to polecenie bezpośrednio.

Więc teraz, że wiesz, jak Git jest SSH praca operacje, w prostym przypadku, pozwól mi pokazać, jak my sobie z tym poradzić w architekturze GitHub jest.

Po pierwsze, klient Git inicjuje sesję SSH. Połączenie przychodzi z internetu i uderza nasz równoważenia obciążenia.

Stamtąd, połączenie jest wysłany do jednego z nakładki którym sshd akceptuje. Mamy załatana naszą SSH klucza publicznego do wykonywania wyszukiwań z naszej bazy danych MySQL. Klucz identyfikuje użytkownika GitHub i ta informacja jest wysyłana wraz z oryginalnym polecenia i argumenty do naszej zastrzeżonej skrypt o nazwie Gerve (Git służyć). Pomyśl Gerve jako super wersji inteligentnej z git-shell.

Gerve sprawdza, czy użytkownik ma dostęp do repozytorium podanego w argumentach. Jeśli jesteś właścicielem składowiska, nie podglądy baz danych muszą być wykonywane, inaczej kilka zapytań SQL są do ustalenia uprawnień.

Gdy dostęp został zweryfikowany, Gerve używa Chimney wyszukać trasę właściciela repozytorium. Celem jest teraz wykonać oryginalną polecenie właściwego serwera plików i podłączyć lokalną maszynę do tego procesu. Czy jest lepszy sposób, aby to zrobić, niż z innym zdalnego wykonania SSH!

Wiem, że to brzmi dziwnie, ale działa świetnie. Gerve prostu używa exec (3), aby zastąpić się z git tossh połączeń @ . Po tym połączenia, klient jest podłączony do procesu na maszynie nakładka które jest z kolei podłączony jest do procesu na serwerze plików.

Pomyśl o tym w ten sposób: po określeniu uprawnień i lokalizację repozytorium, nakładka staje się przezroczyste proxy do końca sesji. Jedyną wadą tego podejścia jest to, że wewnętrzny SSH jest niepotrzebnie obciążone napowietrznej szyfrowania / deszyfrowania, gdy żaden nie jest ona konieczna. Jest to możliwe, możemy zastąpić ten to połączenie wewnętrzne SSH z czymś bardziej efektywny, ale takie podejście jest po prostu zbyt cholernie proste (i nadal bardzo szybko), aby mnie martwić bardzo.

Śledzenie Poproś Git

Sprawującą publiczne klonów i ciągnie przez Git jest podobny do tego, jak działa metoda SSH. Zamiast korzystać z SSH do uwierzytelniania i szyfrowania, jednak opiera się na stronie serwera demona Git. Ten demon akceptuje połączenia, sprawdza polecenia do wykonania, a następnie używa fork (2) i exec (3) na tarło pracownika, który następnie staje się proces dowodzenia.

Mając to na uwadze, pokażę ci, jak public operacja clone działa.

Po pierwsze, twoje problemy klienta Git wniosek zawierający nazwę polecenia i repozytorium chcesz sklonować. Niniejszy wniosek wchodzi w nasz system na równoważenia obciążenia.

Stąd wniosek został wysłany do jednego z interfejsów. Każda nakładka prowadzi cztery ProxyMachine instancji za HAProxy które działają jako routing proxy dla protokołu Git. Proxy sprawdza wniosek i wyodrębnia nazwę użytkownika (lub nazwy GIST) z repo. Następnie wykorzystuje Komin do wyszukiwania trasy. Jeśli nie ma trasy lub inny błąd, mówi proxy protokołu Git i wysyła odpowiednie komunikaty do klienta. Gdy trasa jest znana nazwa repo (np. mojombo / bert) przekłada się na jej drodze na dysku (np. a/a8/e2/95/mojombo/bert.git). Na naszych starych instalacji, które nie miały pełnomocnictw, musieliśmy użyć zmodyfikowanej demona, który może przekształcić użytkownika / repo do prawidłowego filepath. Robiąc ten krok w proxy, możemy teraz korzystać niemodyfikowany demona, co pozwala na znacznie łatwiejszą ścieżkę uaktualnienia.

Dalej, Git tworzy proxy transparent proxy z właściwego serwera plików i wysyła zmodyfikowany wniosek (z przebudowanej drogi repozytorium). Każdy serwer plików prowadzi dwa procesy Daemon Git leżących HAProxy. Demon mówi protokół plik pakietu i strumienie danych z powrotem przez proxy i bezpośrednio Git do klienta Git.

Gdy klient posiada wszystkie dane, już sklonowane repozytorium i można dostać się do pracy!
Sub-a Side-Systems

Oprócz podstawowej aplikacji internetowej i git hosting systemów, prowadzimy również szereg innych podsystemów i side-Systems. Podsystemy obejmują kolejkę zadań, pliki z archiwum, fakturowanie, mirroring, i svn importera. Side-systemy obejmują GitHub Pages, GIST, serwer klejnot, i kilka wewnętrznych narzędzi. Można oczekiwać wyjaśnień, jak niektóre z tych prac w ramach nowej architektury, i jakie technologie stworzyliśmy pomóc nasza aplikacja się gładko nowego.
Konkluzja

Architektura opisane tutaj pozwoliło nam właściwie skalować serwis i spowodowały ogromne zwiększenie wydajności w całej witrynie. Nasza średnia odpowiedź Rails czas na naszej poprzedniej konfiguracji było wszędzie od 500 ms do kilku sekund w zależności od obciążenia plastry były. Przeprowadzka do gołego metalu i przechowywania stowarzyszonego na Rackspace przyniósł nasz średni czas odpowiedzi Rails konsekwentnie poniżej 100ms. Ponadto, kolejka zadań teraz nie ma problemu z utrzymaniem się z 280.000 miejsc pracy tle przetwarzamy codziennie. Mamy jeszcze sporo zapasu do wzrostu z obecnego zestawu sprzętu, a gdy przychodzi czas, aby dodać więcej maszyn, możemy dodać nowe serwery na każdej kondygnacji z łatwością. Jestem bardzo zadowolony z tego, jak dobrze wszystko działa, a jeśli jesteś podobny do mnie, jesteś korzystających z nowych i ulepszonych GitHub na co dzień!
Masz uwagi? Niech @ github wiedzieć na Twitterze

Dbamy, aby odczytać każdy wzmianki na Twitterze. Jeśli znajdziesz jakiś błąd, przedkłada go support@github.com. Każdy e-mail jest czytane przez prawdziwego użytkownika.