---
title: Алекс Сюй, «System Design. Подготовка к сложному интервью» (Vol. 1, рус. изд.) — глава 12: Мгновенный обмен сообщениями (мессенджер)
source: materials/System Design. Подготовка к сложному интервью.pdf, стр. 197–221
конспект: TASK-45.38, извлечено pymupdf 2026-09-17; ниже полный «грязный» текст главы + выжимка
статус: выжимка и перенос в knowledge-base.md / methodology.md / classic-designs.md — см. _map.md
---

# Глава 12. Мгновенный обмен сообщениями (мессенджер)

## Выжимка

Мессенджер на 50 млн DAU, приватные + группы ≤100, presence, мультиустройство, push.

**Протокол приёма — ключевой выбор**:

- **HTTP-опрос**: клиент опрашивает сервер — расточительно.
- **Длинный HTTP-опрос (long polling)**: соединение держится до сообщения/таймаута; минусы: отправитель и получатель на разных серверах, сервер не видит дисконнект клиента.
- **WebSocket**: двунаправленный, постоянный, upgrade от HTTP, порты 80/443 (фаерволы пропускают) — дефолт и для отправки, и для получения.

**Архитектура**: stateless-часть (логин, профиль, группы) по HTTP за LB; **stateful чат-серверы** держат ws-соединения; **service discovery (Zookeeper)** подбирает лучший чат-сервер клиенту; push-уведомления — сторонний сервис (гл. 10).

**Хранилище**: профили/настройки — реляционная БД; **история сообщений — KV** (HBase у Facebook, Cassandra у Discord): гигантский объём, доступ в основном к свежим чатам, горизонтальный масштаб, низкая латентность. `message_id` — уникален и сортируем по времени (snowflake гл. 7 либо **локальный** генератор в рамках канала — достаточно упорядочивать внутри беседы; `created_at` не годится — коллизии). Групповой чат: составной ключ `(channel_id, message_id)`, channel_id — ключ партиции.

**Доставка**: приватный — очередь синхронизации; **групповой: копия в «почтовый ящик» (sync queue) каждого участника** — просто и дёшево для малых групп (у WeChat ≤500); оффлайн → push. **Синхронизация устройств**: каждое держит `cur_max_message_id`, новое = `id > cur_max` на своего получателя.

**Presence**: ws-соединение → статус online + `last_active_at` в KV; сердцебиение (heartbeat) — чтобы индикатор не мигал при кратковременных разрывах.

Перенос: classic-designs §3 (сверка), KB §14.7 (протоколы, mailbox-модель, presence).

## Полный текст (грязная выгрузка)

12 
ПРОЕКТИРОВАНИЕ СИСТЕМЫ 
МГНОВЕННОГО ОБМЕНА СООБЩЕНИЯМИ
В этой главе мы исследуем архитектуру системы мгновенного обмена со­
общениями или чата. Чатами пользуются почти все. На рис. 12.1 показаны 
некоторые из самых популярных приложений на рынке.
Whatsapp
Facebook messenger
Discord
Google Hangout
Line
Wechat
Рис. 12.1
Люди используют чаты для разных задач. Мы обязательно должны 
определить точные требования к нашей системе. Например, вам вряд ли 
захочется спроектировать групповой чат, если интервьюер имеет в виду 
обмен сообщениями между двумя пользователями. Очень важно понять, 
какие возможности нужно предусмотреть.
ШАГ 1: ПОНЯТЬ ЗАДАЧУ И ОПРЕДЕЛИТЬ МАСШТАБ РЕШЕНИЯ
Вы обязательно должны определиться с тем, какого рода приложение 
нужно проектировать. На рынке есть приложения для прямой переписки, 
такие как Facebook Messenger, WeChat и WhatsApp, офисные системы для 
группового обмена сообщениями вроде Slack и игровые сервисы наподо­


