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 (2)

Collapse
 
alex282882 profile image
Alex Sh

Great article!

Just one question.
Could you clarify the missing link between the Message Server and the WebSocket Manager?
After the Message Server processes a message, how does it notify the WebSocket Manager to deliver that message to the recipient? What mechanism is in place to ensure the WebSocket Manager is aware of messages ready for delivery?

Collapse
 
faangmaster profile image
faangmaster

Once a new connection is established with a user, the WebSocket server can call the Message Server to retrieve any undelivered messages and push them to the user device.