Давайте сегодня представим, что на System Design попросили спроектировать WhatsApp. Пройдемся по плану.
Начинаем с функциональных требований. Подумаем, что входит в классический сценарий пользователя?
⬛Миша и Маша обмениваются сообщениями.
⬛У SMS каждого статус: отправлено, доставлено, прочитано.
⬛В чате видна история переписки.
Теперь можно подкинуть дополнительных вопросов. ⬛Только текст или еще нужны какие-то форматы (фото, видео, гео и т.д.)? Нет, только текст.
⬛Можно ли изменять отправленные сообщения? Нельзя.
⬛Групповые чаты? Нет.
⬛Звонки? Нет.
⬛Банить? Нет.
⬛Нужен ли статус пользователя: «онлайн» / «был в сети 3 часа назад»? Да.
⬛Нужно ли чистить историю старше 5 лет? Не важно, давайте скипаем.
Дальше уточняем технические требования.
⬛High availability — договариваемся, что сервис будет доступен 99,99% времени (наши любимые технические проекты про девятки).
⬛Low latency — нужно, чтобы обмен сообщениями происходил быстро, должно быть похоже на общение в реальном времени.
⬛Scalability — приложение будет использоваться для большого числа пользователей.
◾Ежедневно — 1 000 000 000 пользователей.
◾Всего 3 000 000 000 пользователей, ждем рост до 5 000 000 000.
◾Каждый пользователь в среднем днем шлет 10 сообщений.
◾Средний размер сообщения — 100 бит.
⬛Security — мы хотим, чтобы частная переписка осталась частной.
На этом достаточно! Не закапывайтесь в этот этап. 10 минут — максимум. Понятно, что можно закопаться на весь час: юрисдикции, спам, детские аккаунты и так далее. Не надо!
Дальше можно начать с модели данных. У нас есть объект «сообщение»: пользователь отправляет его, у сообщения меняется статус, отправитель получает уведомление, получатель читает сообщение.
Итого над каждым сообщением можно сделать три операции записи:
1️⃣создать сообщение (статус «отправлено»),
2️⃣изменить статус на «доставлено», изменить
3️⃣статус на «прочитано».
И три операции чтения:
1️⃣-2️⃣прочитать изменения статуса на «доставлено» и «прочитано»,
3️⃣прочитать само сообщение.
Вот такой цикл жизни получился у самой важной сущности.
Давайте перейдем к API. Отправлять сообщение попробуем через WebSocket.
Формат:
{
"type": "message",
"from": "Misha_id",
"to": "Masha_id",
"content": "Hello! How are you?",
"timestamp": 1786130648
}Второй вид взаимодействия — обновление статуса:
message_status: message_id, statusТретий вид сообщений — статус пользователя. Когда мы онлайн, приложение каждые 5 минут шлет
user_online_status с user_id и timestamp (сервер хранит только последнее).Когда Маша хочет узнать Мишин статус, отправляет
get_user_status, а в ответ получает user_status_response с timestamp.Давайте нарисуем архитектуру. Сделала несколько картинок.
Посмотрите первую — это установка соединения через WebSocket.
⬛Отправляем запрос на API Gateway.
⬛Потом Load Balancer отправляет нас на один из серверов WebSocket Handler
⬛именно с ним мы будем в дальнейшем держать канал для связи.
⬛Кроме установки соединения Handler сходит в WebSocket Connection Manager. Он помогает искать, с каким Handler соединен наш собеседник.
⬛Connection Manager хранит актуальный маппинг между пользователями и Handler'ами. Пусть для этого будет WebSocket Connection Cache.
Вторая картинка — работа с сообщениями.
⬛Миша отправляет сообщение. Сначала идем в Message Service: он сохраняет сообщение в БД. Только после этого Миша получает подтверждение, что сообщение отправлено. Теперь нам не важно, в сети Маша или нет.
⬛Дальше узнаем номер Машиного WS Handler. Если его нет в map, значит, она offline. Больше ничего делать не нужно.
⬛Если вернулся — ура! Передаем ему сообщение, и он отправит его Маше по открытому двустороннему каналу.
Note: Message Service при подключении пользователя может собрать все сообщения, где он получатель, но которые в статусе SEND.
В следующей части разберем статусы сообщений и пользователей, а потом нырнем в детали архитектуры.
#бабанюра_программирует