198      ГЛАВА 12 
бие Discord, которые делают акцент на взаимодействии больших групп 
пользователей и голосовом общении с низкой латентностью.
Первая череда уточняющих вопросов должна прояснить, что именно име­
ет в виду интервьюер под системой мгновенного обмена сообщениями. 
Вам как минимум необходимо понять, на взаимодействии какого типа 
следует сосредоточиться: приватном (между двумя пользователями) или 
групповом. Ниже даны примерные вопросы.
Кандидат: «Какого рода чат мы будем проектировать: приватный 
(один на один) или групповой?»
Интервьюер: «Чат должен поддерживать оба вида взаимодействия».
Кандидат: «Это приложение должно быть мобильным, браузерным 
или и тем и другим?»
Интервьюер: «И тем и другим».
Кандидат: «Каков масштаб этого приложения? Стартап или огромная 
система?»
Интервьюер: «Оно должно поддерживать 50 миллионов ежедневных 
активных пользователей (DAU)».
Кандидат: «Что касается группового чата, насколько большой может 
быть группа?»
Интервьюер: «Не больше 100 человек».
Кандидат: «Какими важными возможностями обладает система? 
Поддерживает ли она вложения?»
Интервьюер: «Приватный и групповой чат, индикатор присутствия 
в сети. Система поддерживает только текстовые сообщения».
Кандидат: «Ограничен ли размер сообщений?»
Интервьюер: «Да, длина текста не должна превышать 100 000 сим­
волов».
Кандидат: «Требуется ли сквозное шифрование?»
Интервьюер: «Пока что нет, но мы это обсудим, если время позволит».
Кандидат: «Как долго должна храниться история переписки?»
Интервьюер: «Вечно».


Проектирование системы мгновенного обмена сообщениями 
    199
В этой главе мы сосредоточимся на проектировании приложения напо­
добие Facebook Messenger с акцентом на следующие возможности:
 
 приватный чат с низкой латентностью доставки сообщений;
 
 небольшой групповой чат (не больше 100 человек);
 
 индикатор сетевого статуса;
 
 поддержка нескольких устройств; один и тот же пользователь 
может быть аутентифицирован сразу на нескольких устройствах;
 
 push-уведомления.
Также необходимо определиться с масштабом системы. Наша архитектура 
должна поддерживать 50 миллионов DAU.
ШАГ 2: ПРЕДЛОЖИТЬ ОБЩЕЕ РЕШЕНИЕ 
И ПОЛУЧИТЬ СОГЛАСИЕ
Чтобы разработать высококачественную архитектуру, необходимо иметь 
общее представление о взаимодействии между клиентами и серверами. 
Клиентские приложения могут быть либо мобильными, либо браузер­
ными. Они не взаимодействуют друг с другом напрямую. Вместо этого 
каждый клиент подключается к сервису чата, который поддерживает все 
вышеупомянутые возможности. Давайте обсудим основные операции. 
Сервис чата должен поддерживать следующие функции:
 
 прием сообщений от других клиентов;
 
 поиск подходящего получателя для каждого сообщения и передача 
сообщений получателям;
 
 временное хранение сообщений на сервере, если их получатель не 
в сети.
На рис. 12.2 показаны отношения между клиентами (отправителем и по­
лучателем) и сервисом чата.
Когда клиент хочет начать общение, он соединяется с сервисом чата, 
используя один или несколько сетевых протоколов. Для сервиса 
чата ­выбор протокола имеет значение. Давайте обсудим это с интер­
вьюером.


200      ГЛАВА 12 
Отправитель
сообщение
Получатель
сообщение
Сервис чата:
1. Хранение сообщения
2. Передача сообщения
Рис. 12.2
В большинстве клиент-серверных приложений запросы инициируются 
клиентом. То же самое относится и к отправляющей стороне нашего при­
ложения. На рис. 12.2 видно, что для передачи сообщения отправитель 
использует проверенный временем протокол HTTP, который чаще всего 
встречается в интернете. В этом случае клиент открывает HTTP-соединение 
с сервисом чата и отправляет сообщение, которое сервис должен передать 
получателю. Для этого хорошо подходит заголовок Keep-Alive, который по­
зволяет клиенту поддерживать постоянное соединение с сервисом чата. Он 
также уменьшает количество TCP-согласований. Протокол HTTP хорошо 
работает на стороне сервера, и многие системы обмена сообщениями, такие 
как Facebook [1], изначально использовали именно его.
Однако на стороне получателя все немного сложнее. Поскольку в HTTP 
соединение инициирует клиент, отправить сообщение с сервера не так-то 
просто. С годами было выработано множество способов имитации соеди­
нения, инициированного сервером: HTTP-опрос, длинный HTTP-опрос 
и WebSocket. Это важные методики, которые широко применяются в интер­
вью по проектированию ИТ-систем. Давайте рассмотрим каждую из них.
HTTP-опрос
Как видно на рис. 12.3, HTTP-опрос (polling) состоит в том, что клиент 
периодически спрашивает сервер о наличии новых сообщений. В зави­
симости от частоты опроса этот подход может быть затратным. Драго­
ценные ресурсы сервера могут уходить на возвращение ответа, который 
в большинстве случаев не несет в себе никакой полезной информации.
Длинный HTTP-опрос
Поскольку HTTP-опрос может быть неэффективным, его логическим 
развитием является длинный HTTP-опрос (long polling) (рис. 12.4).


