Дизайн мессенджера Telegram
Published: 2023-10-24 01:23:09

## Задача. 
Задизайнить мессенджер по типу 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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/28xjz82qkepggvy8hsqw.png)

Нам нужен 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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/e9cc9tdc6a8k01b1qhdy.png)

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

Нам также нужно временно хранить сообщения, на случай если получатель 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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/os9d55hp85hwlf1a1m62.png)



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

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



![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/oyu93tnxpv7pt63bu7bc.png)



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

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

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


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xhyk5hnkc5ruy0535jdw.png)