Всем привет! Меня зовут Перебиковский Ярослав и это моя первая статья для Хабр.
Я ведущий разработчик компании «Эм Си Арт» — интегратора Битрикс24. Мы давние партнеры Битрикс24 и у нас в арсенале есть множество кастомных и, что важнее, интересных решений. Одним из них хотелось бы поделиться — расскажу о нем в разрезе пользователя, разработчика, архитектуры и опишу использованные подходы.
У одного из наших клиентов возникла нетривиальная задача — интегрировать CRM Битрикс24 с сайтом, написанным на Go. В качестве брокера сообщений клиент использовал Kafka - как самый устойчивый к нагрузкам и подходящий по ряду других параметров. Но оказалось, что готовых решений для интеграции с этим стеком технологий не было ни у нас, ни на рынке вообще. Поэтому решили написать собственный модуль с нуля.
Предыстория
Небольшая предыстория для погружения в путь развития проекта. Еще будучи на предыдущей позиции в компании. В один прекрасный, как ещё тогда казалось, день мне пришла задача: «Изучи, пожалуйста, как работает брокер сообщений — Kafka Apache» — примерно так звучало в тексте.
Всё ещё не чуя подвоха я принялся разбираться с инструментом: C-keeper, Zookeeper, патриции, партиции, продюсер и прочие радости инструмента и не только. Как древний Рим и зоопарк оказались в одном котле? Почему именно я должен его варить? И отчего это мой лид так хитро улыбается и потирает ручки, будучи знакомым с Kafka Apache и RabbitMQ лично...
В целом не будет спойлером сказать, что следующей на меня упала задача: «А теперь напиши модуль, осуществляющий интеграцию CRM Битрикс24 с брокером». Пройдя все 5 стадий принятия и ещё 3 стадии возвращения своей любви к постановщику задачи, смирившись со сжатыми сроками, я с головой погрузился в работу.
В общем, возвращаясь в серьёзное русло, это была классная задача и крутой опыт. В действительности, на тот момент не было таких решений, которые бы работали с Битрикс24 и покрывали требования внутренней политики заказчика, нашей команды и возможности масштабироваться, при этом разделяя ответственность между нами.
Решение
Мне совсем не хотелось походить на того индуса с арбузом — я не планировал тащить всё в один модуль и в результате разбить все вдребезги. Но в тот момент я не до конца понимал, как будет выглядеть итоговое решение... Поэтому сперва я раздобыл себе индульгенцию: «Решай сам! И... ну, ты в курсе последствий!» — и лишь затем взялся за дело.
Конечно, у нас уже была экспертиза в этом вопросе, и брокеры для нас — не новая история, поэтому пренебрегать опытом коллег я не стал и отправился собирать ценные инсайты на встречах с командой. На юге проглядывался Yandex Message Queue, работающий на Symfony/Console, на востоке — RabbitMQ со своим шаманом и бубном, а на западе — Kafka Apache с хранением offset-а в БД по требованию клиента.
Я пошел своим путем и начал строить замок из кубиков, свой собственный.
В итоге появилось два модуля. Первый — это базовый модуль для работы с брокером, который ничего не знает про бизнес-логику. Ему не важно, что за проект и какие в нем требования. Через второй модуль красной линией проходит именно бизнес-логика.
mcart.kafka
Так мы плавно переходим к устройству первого модуля. Начнем с пользовательской стороны.

После установки модуля в административном меню у нас появляется пункт с данными.
Статус Consumer — выводим текущее состояние чтения сообщений.
Статус Агента очереди отправки — выводит количество сообщений в очереди.
Consumer — можем запустить или остановить его вручную (не рубильник, а мягкое выключение с прочтением текущего сообщения и завершение).
Агент очереди отправки — выводим кнопку для запуска этого агента, если в очереди есть сообщения (об этом позже).
Установлено расширение для работы модуля — соответствует ли окружение необходимым требованиям для работы модуля.
Немного о настройках модуля: мы позволяем клиенту выбрать тип подтверждения при чтении сообщения, способ записи в партицию (указанную, rand-robin, определяемую управляющим брокером или указанной партиции в событии).
Также мы развели креды для consumer и producer, чтобы увеличить степень безопасности, с выбором типа подключения к брокеру. У consumer мы можем указать группу, что дает нам отказоустойчивость при нескольких ДЦ. Указываем количество сообщений, после которых наш читающий демон будет самостоятельно перезагружен, а также время в секундах до перезапуска при бездействии, чтобы освободить подключение к базе и зайти заново, что очень актуально для выходных или ночных простоев.


