DEV Community

faangmaster
faangmaster

Posted on

Дизайн мессенджера Telegram

Задача.

Задизайнить мессенджер по типу Telegram или WhatsApp.

Решение.

Уточняем требования.

Функциональные требования:

  • Система должна поддерживать одиночные и групповые чаты.
  • Система должна поддерживать уведомления о доставке сообщений, такие как "отправлено", "доставлено" и "прочитано" (одна галочка, 2 галочки и синие галочки).
  • Система должна поддерживать обмен изображениями, видео и аудио.
  • Система должна обеспечивать постоянное хранение чат-сообщений, когда пользователь находится в офлайне, до успешной доставки сообщений.
  • Система должна уметь уведомлять офлайн-пользователей о новых сообщениях, как только их статус становится онлайн.

Не функциональные требования:

  • Пользователи должны получать сообщения с минимальной задержкой (low latency).
  • Консистентность. Сообщения должны доставляться в том порядке, в котором они были отправлены. Кроме того, пользователи должны видеть одну и ту же историю чатов на всех своих устройствах.
  • Система должна обеспечивать high availability. Но консистентность при этом не должна страдать.
  • Система должна обеспечивать безопасность **с помощью **end-to-end шифрования. End-to-end шифрование гарантирует, что только две взаимодействующие стороны могут видеть содержание сообщений. Никто посередине, даже Telegram, не должен иметь доступ к нему.
  • Масштабируемость: Система должна быть масштабируемой, чтобы поддерживать растущее количество пользователей и сообщений в день.

Оценка требуемых ресурсов.

Пусть у нас 2 миллиарда пользователей, которые пересылают 100 миллиардов сообщений в день.
Storage
Предположим, что каждое сообщение в среднем занимает 100 байт. Кроме того, серверы хранят сообщения до 30 дней. Т.е. если пользователь не подключается к серверу в течение этого времени, сообщения будут удалены с сервера навсегда.

100 миллиардов сообщений/день * 100 байт/сообщение = 10 терабайт/день

За 30 дней объем хранилища будет следующим:

30 * 10 терабайт/день = 300 терабайт/месяц

Помимо чатов, у нас также есть медиа-файлы, которые занимают более 100 байт на сообщение. Кроме того, нам необходимо хранить информацию о пользователях и метаданные сообщений, такие как временная метка, идентификатор и так далее. В процессе также необходимо обеспечивать шифрование и расшифровку для безопасной коммуникации. Поэтому нам также понадобится хранить ключи шифрования и соответствующие метаданные. Таким образом, более точно нам потребуется более 300 терабайт в месяц, но для упрощения давайте оставим цифру 300 терабайт в месяц.
Пропускная способность сети
Нам надо пропускать через систему ~10 терабайт в день, или 10 TB/(24 * 3600 секунд) ~ 121 Mb/s ~ 970 Мбит/с. Это все без учета картинок и видео, только для текста.
Число серверов
Тут все зависит от числа соединений, которые может поддерживать один сервер. Пусть у нас число соединений, которые нам нужно держать одновременно ~2 миллиардов. Если, скажем, один сервер может поддерживать 50K соединений одновременно, то нужно 2B/50K = 40K серверов. Существуют оптимизации, которые позволяют держать намного больше соединений на одном сервере, в таком случае нам потребуется сильно меньше серверов.

High Level Design

Первоначальная идея дизайна:

Image description

Нам нужен Chat Server, который будет отвечать за установления соединений с пользователями, пересылать сообщения, сохранять их в базе, если получатель сообщения offline, а также отвечать за отправку состояний пересылки сообщения (одна, две галочки или синии галочки).

