
Доброго времени суток. Давно хотелось написать про что-то больше, нежели чем WP. Заметил, что чем больше пытаешься прогрессировать, как разработчик, тем тривиальней кажется то, о чем хочешь написать. Но да ладно, возможно кому-то будет довольно полезен мой опыт. Цикл заметок будет ориентирован в первую очередь на тех, кто только начинает собирать свои первые реалтайм веб-приложения.
Итак, стоит задача синхронизации того, что видит пользователь и того, что есть в бд. Ранее для подобных задач использовал сервис Pusher, но, в последнее время, предпочитаю использовать Centrifuge. Предвосхищу вопрос о том, чем лучше это обычной связки redis/socket.io/node.js. Из коробки приватные каналы, простая интеграция, масштабирование, api, история сообщения в канале, события отписки и подписки пользователей на канал и много другое, что позволяет крайне быстро построить нужный прототип приложения, без раздувания стека технологий. Это работает для меня, у каждого свой путь. Кстати, язык на бекенде — php, и, соответственно, на фронтенде — js.
Что из этого получилось, некоторые нюансы — можете посмотреть ниже.
Здесь буду складывать ссылки на следующие заметки.
1. Собираем простенький прототип
2. Вводим приватные каналы
3. Пишем простенькую crm в качестве примера
2. Вводим приватные каналы
3. Пишем простенькую crm в качестве примера
Собираем простенький прототип
В качестве первого примера возьмем следующую задачу. На экране будет график, который одинаков у всех пользователей и обновляется по мере поступления данных.

Для простоты брокер на данном этапе будет работать в insecure mode:
- Отключение проверки токена и временных меток;
- Разрешение анонимного доступа ко всем каналам;
- Клиентам разрешено публиковать сообщения во все каналы;
- Убрана проверка соединения.
Конечно, в боевом режиме этого делать не следует, но для наших целей и ради понимания концепции — подойдет.
Дебют
Для начала необходимо развернуть центрифугу на сервере. Делается это в несколько простейших шагов:
- Выбираем необходимый исполняемый файл под свою платформу в репозитории;
- В качестве транспорта предпочитаю использовать Redis. Но, если хочется, то можно и по http.
- Пишем небольшой конфиг для нашего проекта;
- Стартуем центрифугу на 8002 порту и загоняем все это дело под nginx.
Миттельшпиль
Центрифуга работает, готова к подсоединению клиентов и отправки им сообщений. Теперь дело за бекендом.
Генерируем composer.json, делаем composer require predis/predis, пишем пару классов. Собственно сам модуль отправки сообщений в брокер и транспорт, через редис. Т.к. центрифуга на данном этапе работает в insecure mode, токены генерировать нет нужды и задача еще более упрощается.

Если кому интересно — конечный вариант наброска и демо. В демо точки уничтожаются каждые 240 секунд, просто можно пощелкать кнопку на добавление новых в историю и обновить страницу. В реальном мире, конечно, данные берутся с api, в следующих заметках мы вернемся к этому.
Как итог, теперь мы можем отправлять с бекенда пользователям сообщения. Примерно следующим образом:
... require __DIR__.'/vendor/autoload.php'; use Push\Push; use RedisCommunucate\Communicate; use Predis\Client; $redisClient = new Communicate(new Client()); $push = new Push('development', 'secret', $redisClient); //Точку в канал "chart" $push->publish('chart', ['point' => rand(0,10)]); ...
... //Отправляем в центрифугу командой "publish" public function publish($channel, $data = []) { return $this->send("publish", ["channel" => $channel, "data" => $data]); } public function send($method, $params = []) { if (empty($params)) { $params = new \StdClass(); } $data = json_encode(["method" => $method, "params" => $params]); return $this->communicate ->communicate( $this->projectKey, ["data" => $data] ); } ...
... //Через redis rpush public function communicate($projectId, $data) { $toSend = array( "project" => $projectId ); $data['data'] = json_decode($data['data']); $toSend = array_merge($toSend, $data); $toSend['data'] = array($toSend['data']); $this->redisClient->rpush("centrifugo.api", json_encode($toSend)); } ...
И на этом первую часть задачи можно считать выполненной. Теперь дело за фронтендом.
Эндшпиль
В качестве библиотеки для графика возьмем c3.js. Подключаем c3, sockjs, centrifuge.js.
... <link href="https://cdnjs.cloudflare.com/ajax/libs/c3/0.4.10/c3.min.css" rel="stylesheet" type="text/css"> <script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.6/d3.min.js" charset="utf-8"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/c3/0.4.10/c3.min.js" type="text/javascript"></script> <script src="https://cdn.jsdelivr.net/sockjs/1.0/sockjs.min.js" type="text/javascript"></script> <script src="https://rawgit.com/centrifugal/centrifuge-js/master/centrifuge.js" type="text/javascript"></script> ...
Соединяемся с центрифугой, ловим точки. И сделаем кнопку, чтобы дергать сервер на генерацию новых точек.
... var chart; // Конфигурируем. var centrifuge = new Centrifuge({ url: 'http://socket.logistics.app/connection', project: 'development', insecure: true }); // Когда центрифуга подключилась, centrifuge.on('connect', function() { // просто подписываемся на канал, var subscription = centrifuge.subscribe('chart', function(message) { // ловим точки и добавляем в график, chart.flow({ columns: [ ['sample', message.data.point] ], duration: 100 }); }); // не забывая взять предыдущие. В реальном мире - берем с api. subscription.on('ready', function() { subscription.history(function(message) { var data = message.data.map(function(point){ return point.data.point }); data.reverse(); data.unshift('sample'); chart = c3.generate({ bindto: '#chart', data: { columns: [ data ] } }); }); }); }); // Запускаем коннект centrifuge.connect(); // Дергаем сервер, чтобы отправлял в центрифугу сообщения через редис $('.random').click(function(){ $.get('/random.php'); }); ...
Задача выполнена, открываем страницу с демо на разных устройствах или просто в разных браузерах. Когда появляется новая точка — график обновляется. Да, на данном этапе это все работает крайне небезопасно. Но для понимания концепции — в самый раз. Из известных проблем, решение которых я бы хотел описать в следующих заметках:
- Если между получением хистори и подпиской на канал придут новые точки, то они потеряются.
- Сортировка точек. Если они будут приходить крайне часто, то будут добавляться в неправильном порядке.
- Мы рассмотрели только добавление данных в конец. Но есть еще списочные данные. И, допустим, мы используем datatables для показа табличек. И данные обновляются, приходят с центрифуги, но мы находимся на третьей странице таблицы с фильтрами/сортировками. И вопрос в том, как синхронизировать это.
Вообщем я буду рад, если это будет кому-нибудь интересно и вдвойне рад, если это кому-нибудь поможет. Думаю, что каждому хочется в его проектах, когда один пользователь производит некое действие, все остальные могли сразу увидеть обновленные данные. Спасибо за ваше внимание, до скорой встречи.