Проектирование системы мгновенного обмена сообщениями 
    201
Клиент
Сервер
соединение закрывается
.
.
.
Есть новые 
сообщения?
НЕТ
соединение закрывается
Есть новые 
сообщения?
НЕТ
соединение закрывается
Есть новые 
сообщения?
ДА. Возврат 
новых сообщений
соединение закрывается
Есть новые 
сообщения?
НЕТ
Рис. 12.3
При длинном HTTP-опросе клиент оставляет соединение открытым, 
пока не появятся новые сообщения или пока не истечет время ожидания. 
Получив новые сообщения, клиент немедленно отправляет серверу еще 
один запрос, повторяя весь процесс заново. У длинного HTTP-опроса 
есть несколько недостатков.
 
 Отправитель и получатель могут быть подключены к разным сер­
верам чата. HTTP-серверы обычно не хранят свое состояние. Если 
вы балансируете нагрузку путем циклического перебора, у сервера, 
принявшего сообщение, может не быть соединения с клиентом, 
которому это сообщение направлено.
 
 У сервера нет хорошего механизма для определения того, отклю­
чился ли клиент.


202      ГЛАВА 12 
 
 Это неэффективный подход. Если пользователь не слишком акти­
вен, время ожидания будет периодически истекать и соединение 
будет устанавливаться заново.
Клиент
Сервер
Есть новые 
сообщения?
соединение закрывается
Да. Возврат новых 
сообщений
Есть новые 
сообщения?
Ожидание новых сообщений
соединение закрывается
Истечение 
времени ожидания
Есть новые 
сообщения?
.
.
.
Рис. 12.4
WebSocket
WebSocket — это наиболее распространенное решение для передачи 
асинхронных обновлений от сервера к клиенту. Принцип его работы по­
казан на рис. 12.5.
Соединение по WebSocket инициируется клиентом. Оно является двуна­
правленным и постоянным. Все начинается с HTTP-соединения, которое 
можно «модернизировать» до WebSocket с помощью определенной про­
цедуры согласования. По этому постоянному соединению сервер может 
отправлять обновления клиенту. Соединения по WebSocket обычно 
работают даже при наличии брандмауэра. Все благодаря тому, что они ис­
пользуют порты 80 или 443, принадлежащие протоколам HTTP/HTTPS.


Проектирование системы мгновенного обмена сообщениями 
    203
Клиент
Сервер
HTTP-согласование
Двунаправленные сообщения
Подтверждение
GET/ws
Рис. 12.5
Ранее мы упоминали, что HTTP хорошо подходит для использования 
на стороне отправителя, однако протокол WebSocket является двуна­
правленным, поэтому с технической точки зрения нам ничего не мешает 
применять его и для получения сообщений. На рис. 12.6 показано, как 
WebSocket (ws) работает на стороне отправителя и получателя.
Сервис чата
Отправитель
Получатель
ws
ws
Рис. 12.6
Использование WebSocket как для отправки, так и для получения сообще­
ний упрощает архитектуру и делает реализацию клиента и сервера более 


204      ГЛАВА 12 
понятной. Поскольку соединение по WebSocket является постоянным, 
нам нужно позаботиться об его эффективном управлении на серверной 
стороне.
Общая архитектура
Мы только что упомянули, что выбор WebSocket в качестве основного 
протокола взаимодействия между клиентом и сервером обусловлен его 
двунаправленностью. Но необходимо также отметить, что его необяза­
тельно использовать для всего остального. На самом деле большинство 
возможностей приложения (регистрация, вход в систему, профили 
пользователей и т. д.) могут быть реализованы в традиционном сти­
ле «запрос–ответ» по протоколу HTTP. Давайте немного углубимся 
в этот вопрос и поговорим о том, из каких компонентов состоит наша 
система.
Как показано на рис. 12.7, чат разделен на три основных части: сервисы без 
состояния, сервисы с состоянием и интеграция со сторонними сервисами.
Сервисы без сохранения состояния
Сервисы без сохранения состояния традиционно используются для вза­
имодействия с клиентами по принципу «запрос–ответ». На их основе 
реализованы такие функции, как вход в систему, регистрация, профили 
пользователей и т. д. Эти возможности присутствуют во многих веб-
сайтах и приложениях.
Сервисы без сохранения состояния находятся за балансировщиком на­
грузки. Последний отвечает за маршрутизацию запросов и выбор подхо­
дящего сервиса с учетом указанного пути. Эта часть системы может быть 
как монолитной, так и разделенной на отдельные микросервисы. Многие 
из этих сервисов не нужно создавать с нуля, так как на рынке уже есть 
решения, которые можно легко интегрировать. Мы подробно остановимся 
на механизме обнаружения сервисов. Основная задача этого компонента 
состоит в возвращении клиенту списка доменных имен, принадлежащих 
серверам чата, к которым можно подключиться.
Сервисы с сохранением состояния
Единственный сервис, который хранит свое состояние, — это чат. Это 
обу­словлено тем, что каждый клиент поддерживает постоянное сетевое 