Дополнительно для разных стендов мы можем указать хранение логов в базе. Например, не хотелось бы раздувать базу на проде, а при проведении работ в брокере мы можем использовать внутреннюю очередь модуля и при выключенной Kafka продолжать собирать все сообщения в CRM.

Теперь давайте схематично посмотрим, как устроен модуль, и раскроем ещё несколько его преимуществ:

Таблицы:
mc_kafka_log
— таблица, в которую модуль пишет логи.mc_kafka_producer_queue
— таблица очереди, в которую производится запись при включенной опции или при ошибке отправки сообщения автоматически.
Файлы:
consumer.php
— демон, который запускается как PHP-процесс и читает сообщения в реальном времени.mcart.kafka/consumer.log
— файл лога, процесс переводит вывод ошибок в файл при падении.{/home/www/}consumer.log
— история жизни брокера: запуск, перезапуск, падение.
События, работающие через \Bitrix\Main\Event
:
OnBeforeKafkaLogAdd — событие записи в лог таблицу. Когда мы выключаем на проде запись в таблицу, с помощью этого события мы перенаправляем бизнес-модулем логи в удобное нам место: файловый лог, OpenSearch/Elasticsearch, Sentry.
GetKafkaMessage — событие, в которое прилетают сообщения. Так мы обеспечиваем чтение брокера исключительно через события Битрикс24.
- Стоит сказать, что именно эти события полностью обеспечивают независимость модуля в пространстве любого клиента.
OnAfterProduceErrorMessage — событие, аналогичное методу Kafka
rd_kafka_err2str
.OnAfterProduceMessage — событие, аналогичное методу Kafka
setErrorCb
(ошибки подключения).SendKafkaMessage — событие позволяет отправить сообщение в очередь.
$event = new Event(′mcart.kafka′, ′SendKafkaMessage′, [
′key′ = > 'uniq_key',
′message′ => $this->getMessage(),
])
$event->send();
Примерно так выглядит модуль, представляющий ядро общения с брокером. Он предоставляет статусную страницу для клиента и менеджера, набор событий для независимого существования и набор файлов, контролирующий работу consumer-элемента на демоне.
Подведем итог: мы создали независимый модуль, способный работать отказоустойчиво, существовать в рамках нескольких ДЦ, покрывать базовые потребности работы с брокером.
Теперь можно немного поговорить о втором модуле.
{client}.kafkamanager
Решено было использовать паттерн «реестр», который получает свой реестр-список из .settings.php
модуля.
return [
/* other */
′handler′ => [
′value’ => [
\Client\KafkaManager\DealUpdater::class,`
\Client\KafkaManager\ContactUpdater::class,`
]
],
];
Получая сообщения, мы применяем паттерн «очередь» и складываем их в DB, а агент, не затормаживая работу consumer-а, читает их на фоне.

Так что второй модуль достаточно прост в реализации. Мы не затормаживаем работу основного модуля, обеспечиваем быструю обработку сообщений и подключаем нужный нам провайдер логирования.
Вывод
В итоге мы создали гибкое решение — его легко настраивать, масштабировать и адаптировать под нужды клиента. Наша компания чаще работает с крупными заказчиками, чьи процессы выстроены с использованием Битрикс24. И эта система стабильно показывает хорошую работу с большими объемами данных на высокой скорости.
Наша задача — обеспечить быструю и правильную работу приложения для бизнеса. В конечном итоге клиент остался доволен, бизнес развивается, вот уже более года нагрузка продолжает расти, а мы этого и не замечаем. Мне кажется, что именно такими должны быть решения: с одной стороны — простыми, а с другой — полезными.