предисловие
Для начала поговорим о том, для чего можно использовать технологию IM (Instant Messaging):
Чат: qq, WeChat
В прямом эфире: Douyu Live, Douyin
Передача данных о местоположении в режиме реального времени, многопользовательская игра и многое другое.
Можно сказать, что почти все сценарии приложений с высоким уровнем реального времени требуют использования технологии обмена мгновенными сообщениями.
В этой статье вы с нуля создадите легкий IM-сервер.Хотя воробей маленький, но у него есть все внутренние органы.Построенный нами IM-сервер достигает следующих результатов:Функции:
- Индивидуальные текстовые сообщения, обмен файлами
- Каждое сообщение имеет квитанцию "отправлено"/"доставлено"/"прочитано"
- Хранить офлайн-сообщения
- Поддержка основных функций, таких как вход в систему и дружеские отношения.
- Легко масштабируется по горизонтали
Чему можно научиться благодаря этому проекту?
Этот проект охватывает многоеБазовые необходимые знания:
- RPC-коммуникация
- база данных
- тайник
- очередь сообщений
- Распределенная архитектура с высокой степенью параллелизма
- развертывание докера
сообщение сообщения
текстовое сообщение
Начнем с самой простой функции: отправка обычного сообщения
Формат сообщения следующий:
message ChatMsg{
id = 1;
//消息id
fromId = Alice
//发送者userId
destId = Bob
//接收者userId
msgBody = hello
//消息体
}
Как показано выше, теперь у нас есть два пользователя: Алиса и Боб подключены к серверу, когда Алиса отправляет сообщениеmessage(hello)
Для Боба сервер получает сообщение, пересылает его в соответствии с идентификатором destId сообщения и пересылает его Бобу.
отправить квитанцию
Итак, как нам добиться отправки квитанции?
Определяем формат данных квитанции ACK, есть три MsgType соответственноsent
(Отправлено), delivered
(приехал),read
(Прочитал):
message AckMsg {
id;
//消息id
fromId;
//发送者id
destId;
//接收者id
msgType;
//消息类型
ackMsgId;
//确认的消息id
}
enum MsgType {
DELIVERED;
READ;
}
Когда сервер получает сообщение от Алисы:
- отправить Алисе
sent(hello)
Указывает, что сообщение отправлено на сервер.
message AckMsg {
id = 2;
fromId = Alice;
destId = Bob;
msgType = SENT;
ackMsgId = 1;
}
2. Сервер ставитhello
После отправки Бобу немедленно отправьте его Алисе.delivered(hello)
Указывает, что сообщение было отправлено Бобу.
message AckMsg {
id = 3;
fromId = Bob;
destId = Alice;
msgType = DELIVERED;
ackMsgId = 1;
}
3. После того, как Боб прочитает сообщение, клиент отправляет на серверread(hello)
Указывает, что сообщение прочитано
message AckMsg {
id = 4;
fromId = Bob;
destId = Alice;
msgType = READ;
ackMsgId = 1;
}
Это сообщение будет обработано сервером как обычное сообщение чата и, наконец, отправлено Алисе.
Здесь нет различия на сервереChatMsg
иAckMsg
, обработка та же: разбор сообщенияdestId
и переадресовано.
Горизонтальное расширение
Когда количество пользователей увеличивается, необходимо увеличивать количество серверов, а подключения пользователей раскидываются по разным машинам. На этом этапе необходимо сохранить, к какой машине подключен пользователь.
Мы представляем новый модуль для управления информацией о подключении пользователя.
Управление статусом пользователя
модуль называетсяuser status
, есть три интерфейса:
public interface UserStatusService {
/**
* 用户上线,存储userId与机器id的关系
*
* @param userId
* @param connectorId
* @return 如果当前用户在线,则返回他连接的机器id,否则返回null
*/
String online(String userId, String connectorId);
/**
* 用户下线
*
* @param userId
*/
void offline(String userId);
/**
* 通过用户id查找他当前连接的机器id
*
* @param userId
* @return
*/
String getConnectorId(String userId);
}
Таким образом, мы можем управлять статусом подключения пользователя, а конкретная реализация должна учитывать сервисКоличество пользователей, ожидаемая производительность и т. д.реализовать.
Здесь мы используем Redis для реализации и хранения отношений между userId и ConnectorId в форме ключ-значение.
пересылка сообщений
Кроме того, необходим модуль для пересылки сообщений на разные машины следующим образом:
На данный момент наш сервис разделен наconnector
иtransfer
два модуля,connector
модули используются для поддержки длинных ссылок для пользователей, в то время какtransfer
Роль состоит в том, чтобы поместить сообщение в несколькоconnector
пересылается между.
Теперь, когда Алиса и Боб подключены к двум коннекторам, как передать сообщение?
- Алиса выходит в сеть, подключается к
机器[1]
вовремя- Храните Алису и ее соединения в памяти.
- перечислить
user status
изonline
Метод записывает, что Алиса выходит в сеть.
- Алиса отправляет сообщение Бобу
-
机器[1]
Получив сообщение, проанализируйте destId и найдите Боба в памяти. - Если нет, значит, Боб не подключен к этой машине, тогда вперед к
transfer
.
-
-
transfer
перечислитьuser status
изgetConnectorId(Bob)
Метод находит соединитель, к которому подключен Боб, и возвращает机器[2]
, затем вперед机器[2]
.
блок-схема:
Суммировать:
- вводить
user status
модуль управляет подключениями пользователей,transfer
Модули пересылаются между разными машинами, что позволяет масштабировать сервисы по горизонтали. - Чтобы обеспечить переадресацию в реальном времени,
transfer
нужно и каждыйconnector
Машины держат длинные ссылки.
оффлайн сообщение
Если пользователь в данный момент не в сети, сообщение должно быть сохранено и отправлено до тех пор, пока пользователь не выйдет в сеть в следующий раз.Здесь mysql используется для хранения офлайн-сообщений.
для удобстваГоризонтальное расширение,Мы используемразделение очереди сообщений.
-
transfer
После получения сообщения, если обнаружится, что пользователь не в сети, оно будет отправлено в очередь сообщений на хранение. - Когда пользователь входит в систему, сервер извлекает автономные сообщения из библиотеки и отправляет их.
Логин пользователя, дружеские отношения
Такие функции, как регистрация пользователей и вход в систему, управление учетными записями и цепочка дружеских отношений, больше подходят для использования протокола http, поэтому мы делаем этот модуль спокойной службой и открываем интерфейс http для вызовов клиентов.
На этом базовая архитектура сервера завершена:
Суммировать
Выше приведено все содержание этого блога. Эта статья поможет вам построить архитектуру сервера обмена мгновенными сообщениями, но есть еще много деталей, о которых нам нужно подумать, например:
- Как обеспечить порядок и уникальность сообщений
- Как обеспечить согласованность сообщений, когда несколько устройств подключены к сети
- Как справиться с ошибкой отправки сообщения
- безопасность сообщений
- Что делать, если вы хотите сохранить историю чата
- Подтаблица базы данных
- Высокая доступность сервиса
...
Более подробная информация будет реализована в следующей статье~
ссылка на гитхаб:
github.com/yuanrw/IM
Если вы считаете, что это полезно для вас, пожалуйста, нажмите звездочку~!