Проектирование системы мгновенного обмена сообщениями 
    205
соединение с сервером чата. Пока сервер остается доступным, клиент 
обычно не переходит на другой сервер. Чтобы серверы не перегружались, 
механизм обнаружения сервисов координирует свою работу с чатом. 
В следующем разделе мы обсудим это подробнее.
Пользователь
Балансировщик нагрузки
http
Сервис 
аутентификации
Обнаружение 
сервисов
Профили 
пользователей
Управление 
группами
Без сохранения 
состояния
Push-
уведомления
Сторонние 
сервисы
Сервис чата
ws
ws
С сохранением состояния
Пользователь 1
Пользователь 2
Рис. 12.7


206      ГЛАВА 12 
Интеграция со сторонними сервисами
Для такого приложения, как чат, самым важным сторонним сервисом яв­
ляются push-уведомления. Они позволяют информировать пользователей 
о новых сообщениях, даже когда приложение не работает. Интеграция 
push-уведомлений крайне важна. Подробнее об этом можно почитать 
в главе 10 «Проектирование системы уведомлений».
Масштабируемость
Если масштаб небольшой, все сервисы, перечисленные выше, можно 
уместить на одном сервере. Но даже при таком масштабе все пользова­
тельские соединения теоретически могут обрабатываться одним совре­
менным облачным сервером. Ограничивающим фактором, скорее всего, 
будет количество параллельных соединений. В нашем случае речь идет 
об 1 миллионе активных пользователей; если предположить, что каждое 
пользовательское соединение занимает 10 Кб (это очень грубая оценка, 
которая сильно зависит от выбранного языка программирования), то 
для того, чтобы все они уместились на одном сервере, потребуется 10 Гб 
оперативной памяти.
Если мы предложим архитектуру, в которой все находится на одном 
сервере, это может очень сильно насторожить интервьюера. Ни один 
разработчик не стал бы использовать единственный сервер для системы 
такого масштаба, и для этого есть много причин. Самая главная из них — 
единая точка отказа.
И все же односерверная архитектура вполне может послужить отправной 
точкой. Просто дайте интервьюеру понять, что это лишь начало. Если 
собрать воедино все, о чем упоминалось выше, получится улучшенная 
общая архитектура, показанная на рис. 12.8.
На рис. 12.8 клиент поддерживает постоянное соединение с сервером чата 
по WebSocket, чтобы обеспечить обмен сообщениями в реальном времени.
 
 Серверы чата отвечают отправкой/получением сообщений.
 
 Серверы присутствия следят за тем, находятся ли пользователи 
в сети.
 
 Серверы API занимаются всем остальным, включая вход в систему, 
регистрацию, редактирование профиля и т. д.
 
 Серверы уведомлений отправляют push-уведомления.


Проектирование системы мгновенного обмена сообщениями 
    207
 
 И наконец, хранилище типа «ключ–значение» используется для 
хранения истории переписки. Когда пользователь появляется 
в сети, он видит все предыдущие сообщения.
Балансировщик 
нагрузки
http
ws
Серверы API
Хранилище 
«ключ–значение»
Серверы 
уведомлений
Серверы 
чата
Серверы 
присутствия 
в сети
Сервис реального времени
Пользователь
Хранилище 
«ключ–значение»
Хранилище 
«ключ–значение»
Рис. 12.8
Хранилище
Итак, мы подготовили наши серверы, запустили сервисы и завершили 
интеграцию со сторонними системами. Глубоко внутри технологического 
стека находится уровень данных, для корректного проектирования кото­
рого обычно нужно приложить некоторые усилия. Очень важно опреде­


208      ГЛАВА 12 
литься с тем, какая база данных нам лучше подходит: реляционная или 
NoSQL. Чтобы сделать обоснованный выбор, необходимо исследовать 
типы данных и модель чтения/записи.
В типичной системе мгновенного обмена сообщениями существует два 
вида данных. Первый вид, обобщенный, включает в себя профили поль­
зователей, настройки и списки друзей. Такие данные хранятся в устой­
чивой и надежной реляционной БД. Для соответствия требованиям 
доступности и масштабируемости обычно применяются репликация 
и сегментирование.
Второй вид данных встречается только в системах мгновенного обмена 
сообщениями. Здесь важно понять модель чтения/записи.
 
 Такие системы обрабатывают огромные объемы данных. Согласно 