Процесс отправки и получения сообщения выглядит следующим образом:

  • User A и User B создают канал связи с чат-сервером.
  • User A отправляет сообщение на чат-сервер.
  • Получив сообщение, чат-сервер отправляет подтверждение пользователю A.
  • Чат-сервер отправляет сообщение пользователю B и сохраняет его в базе данных, если пользователь offline
  • Пользователь B отправляет подтверждение чат-серверу.
  • Чат-сервер уведомляет пользователя A о том, что сообщение успешно доставлено.
  • Когда пользователь B прочитывает сообщение, приложение уведомляет чат-сервер.
  • Чат-сервер уведомляет пользователя A о том, что пользователь B прочитал сообщение.

Детальный дизайн.

High Level идея, описанная ранее, не отвечает на следующие вопросы:

  • Как создается канал между клиентами и серверами?
  • Как можно масштабировать этот дизайн на миллиарды пользователей?
  • Как хранятся данные пользователя?
  • Как идентифицируется получатель, которому доставляется сообщение?

Попробуем ответить на эти вопросы более делатьно.

Соединение клиента с сервером

Соединение с клиетом будем держать при помощи WebSocket. Это позволит постоянно держать соединение открытым со всеми online пользователями и позволит пушить сообщения и нотификации клиенту в режиме реального времени с минимальной задержкой (low latency). Для поддержания WebSocket соединения будем использовать WebSocket сервера, каждый из которых будет отвечать за предоставление порта для каждого клиента. Маппинг серверов, портов и пользователей будем хранить в кэше, например, в Redis, из которого будет читать WebScoket Manager.

Image description

Хранение сообщений

Нам также нужно временно хранить сообщения, на случай если получатель offline. Причем на какое-то максимальное время (например, 30 дней), после которого они должны быть удалены автоматически (TTL - time to live). Для этого добавим еще одну компоненту - Message Server и базу, в которой он будет это хранить. Например, для этого можно использовать базу Mnesia.

Отправка сообщений будет работать следующим образом:

  • Пользователь отправляет сообщение, оно по WebSocket протоколу попадает на WebSocket Server.
  • Этот WebSocket Server временно сохранит сообщение в базе, через Message Server.
  • Также он узнает WebSocket Server, порт, статус получателя через WebSocket Manager.
  • Если получатель online, то сообщение будет отправлено получателю через его WebSocket Server и удалено из базы сообщений.
  • Если получатель offline, то сообщение будет в базе. Как только он станет online и будет добавлена запись в WebSocket Manager - мы прочитаем все недоставленные сообщения из Message Server и отправим получателю, после чего удалим их из базы (после подтверждения доставки).

Image description

Отправка медиа файлов
Медиа файлы будем хранить в blob-хранилище по типу AWS S3. Также добавим к нему кэш и CDN на случай если какие-то медиа файлы будут просматриваться множество раз.

Работать пересылка картинок и видео будет работать следующим образом.
Файл будет зашифрован и заархивирован на стороне клиента, будет отправлен в MediaFiles Service, который вычислит hash файла. Если файл с таких hash уже есть, то снова его сохранять не будем. Если нет - то сохраним в blob-хранилище, присвоим id и отправим этот id получателю внутри сообщения. Клиент получателя по этому id обратится к MediaFiles Service для получения файла, MediaFiles Service получит файл из blob-хранилища или из кэша или CDN и отправит получателю.

Image description

Отправка групповых сообщений

И наконец, расширим наш дизайн поддержкой груповых чатов.
Для этого нам надо хранить информацию о пользователях, чатах, и кто в какой группе состоит в какой-то базе. Например, это может быть MySQL cluster, с множествами репликами в разных географических зонах. Также добавим расспределенный кэш для быстрого доступа к этой информации. Возьмем, например, Redis. Доступ к этой информации будет осуществлятся через Group Service.

Как только приходит какое-то сообщение через WebSocket, в котором получатель это группа, то такое сообщение попадает в Message Server, который отправит это сообщение в Kafka. Из Kafka это сообщение читает GroupMessage Handler, который из Group Service берет список всех получателей по id группы и в зависимости от их статуса отправит сообщение или через их WebSocket Server или просто сохранит сообщение для них в базе, пока они не станут on-line и не прочитает его.

Image description

Top comments (0)