Real-Time Backend

Design: Uber/Lyft

Нажатие кнопки 'Вызвать' в Uber запускает цепочку из дюжины распределённых сервисов. Через 30 секунд на экране - водитель, маршрут и точная минута прибытия. Это не магия - это конкретная архитектура с конкретными трейдофами.

  • Uber обрабатывает более 2 млрд поездок в год и 100 000 ETA-запросов в секунду в пиковые часы
  • Время матчинга водитель-пассажир составляет в среднем менее 30 секунд при 6 млн+ активных водителей
  • Location Service принимает 1.5 млн обновлений координат в минуту только от водителей - без специализированного геоиндекса это невозможно
  • Lyft, DiDi, Grab строят идентичные системы - паттерны ride-hailing стали индустриальным стандартом для геораспределённых realtime-платформ

Архитектура Uber

Uber обрабатывает более 2 млрд поездок в год. На платформе зарегистрировано свыше 6 млн водителей в 70+ странах. В пиковые часы система принимает десятки тысяч запросов в секунду. Монолит с таким трафиком невозможен физически.

Архитектура разбита на домены: поездки (Trip Service), геолокация (Location Service), матчинг (Dispatch), ETA, оплата, уведомления. Каждый домен - отдельная группа сервисов с независимым деплоем и масштабированием.

Uber использует Apache Kafka как центральную шину событий. Каждое изменение состояния поездки публикуется в топик - ETA, Dispatch, Payment и Notifications подписаны независимо. Это позволяет добавлять новые потребители без изменения продюсеров.

Хранилище данных разделено по доменам. Trip Service использует MySQL с горизонтальным шардированием по trip_id. Location Service - in-memory Riak или Redis для хранения последних координат водителей. Поиск по координатам требует специализированного геоиндекса.

СервисХранилищеПричина
Trip ServiceMySQL (sharded)ACID, история поездок
Location ServiceRedis / RiakSub-millisecond обновления координат
DispatchIn-memory + KafkaStateful matching, скорость важнее персистентности
ETA ServiceDynamoDB / CassandraВысокая read-нагрузка, eventual consistency OK

Почему Uber использует отдельный сервис для геолокации вместо хранения координат в Trip Service?

Driver-Rider Matching

Dispatch Service - самая чувствительная часть Uber. Среднее время до принятия заказа водителем составляет менее 30 секунд. Алгоритм матчинга запускается каждые несколько секунд и перебирает кандидатов в радиусе от точки подачи.

Первый этап - пространственный поиск. Geohash делит поверхность Земли на иерархические ячейки. Precision 6 (около 1.2 км x 0.6 км) дает приемлемый баланс - в каждой ячейке обычно несколько доступных водителей. Поиск начинается с текущей ячейки и расширяется на соседние, если кандидатов недостаточно.

Второй этап - скоринг кандидатов. Расстояние важно, но не единственный фактор. Uber учитывает: рейтинг водителя, тип автомобиля (UberX / Black / XL), текущую загруженность водителя, историю принятия/отклонения заказов и время ожидания конкретного пассажира.

  • Proximity score: линейная функция от расстояния, вес ~40%
  • Driver rating: нормализованный рейтинг 1-5, вес ~20%
  • Acceptance rate: водители с высоким процентом принятия получают приоритет
  • Vehicle match: тип запрошенного сервиса (Pool, X, Black, XL)
  • Supply/demand: в условиях дефицита водителей расширяется радиус и снижаются пороги

Предложение поездки отправляется одному водителю за раз, не всем сразу. Если водитель не отвечает в течение ~15 секунд - заказ переходит к следующему кандидату. Параллельный оффер нескольким водителям создаёт race condition: несколько водителей едут к пассажиру, один "побеждает", остальные теряют время.

ПроблемаРешениеТрейдоф
Водитель не отвечаетТаймаут 15с, следующий кандидатРастёт время ожидания пассажира
Несколько запросов для одного водителяDistributed lock (Redis SETNX) на driver_idНебольшая латентность локинга
Водитель принял заказ, пока система думалаOptimistic locking + версионирование состоянияПовторные попытки при конфликте
Зоны с дефицитом водителейSurge pricing + расширение радиуса поискаПассажир ждёт дольше или платит больше

Почему Uber предлагает поездку водителям последовательно (один за раз), а не параллельно всем кандидатам сразу?

ETA Calculation

ETA - не просто расстояние, делённое на скорость. Uber вычисляет время прибытия с учётом реального трафика, историческихпаттернов по времени суток и дню недели, текущих аварий и дорожных событий, предпочтений маршрута водителя.

Архитектурно ETA Service строится поверх графа дорог. Вершины - перекрёстки, рёбра - отрезки дорог с динамическим весом (время проезда). Алгоритм Дейкстры или A* находит кратчайший путь по времени, а не по расстоянию.