исследованию [2], через Facebook Messenger и WhatsАpp проходит 
больше 60 миллиардов сообщений в день.
 
 Активный доступ осуществляется только к недавним чатам. Поль­
зователи обычно не возвращаются к старым перепискам.
 
 В большинстве случаев просматривается только самая свежая исто­
рия сообщений, но пользователи могут обращаться к функциям, 
требующим произвольного доступа, таким как поиск, просмотр 
сообщений, в которых упоминается ваше имя, переход к опреде­
ленным сообщениям и т. д. Все эти возможности должны поддер­
живаться на уровне доступа к данным.
 
 В приватных чатах чтение и запись происходят примерно с оди­
наковой частотой.
Выбор подходящей системы хранения, которая поддерживает все эти 
сценарии использования, имеет большое значение. Мы советуем ис­
пользовать хранилища типа «ключ–значение» по следующим причинам:
 
 хранилища типа «ключ–значение» легко поддаются горизонталь­
ному масштабированию;
 
 хранилища типа «ключ–значение» имеют низкую латентность об­
ращения к данным;
 
 реляционные БД плохо справляются с длинными последователь­
ностями данных [3]. С увеличением индекса замедляется произ­
вольный доступ.


Проектирование системы мгновенного обмена сообщениями 
    209
 
 хранилища типа «ключ–значение» применяются в других надеж­
ных системах мгновенного обмена сообщениями, проверенных 
временем. Например, они используются в Facebook Messenger 
(HBase [4]) и Discord (Cassandra [5]). 
Модели данных
Мы только что обсудили использование хранилищ типа «ключ–значе­
ние» в качестве уровня данных. Самыми важными данными являются 
сообщения. Давайте рассмотрим их подробнее.
Таблица сообщений для приватного чата
На рис. 12.9 показана таблица сообщений для приватного чата. Первич­
ный ключ, message_id, помогает определить порядок следования сообще­
ний. Мы не можем полагаться в этом на поле created_at, поскольку два 
разных сообщения могут быть созданы одновременно.
Рис. 12.9
Таблица сообщений для группового чата
На рис. 12.10 показана таблица сообщений для группового чата. Состав­
ной первичный ключ имеет вид (channel_id, message_id). В этом кон­
тексте канал и группа являются синонимами. Все запросы в групповом 
чате выполняются в рамках канала, поэтому ключом раздела является 
channel_id.


210      ГЛАВА 12 
Рис. 12.10
ID-сообщения
Стоит поговорить о том, как генерируется message_id. Это поле обеспе­
чивает правильный порядок вывода сообщений. Для этого оно должно 
удовлетворять следующим двум требованиям:
 
 идентификаторы должны быть уникальными;
 
 идентификаторы должны поддерживать сортировку по времени, 
то есть у новых строк идентификаторы должны быть больше, чем 
у старых.
Как реализовать эти два свойства? Первое, что приходит на ум, это клю­
чевое слово auto_increment из MySQL. Однако в базах данных NoSQL 
такой возможности обычно нет.
Еще один подход состоит в использовании глобального генератора по­
следовательных 64-битных чисел вроде Snowflake [6]. Это обсуждалось 
в главе 7 «Проектирование генератора уникальных идентификаторов 
в распределенных системах».
Последним вариантом будет использование локального генератора 
последовательных чисел. Локальным его делает то, что идентифика­
торы уникальны только в пределах группы. Этот подход работает, по­
тому что сообщения достаточно упорядочивать на уровне приватного 
канала или группы. Локальные ID легче реализовать по сравнению 
с глобальными.


Проектирование системы мгновенного обмена сообщениями 
    211
ШАГ 3: ПОДРОБНОЕ ПРОЕКТИРОВАНИЕ
В ходе интервью по проектированию ИТ-систем от кандидата обычно 
ожидают подробного анализа некоторых компонентов общей архи­
тектуры. В случае с системой мгновенного обмена сообщениями от­
дельного внимания заслуживают механизм обнаружения сервисов, 
маршруты прохождения сообщений и индикатор сетевого статуса 
собеседника.
Обнаружение сервисов
Основная задача механизма обнаружения сервисов — предложить кли­
енту лучший сервер чата с учетом таких критериев, как географическое 
местоположение, емкость сервера и т. д. Популярное решение — система 
с открытым исходным кодом Apache Zookeeper [7]. Она регистрирует 
все доступные серверы чата и выбирает из них тот, который лучше всего 
соответствует заранее заданным критериям.
Принцип работы обнаружения сервисов (на примере Zookeeper) показан 
на рис. 12.11.
1.	 Пользователь A пытается войти в приложение.
2.	 Балансировщик нагрузки передает запрос входа в систему серверам 
API.
3.	 Когда внутренняя часть системы аутентифицирует пользователя, 
механизм обнаружения сервисов подберет для него наиболее подхо­
дящий сервер чата. В этом примере выбран сервер 2, и информация 
о нем возвращается обратно пользователю.
4.	 Пользователь A подключается к серверу чата 2 по WebSocket.
Маршруты прохождения сообщений
Давайте посмотрим, как проходят данные по системе мгновенного обме­
на сообщениями. В этом разделе мы исследуем маршрут прохождения 
сообщений в приватном и групповом чатах, а также синхронизацию со­
общений между разными устройствами.


