Rozmowa o projektowaniu systemów: Zaprojektuj Ubera
„Zaprojektuj Ubera" to jedno z najczęstszych zadań z projektowania systemów, bo dotyka jednocześnie indeksowania geoprzestrzennego, strumieniowania lokalizacji w czasie rzeczywistym, dopasowywania (matching) i wysokiej przepustowości zapisu. Rekruter nie szuka prawdziwej architektury Ubera — chce zobaczyć, jak ograniczasz zakres problemu, podejmujesz uzasadnione decyzje i głośno rozumujesz o skali.
Pracuj w zamierzonym porządku: wyjaśnij wymagania, oszacuj skalę, naszkicuj podstawowe encje i API, zaprojektuj dane i ścieżkę dopasowania, a następnie przejdź do wąskich gardeł i kompromisów. Poniższy przewodnik trzyma się tego porządku, byś ćwiczył strukturę, a nie tylko odpowiedź.
1. Wyjaśnienie wymagań
Oddziel funkcjonalne od niefunkcjonalnych i ustal zakres przed projektowaniem. Pasażerowie zamawiają przejazd i widzą kierowcę zbliżającego się w czasie rzeczywistym; kierowcy łączą się online i odbierają zlecenia z okolicy; system dopasowuje ich i śledzi przejazd do zakończenia.
- Funkcjonalne: zamówienie przejazdu, dopasowanie do kierowcy w okolicy, śledzenie przejazdu na żywo, obliczanie opłaty, historia przejazdów.
- Niefunkcjonalne: dopasowanie o niskim opóźnieniu (sekundy), wysoka dostępność, aktualizacje lokalizacji w czasie rzeczywistym, spójność — kierowca przypisany do dokładnie jednego pasażera.
- Poza zakresem (powiedz to wprost): wewnętrzne przetwarzanie płatności, oceny, ML dynamicznych cen — wspomnij o nich, a potem odłóż na później.
2. Oszacowanie skali
Przybliżone liczby uzasadniają późniejsze decyzje. Przyjmijmy ~10 mln aktywnych codziennie kierowców wysyłających ping lokalizacji co 4 sekundy — to ~2,5 mln zapisów na sekundę danych lokalizacyjnych, obciążenie zdominowane przez zapis, które od razu wyklucza naiwną tabelę relacyjną jako ścieżkę krytyczną.
3. Główne komponenty i API
Naszkicuj usługi i przepływ żądań. Utrzymuj ścieżkę dopasowania niezależną od trwałego magazynu przejazdów, aby każda mogła skalować się według własnej charakterystyki.
- Usługa lokalizacji: przyjmuje pingi kierowców, przechowuje aktualne pozycje w indeksie geoprzestrzennym w pamięci.
- Usługa dopasowania: przy żądaniu przejazdu wyszukuje bliskich kierowców online i przypisuje jednego.
- Usługa przejazdów: trwały zapis cyklu życia przejazdu (zamówiony → dopasowany → w trakcie → zakończony).
- Powiadomienia/streaming: przesyła pozycję kierowcy na żywo do pasażera (WebSocket/long-poll).
4. Dopasowanie geoprzestrzenne — serce problemu
To właśnie najbardziej chce usłyszeć rekruter. Przechowywanie surowych lat/long i skanowanie ich to O(n); potrzebny jest indeks przestrzenny. Wyjaśnij quadtrees lub geohashing: podział mapy na komórki, dzięki czemu „znajdź kierowców w pobliżu tego punktu" staje się wyszukiwaniem kilku komórek, a nie pełnym skanem.
Geohashing koduje region w krótki ciąg znaków, gdzie wspólne prefiksy oznaczają bliskość przestrzenną, co zmienia „blisko" w zapytanie o prefiks. Aktualne komórki kierowców mieszkają w magazynie w pamięci (np. Redis) dla szybkich odczytów i częstych aktualizacji; granulacja komórek to regulowany kompromis między precyzją a rozrzutem zapytań.
5. Skalowanie i wąskie gardła
Nazwij punkty nacisku i sposób ich odciążenia. Strumień 2,5 mln zapisów lokalizacji na sekundę jest tu dominujący.
- Partycjonuj (shard) indeks lokalizacji geograficznie — kierowcy i pasażerowie jednego miasta są obsługiwani przez ten sam shard, co utrzymuje dopasowanie lokalnie.
- Pochłoń skok zapisów za pomocą magazynu w pamięci i asynchronicznych trwałych zapisów; nie każdy ping musi trafić do trwałej bazy danych.
- Zapewnij jednego kierowcę na jednego pasażera dzięki atomowemu przypisaniu (blokada lub aktualizacja warunkowa), aby uniknąć podwójnych rezerwacji.
- Użyj kolejki komunikatów (Kafka) między pozyskiwaniem lokalizacji a konsumentami niższego poziomu, aby oddzielić skoki obciążenia.
6. Kompromisy, które warto wypowiedzieć
Zakończ, nazywając napięcia, które docenia rekruter: silna spójność kontra ostateczna (przypisanie kierowcy musi być silne; wyświetlanie lokalizacji może być ostateczne), szybkość pamięci kontra trwałość, oraz precyzja rozmiaru komórki kontra rozrzut zapytań. Wyraźne nazwanie tego to różnica między odpowiedzią zaliczającą a mocną odpowiedzią.
Poćwicz projektowanie systemu Ubera z pomocą w czasie rzeczywistym
Asystent rozmów o projektowaniu systemów od Natively może utrzymać cię w uporządkowanej strukturze podczas rozmowy na żywo — proponując następny wymiar do omówienia i pokazując kompromisy w czasie rzeczywistym, wszystko na twoim własnym urządzeniu.
