При работе над одним из проектов перед нами встала задача реализации переписки между зарегистрированными пользователями. По своей сути – это должен быть чат, но одновременно общаться в нём можно только с одним собеседником.
Потенциальная нагрузка, которую должен выдерживать такой чат — около 10000 одновременных keep-alive соединений. Каждое новое сообщение должно записываться в основную базу данных, а так же в «быструю», задача которой хранить в себе лишь актуальную часть переписок между пользователями, то есть служить своеобразным «временным» хранилищем, из которого сообщения будут сразу доставляться адресату.
Для решения этой задачи хорошо подходит MongoDB. Поскольку в ней хорошо отлажен процесс репликации, мы можем отдельным демоном периодически опрашивать таблицу local.oplog.$main на предмет наличия в ней новых сообщений, и, если таковые имеются, сразу доставлять их по адресу в формате JSON. Для устойчивой работы под нагрузкой, таких демонов будет несколько, к каждому из которых будут подключены несколько тысяч переписчиков.
И здесь перед нами встал выбор, что использовать в качестве «быстрого сервера», на котором будет размещены демоны? Для этого мы протестировали два решения: на основе phpDaemon и на основе Node.JS.
Итак, для начала, полная архитектура чата:
Рис. 1. Полная структура чата
В нашем тестовом варианте не будет постоянной базы данных, а так же авторизации пользователей. Будет только один «быстрый сервер», который будет забирать новые сообщения из коллекции и рассылать всем активным в данный момент клиентам (рис 2).
Рис. 2. Упрощенная структура чата
Составы играющих сегодня команд:
Команда Node.js
Команда phpDaemon
Судья встречи Apache Benchmark.
Итак, мы запускаем один из демонов, который ведет polling MongoDB, а так же «общается» с пришедшими клиентами. С помощью утилиты Apache Benchmark с другой машины подключаем несколько keep-alive клиентов (время тестирования 20 секунд). В это время сторонней формой начинаем записывать в MongoDB сообщения. Для каждого демона и каждого числа keep-alive соединений делаем по 5 итераций, потом усредняем полученные данные и видим результат.
Красным цветом отмечены «лучшие показатели»
Как мы видим, на небольшом числе одновременных соединений (10), чат на основе Node.JS работает лучше. Обрабатывается большее число запросов, соответсвенно сокращается время на обработку одного запроса. Правда, phpDaemon имеет меньшую длительность самого долгого запроса, что, правда, не слишком принципиально.
При увеличении числа соединений до 100, а затем и 500 картина изменилась. Вперед по показателям вышел уже phpDaemon.
А при 1000 keep-alive соединений показатели phpDaemona упали, а Node.JS наоборот возросли, что в итоге дало довольно сильный разрыв.
Потенциальная нагрузка, которую должен выдерживать такой чат — около 10000 одновременных keep-alive соединений. Каждое новое сообщение должно записываться в основную базу данных, а так же в «быструю», задача которой хранить в себе лишь актуальную часть переписок между пользователями, то есть служить своеобразным «временным» хранилищем, из которого сообщения будут сразу доставляться адресату.
Для решения этой задачи хорошо подходит MongoDB. Поскольку в ней хорошо отлажен процесс репликации, мы можем отдельным демоном периодически опрашивать таблицу local.oplog.$main на предмет наличия в ней новых сообщений, и, если таковые имеются, сразу доставлять их по адресу в формате JSON. Для устойчивой работы под нагрузкой, таких демонов будет несколько, к каждому из которых будут подключены несколько тысяч переписчиков.
И здесь перед нами встал выбор, что использовать в качестве «быстрого сервера», на котором будет размещены демоны? Для этого мы протестировали два решения: на основе phpDaemon и на основе Node.JS.
Итак, для начала, полная архитектура чата:
Рис. 1. Полная структура чата
- Каждый новый клиент, входящий в переписку устанавливает соединение с одним из «быстрых» серверов (phpDaemon или Node.js) и от него же будет получать сообщения.
- При отправке на основной сервер нового сообщения, оно записывается в постоянную базу данных (MySQL), а так же в некую «быструю» базу данных (в данном случае MongoDB), где будет храниться только актуальная переписка, например за последние сутки.
- Master MongoDB записывает изменения в коллекцию local.oplog.$main для репликации.
- «Быстрые сервера» в это время ведут опрос «быстрой» базы на предмет наличия в ней новых сообщений, и, если такие есть, отправляют их тому клиенту, которому они адресованы.
В нашем тестовом варианте не будет постоянной базы данных, а так же авторизации пользователей. Будет только один «быстрый сервер», который будет забирать новые сообщения из коллекции и рассылать всем активным в данный момент клиентам (рис 2).
Рис. 2. Упрощенная структура чата
Составы играющих сегодня команд:
Команда Node.js
- Node.js (http://nodejs.org)
- Библиотека Socket.IO для установления соединений между клиентом и Node.js (http://socket.io)
- Библиотека Node-mongo-native от christkv для работы с MongoDB из Node.js (https://github.com/christkv/node-mongodb-native)
- Ну и файл oplog.js из той же библиотеки для опроса MongoDB на предмет наличия новых сообщения (https://github.com/christkv/node-mongodb-native/blob/master/examples/oplog.js)
Команда phpDaemon
- phpDaemon (http://phpdaemon.net)
- Скрипты из состава phpDaemon для установки соединения между клиентом и сервером (https://github.com/kakserpom/phpdaemon/tree/master/clientside-connectors/websocket)
- Для работы с MongoDB так же будем использовать встроенные возможности phpDaemon
- Для опроса базы будет использоваться немного измененное нами приложение MongoNode (https://github.com/kakserpom/phpdaemon/blob/master/app-servers/MongoNode.php), которое не пишет изменения в Memcache, а отправляет их всем подключенным пользователям.
Судья встречи Apache Benchmark.
Итак, мы запускаем один из демонов, который ведет polling MongoDB, а так же «общается» с пришедшими клиентами. С помощью утилиты Apache Benchmark с другой машины подключаем несколько keep-alive клиентов (время тестирования 20 секунд). В это время сторонней формой начинаем записывать в MongoDB сообщения. Для каждого демона и каждого числа keep-alive соединений делаем по 5 итераций, потом усредняем полученные данные и видим результат.
Демон |
Complete requests |
Requests per second |
Time per request, ms (across all concurrent requests) |
Min request, ms |
Max Request, ms |
---|---|---|---|---|---|
10 keep-alive соединений |
|||||
phpDaemon |
1147 |
54.36 |
18.440 |
21 |
7116 |
Node.JS |
1285 |
64 |
15.618 |
16 |
9064 |
100 keep-alive соединений |
|||||
phpDaemon |
1543 |
75.15 |
13.313 |
22 |
12889 |
Node.JS |
1284 |
64.12 |
15.765 |
17 |
13347 |
500 keep-alive соединений |
|||||
phpDaemon |
1365 |
67.73 |
14.824 |
15 |
15174 |
Node.JS |
1236 |
61.71 |
16.611 |
23 |
16078 |
1000 keep-alive соединений |
|||||
phpDaemon |
1159 |
56.99 |
17.680 |
19 |
13103 |
Node.JS |
1528 |
75.02 |
13.354 |
17 |
16785 |
Красным цветом отмечены «лучшие показатели»
Как мы видим, на небольшом числе одновременных соединений (10), чат на основе Node.JS работает лучше. Обрабатывается большее число запросов, соответсвенно сокращается время на обработку одного запроса. Правда, phpDaemon имеет меньшую длительность самого долгого запроса, что, правда, не слишком принципиально.
При увеличении числа соединений до 100, а затем и 500 картина изменилась. Вперед по показателям вышел уже phpDaemon.
А при 1000 keep-alive соединений показатели phpDaemona упали, а Node.JS наоборот возросли, что в итоге дало довольно сильный разрыв.