212      ГЛАВА 12 
Пользователь A
Балансировщик
нагрузки
1. Вход в систему
Серверы API
4. ws
Обнаружение сервисов (Zookeeper)
Сервер 
чата 1
. . .
2
3
Сервер 
чата 2
Сервер 
чата N
Сервер 
чата 2
Рис. 12.11
Маршрут прохождения сообщений в приватном чате
На рис. 12.12 можно видеть, что происходит, когда пользователь A от­
правляет сообщение пользователю Б.
1.	 Пользователь A отправляет мгновенное сообщение на сервер 
чата 1.
2.	 Сервер чата 1 получает ID сообщения из генератора идентифика­
торов.
3.	 Сервер чата 1 передает сообщение в очередь синхронизации со­
общений.
4.	 Сообщение записывается в хранилище типа «ключ–значение».


Проектирование системы мгновенного обмена сообщениями 
    213
Сервер чата 1
Генератор ID
в сети
не в сети
Серверы push-
уведомлений
Очередь синхронизации 
сообщений
6
1
3
2
4
5a
5b
Пользователь A
Пользователь Б
Сервер чата 2
Хранилище 
«ключ–значение»
Рис. 12.12
5.	 а.  Если пользователь Б в сети, сообщение направляется на сервер 2, 
к которому он подключен.
б.  Если пользователь Б не в сети, отправляется push-уведомление.
6.	 Сервер чата 2 передает сообщение пользователю Б. Между поль­
зователем Б и сервером чата 2 установлено постоянное соединение 
по WebSocket.
Синхронизация сообщений между несколькими устройствами
У многих пользователей есть сразу несколько устройств. Ниже объ­
ясняется, как в таких условиях происходит синхронизация сообщений 
(рис. 12.13).


214      ГЛАВА 12 
Сеанс для ноутбука пользователя A
Сеанс для телефона пользователя A
cur_max_message_id = 653
cur_max_message_id = 842
Серверы 
чата 1
Хранилище «ключ–значение»
Телефон 
пользователя A
Ноутбук 
пользователя A
Рис. 12.13
На рис. 12.13 у пользователя A есть два устройства: телефон и ноутбук. 
Когда он входит в чат со своего телефона, приложение устанавливает по 
WebSocket соединение с сервером чата 1. Аналогичным образом с серве­
ром чата 1 соединяется и ноутбук.
Каждое устройство использует переменную под названием cur_max_
message_id для отслеживания ID последнего сообщения. Сообще­
ния считаются новыми, если они соответствуют следующим двум 
условиям:
 
 ID получателя совпадает с ID текущего аутентифицированного 
пользователя.
 
 ID сообщения в хранилище типа «ключ–значение» больше, чем 
cur_max_message_id.
Каждое устройство имеет свое значение cur_max_message_id и мо­
жет ­получить новые значения из хранилища, что упрощает синхрони­
зацию.


Проектирование системы мгновенного обмена сообщениями 
    215
Маршрут прохождения сообщений в небольшом групповом чате
Логика группового чата сложнее по сравнению с приватным. Маршрут 
прохождения сообщений проиллюстрирован на рис. 12.14 и 12.15.
Очередь синхронизации 
сообщений
Очередь синхронизации 
сообщений
Пользователь A
Пользователь Б
Пользователь В
Сервер чата 1
Рис. 12.14
На рис. 12.14 показано, что происходит, когда пользователь A отправ­
ляет сообщение в групповой чат. Предположим, группа состоит из трех 
участников (пользователей A, Б и В). Сначала сообщение пользователя A 
копируется в очередь синхронизации сообщений каждого участника 
группы: одно для пользователя Б, другое для пользователя В. Очередь 
синхронизации сообщений можно считать почтовым ящиком получателя. 
Такая архитектура хорошо подходит для небольших групп, потому что:
 
 она упрощает процесс синхронизации, так как для получения 
