Как я чаще всего структурировал подобные системы:
. Сокет – сервис, который держит WS / TCP / UDP / MQTT соединения, он существует отдельно, чтобы когда мы редеплоим или масштабируем сервисы с бизнес-логикой соеднинения не отваливались.
. Ресивер – сервис обработки сообщений с основной бизнес-логикой.
. Командер – опционально, но в IoT чаще всего нужен сервис, который мапит ваши команды в команды, которые будет понимать устройство и отправляет их.
Сообщения приходят в Сокет, идут в Ресивер, он отправляет команды ответа Камендеру и тот отправляет сообщения через Сокет (очень CQRS-ный подход с раздлением потока чтения и записи)
Какие полезные трюки важно помнить:
. Сервисы должны общаться асинхронно (не ждать ответа) иначе вы рано или поздно будете попадать в dead lock всей системы.
. Соответственно, все сообщения должны представлять собой "события" (event) – структуру описывающие произошедший факт с данными об этом факте ("ПользовательСоздалСообщение", "СообщениеУспешноОбработано", "СообщениеУспешноОтправлено", etc.)
. Сокет должен содержать минимум логики, чтобы его не приходилось часто редеплоить ИЛИ если вам больше важно скорость, тогда объединяем Сокет, Ресивер и Командер
. Все сообщения стоит хранить персистентно (Kafka или БД)
. В Realtime системах максимально важны 2 фактора: скорость и линейный рост нагрузки при увеличение числа сообщений.
Хочу обратить внимание именно на последний пункт: чаще всего, самым узким горшлышком вашей системы будет не язык программирования, не транспорт и не сеть, а обращения в БД.
Во-первых, каждое обращение – это время, во-вторых, запросы к БД сложно батчить, потому что каждое сообщение процесситься независимо друг от друга, в-третьих, если у вас не master-master база у вас будет ограниченное число коннектов.
Все это приводит к тому, что
увеличение числа сообщений в секунду экспоненциально увеличивает нагрузку.
Чтобы сделать скорость обработки сообщений константной, а рост нагрузки линейным лучшим вариантом будет доставать нужные данные в момент старта сервиса (или прихода первого сообщения), класть их в память (или в кэш типа Redis), с каждым сообщением проводить измения данных прямо в памяти и время от времени синхронизировать данные с основной базой
Задача не из легких, но именно тут нам на помощь приходит паттерн "Акторы"
Акторы очень хорошо описывают то, как максимально удобно и понятно структурировать логику подобного рода.
Сообщение получилось итак слишком большим, поэтому продолжение в следующей части, а пока посмотрите видос про акторы, который я еле откопал:
https://www.youtube.com/watch?v=Fw-CXSG8KZE