Uber заранее прогревает кэш ETA для популярных зон. В Нью-Йорке или Сан-Франциско тысячи пар (origin, destination) запрашиваются ежеминутно. Ответ за 50-100 мс достигается через предвычисленные shortest-path деревья от ключевых точек (аэропорты, вокзалы, центры деловых районов).

Для масштаба: Uber обрабатывает пиковую нагрузку около 100 000 ETA-запросов в секунду глобально. Граф дорог только для США содержит десятки миллионов вершин и рёбер. Обход такого графа в реальном времени требует агрессивной сегментации: каждый город/регион имеет изолированный подграф.

  • Дейкстра — Гарантирует оптимальный путь, обходит весь граф от источника. Подходит для офлайн-прекомпутации. Для реального времени слишком медленен на больших графах.
  • A* с эвристикой — Направляет поиск в сторону цели через heuristic (геодезическое расстояние). В 3-10x быстрее Дейкстры на практике. Используется Uber для real-time routing.
  • Contraction Hierarchies — Предобрабатывает граф, добавляя shortcut-рёбра через важные узлы. Запросы в 1000x быстрее Дейкстры. Google Maps и HERE используют этот подход.

Почему ETA в Uber не равна расстоянию / среднюю_скорость?

Live Map и Routing

Живая карта в приложении - это не просто картинка. Каждый водитель отправляет GPS-координаты каждые 4 секунды. 6 млн активных водителей = 1.5 млн событий в минуту только от координат. Location Service должен принять, обработать и раздать эти данные заинтересованным пассажирам в реальном времени.

Архитектура потока координат строится на нескольких слоях. Водитель отправляет HTTP/WebSocket на Location Service. Сервис обновляет Redis GEORADIUS с координатами и публикует событие в Kafka. Dispatch Service и пользовательские WebSocket-соединения подписаны на изменения через Kafka Consumer Groups.

Тайлы карты - отдельная задача. Uber использует собственные векторные тайлы поверх OpenStreetMap и собственных данных о дорогах. Тайлы кешируются на CDN (Cloudfront/Fastly) по координатам и zoom-уровню. Клиент запрашивает только тайлы текущего viewport.

  • Векторные тайлы (.pbf / Mapbox Vector Tiles) - в 10x меньше растровых PNG при том же качестве
  • CDN кеш: тайлы по zoom 14-16 для крупных городов прогреваются заранее
  • Dead reckoning: клиент интерполирует позицию водителя между обновлениями для плавной анимации
  • Bearing-aware rendering: иконка машины поворачивается плавно по направлению движения

WebSocket vs HTTP polling: Uber использует persistent WebSocket для активных поездок. Polling каждые 4 секунды создаёт 15 запросов/минуту с головой в 60-100 байт HTTP header на каждый. WebSocket после handshake передаёт чистые данные без overhead. При миллионах одновременных поездок разница в нагрузке существенна.

Для показа живой карты достаточно периодически запрашивать координаты через REST API

Живая карта требует WebSocket для push-уведомлений о движении, dead reckoning для плавной анимации и CDN-кешированных векторных тайлов - три независимых механизма

REST polling создаёт постоянную задержку в N/2 секунд (половина интервала опроса), видимые рывки в движении иконки и лишние HTTP-соединения под нагрузкой. Каждый из трёх механизмов решает отдельную проблему.

Зачем клиент Uber использует dead reckoning для позиции водителя на карте?

Итоги

  • Микросервисная декомпозиция по доменам (Trip, Location, Dispatch, ETA) - каждый сервис масштабируется независимо и использует оптимальное хранилище
  • Геохеш + Redis GEORADIUS - стандартный паттерн для поиска водителей в радиусе; последовательный оффер предотвращает thundering herd среди водителей
  • ETA строится на A* по графу дорог с динамическими весами рёбер от реального трафика - расстояние / скорость принципиально недостаточно
  • Live map требует трёх независимых механизмов: WebSocket push, dead reckoning на клиенте и CDN-кешированные векторные тайлы

Связанные темы

Система Uber пересекается с несколькими фундаментальными областями системного дизайна.

  • WebSocket и realtime коммуникация — Live map и уведомления водителю строятся на persistent WebSocket соединениях
  • Geospatial индексы — Geohash и Redis GEORADIUS - основа для поиска водителей в радиусе
  • Графовые алгоритмы — A* и Contraction Hierarchies для построения маршрутов и расчёта ETA
  • Apache Kafka — Шина событий для координации между Dispatch, Location, ETA и Notifications

Вопросы для размышления

  • Как изменится архитектура матчинга, если Uber захочет поддерживать carpooling с несколькими пассажирами на одном маршруте?
  • Какие данные нужно собирать и как обрабатывать, чтобы ETA была точной в незнакомом для алгоритма городе с мало исторических данных?
  • При отказе Kafka весь Dispatch встаёт или деградирует частично - как спроектировать fallback для критичного пути матчинга?

Связанные уроки

  • sd-16-uber
Design: Uber/Lyft

0

1

Войти