новых сообщений каждый клиент должен проверять собственный 
почтовый ящик;


216      ГЛАВА 12 
 
 в небольших группах хранение копии сообщения в почтовом ящике 
каждого получателя забирает мало ресурсов.
В системе WeChat используется похожий подход: количество участников 
группы в ней не превышает 500 [8]. Но если группа насчитывает много 
пользователей, хранение копий сообщений для каждого из них будет 
неприемлемым.
Получателю могут поступать сообщения от множества пользователей. 
У каждого получателя есть почтовый ящик (очередь синхронизации 
сообщений) с сообщениями от разных отправителей. Эта архитектура 
проиллюстрирована на рис. 12.15.
Пользователь A
Очередь синхронизации 
сообщений
Сервер чата 1
Сервер чата 2
Пользователь В
Пользователь Б
Рис. 12.15
Сетевой статус
Индикатор сетевого статуса — неотъемлемая часть многих приложений 
для мгновенного обмена сообщениями. Обычно он имеет вид зеленой 


Проектирование системы мгновенного обмена сообщениями 
    217
точки рядом с аватаром или именем пользователя. В этом разделе вы 
узнаете, как этот компонент устроен изнутри.
В нашей общей архитектуре за управление сетевым состоянием и взаимо­
действие с клиентами по WebSocket отвечают серверы сетевого статуса. 
Его изменение может быть инициировано несколькими путями. Давайте 
рассмотрим каждый из них.
Вход пользователя в систему
Процесс входа пользователя в систему был описан в разделе «Обнару­
жение сервисов». После установления соединения на основе WebSocket 
между клиентом и сервисом реального времени сетевой статус пользо­
вателя A и временная метка last_active_at записываются в хранилище 
типа «ключ–значение». После входа в систему индикатор показывает, 
что пользователь находится в сети.
Серверы 
сетевого статуса
User A: {status: online, 
last_active_at: timestamp
Хранилище 
«ключ–значение»
соединение ws
Пользователь A
Рис. 12.16
Выход из системы
При выходе пользователя из системы происходит процесс, показанный 
на рис. 12.17. В хранилище типа «ключ–значение» сетевой статус меня­
ется на offline. Индикатор присутствия показывает, что пользователя 
нет в сети.
Серверы 
сетевого статуса
User A: {status:offline}
Серверы API
выход
Хранилище 
«ключ–значение»
Пользователь A
Рис. 12.17


218      ГЛАВА 12 
Отключение пользователя от сети
Всем нам хочется, чтобы интернет-соединение всегда было стабильным 
и надежным, но в реальности такого не бывает. Поэтому проблему сле­
дует отразить в нашей архитектуре. Когда пользователь отключается от 
интернета, постоянное соединение между клиентом и сервером теряет­
ся. Мы могли бы указать, что пользователь не в сети, и затем поменять 
его состояние на противоположное, когда соединение восстановится, 
но это решение слишком примитивное и имеет серьезный недостаток. 
Пользователи могут отключаться и подключаться заново по многу раз 
за короткое время — это распространенное явление. Например, сетевое 
соединение может временно разорваться, когда пользователь проезжает 
по тоннелю. Если обновлять сетевое состояние при каждом разрыве 
и переподключении, индикатор сетевого статуса будет мигать слишком 
часто, что отрицательно скажется на взаимодействии с пользователями.
Для решения этой проблемы мы воспользуемся механизмом пульсации. 
Время от времени клиент шлет серверам сетевого статуса события. Если 
в течение какого-то времени (скажем, x секунд) серверы получили собы­
тие пульсации, они считают, что пользователь находится в сети. В про­
тивном случае пользователь недоступен.
На рис. 12.18 клиент отправляет серверу событие пульсации раз в 5 се­
кунд. После отправки третьего события клиент отключается на x = 30 
секунд (это число выбрано произвольно, чтобы продемонстрировать 
логику). Сетевой статус меняется на «не в сети».
Распространение информации о сетевом статусе
Как друзья пользователя A узнают об изменении его сетевого статуса? 
На рис. 12.19 показано, как это работает. Серверы сетевого статуса ис­
пользуют модель «издатель–подписчик», в которой между каждой парой 
друзей существует канал. Когда сетевое состояние пользователя A меня­
ется, он публикует соответствующее событие в три канала: A-Б, A-В и A-Г. 
На эти три канала подписаны пользователи Б, В и, соответственно, Г. 
Таким образом эти друзья могут легко получать обновления о статусе. 
Взаимодействие между клиентами и серверами происходит в реальном 
времени по WebSocket.
Приведенная архитектура подходит для небольших групп пользователей. 
Например, в WeChat используется похожий подход, так как группы в этой 
системе могут включать не более 500 участников. Но если мы имеем дело


Проектирование системы мгновенного обмена сообщениями 
    219
Клиент
Сервер
Пульс
Пульс получен. 
Пользователь в сети
Пульс
Пульс получен. 
Пользователь в сети
Пульс
Пульс получен. 
Пользователь в сети
Пульса нет на протяжении 30 секунд. 
Статус меняется на «не в сети»
5s
5s
x = 30s
Рис. 12.18
подписка
подписка
подписка
Канал A-Б
Канал A-В
Канал A-Г
Серверы сетевого
 статуса
Пользователь A
Пользователь Б
Пользователь В
Пользователь Г
Рис. 12.19
с крупными группами, на информирование каждого участника о сетевом 
статусе будет уходить много ресурсов и времени. Представьте себе группу 
из 100 000 участников. Каждое изменение статуса будет генерировать 
100 000 событий. Чтобы избавиться от этой проблемы, можно получать 


220      ГЛАВА 12 
сетевой статус пользователя, только когда он заходит в группу или вруч­
ную обновляет список друзей.
ШАГ 4: ПОДВЕДЕНИЕ ИТОГОВ
В этой главе мы представили архитектуру системы мгновенного обмена 
сообщениями, которая поддерживает как приватные, так и групповые 
чаты. Для взаимодействия в реальном времени между клиентом и серве­
ром используется WebSocket. Система состоит из следующих компонен­
тов: серверы чата для обмена сообщениями в реальном времени, серверы 
сетевого статуса для управления сетевым статусом пользователя, серверы 
для отправки push-уведомлений, хранилища типа «ключ–значение» для 
хранения истории переписки и серверы API для других функций.
Если в конце интервью еще остается время, можете затронуть дополни­
тельные аспекты.
 
 Приложение можно расширить для поддержки медиафайлов, таких 
как фотографии и видео. Медиафайлы имеют намного больший 
размер по сравнению с текстом. Можно обсудить такие вещи, как 
сжатие, облачное хранение и миниатюрные изображения.
 
 Сквозное шифрование. WhatsApp поддерживает сквозное шифро­
вание сообщений. Заинтересованные читатели могут ознакомиться 
со статьей в справочных материалах [9].
 
 Кэширование сообщений на стороне клиента — эффективный 
способ сокращения объема данных, передаваемых между клиентом 
и сервером.
 
 Сокращение времени загрузки. Компания Slack создала географи­
чески распределенную сеть для кэширования пользовательских 
данных, каналов и т. д. Это сокращает время загрузки [10].
 
 Обработка ошибок:

 ошибки на сервере чата. У сервера чата могут быть сотни тысяч 
(или даже больше) постоянных сетевых соединений. Если он 
выйдет из строя, механизм обнаружения сервисов (Zookeeper) 
предоставит клиентам новый сервер чата, к которому они смогут 
подключаться;


Проектирование системы мгновенного обмена сообщениями 
    221

 механизм повторной отправки сообщений. В случае ошибки со­
общения обычно записываются в очередь и затем отправляются 
повторно.
Поздравляем, вы проделали длинный путь и можете собой гордиться. 
Отличная работа!
СПРАВОЧНЫЕ МАТЕРИАЛЫ
[1]  Erlang at Facebook: https://www.erlang-factory.com/upload/presentations/31/
EugeneLetuchy-ErlangatFacebook.pdf
[2]  Messenger and WhatsApp process 60 billion messages a day: https://www.
theverge.com/2016/4/12/11415198/facebook-messenger-whatsapp-number-messages-
vs-sms-f8-2016
[3]  Длинный хвост: https://ru.wikipedia.org/wiki/Длинный_хвост
[4]  The Underlying Technology of Messages: https://www.facebook.com/notes/
facebook-engineering/the-underlying-technology-of-messages/454991608919/
[5]  How Discord Stores Billions of Messages: https://blog.discordapp.com/how-
discord-stores-billions-of-messages-7fa6ec7ee4c7
[6]  Announcing Snowflake: https://blog.twitter.com/engineering/en_us/a/2010/
announcing-snowflake.html
[7]  Apache ZooKeeper: https://zookeeper.apache.org/
[8]  Из ничего: эволюция фоновой системы WeChat (статья на китайском): 
https://www.infoq.cn/article/the-road-of-the-growth-weixin-background
[9]  End-to-end encryption: https://faq.whatsapp.com/en/android/28030015/
[10]  Flannel: An Application-Level Edge Cache to Make Slack Scale: https://slack.
engineering/flannel-an-application-level-edge-cache-tomake-slack-scale-b8a6400e2f6b
