Master-master репликация и масштабирование приложений между всеми IoT-устройствами и облаком

d94c70f6fe71492a8bad91364d20c6e0.png
На фото представлены устройства, использованные для прототипирования. Как видно, за основу взята платформа Intel Edison, так как она поддерживает многие архитектуры, в том числе MIPS и ARM.

Всем привет. В этой статье я хотел бы поделиться опытом решения одной интересной проблемы, связанной с синхронизацией данных между IoT-устройствами и облачным приложением. Сначала я расскажу об основной идее и целях моего проекта, а затем подробно опишу его техническую сторону и реализацию: речь пойдет об ОС Contiki, базах данных, протоколах и подобных аспектах. В заключение я кратко перечислю технологии, использованные при построении системы.

Вкратце о проекте


Для начала давайте поговорим об основной идее проекта. Ниже схематично изображен принцип работы готовой системы:

97d29e74dff84cf7a45ef55198a5a98d.png

Есть пользователь, который через облачный сервис или напрямую (по Wi-Fi) подключается к IoT- устройству. Также где-то в Интернете имеется облачный сервер приложения. Облаком может служить что угодно: скажем, инстанс AWS или Azure или выделенный сервер. Для обмена данными между сервером приложения и IoT-устройствами устанавливается соединение по какому-то протоколу. IoT-устройства каким-то образом соединены друг с другом (например, по Ethernet или Wi-Fi). Помимо этого, есть отдельная группа IoT-устройств, генерирующих телеметрические данные (такие как показатели освещенности или температура).

В общей сложности, может набраться больше 100 или даже больше 1000 устройств. Моя основная задача заключалась в том, чтобы обеспечить обмен данными между облаком и этими IoT-устройствами. Прежде чем двигаться дальше, стоит упомянуть, какие требования предъявлялись к системе:

  • Она должна синхронизировать данные между IoT-устройствами.
  • Она должна собирать данные с IoT-устройств.
  • Она должна синхронизировать данные между IoT-устройствами и облаком.

Техническая реализация


d1a31403ef684e72a9b5ddceff001d43.png

Здесь все довольно просто: пользователь подключается к серверу приложения по HTTP (S), WebSocket или подобному протоколу. Небольшая задачка для читателей: как вы думаете, что можно использовать для соединения между сервером приложения и IoT-устройством?

fc1f8c07a6354540ad11654551e9ec5f.png

Если вы подумали про MQTT, вы однозначно правы! Равно как и те, кто выбрал HTTP (S). На самом деле подойдет любой протокол — выбирайте на свой вкус! Мой же выбор пал на — барабанная дробь — асинхронную репликацию! Я имею в виду обычную для баз данных репликацию.

a111ad038a9841bd97b8885e28ee466f.png

Вы можете спросить, зачем мне репликация. Ответ прост: репликация используется для синхронизации данных, поэтому я могу повсюду — включая облако и IoT-устройства — поддерживать одну версию базы данных. Однако репликацию довольно сложно реализовать. Хочешь репликацию — заведи базу данных, которая ее поддерживает, потому что — повторюсь — репликация естественно присуща базам данных.

Здесь я бы хотел сказать пару слов о тех базах данных, которые я рассматривал при работе над проектом: SQLite, Redis, MySQL, PostgreSQL и Tarantool.

Я сравнил их характеристики и попробовал запустить несколько штук — за исключением MySQL и PostgreSQL — прямо на IoT-устройстве. Ниже расскажу, что из этого вышло.

SQLite — однозначно хорошее решение для хранения данных непосредственно на IoT-устройстве, но у нее нет репликации, и она не поддерживает параллельный доступ из разных процессов.
Redis не поддерживает master-master репликацию и поэтому не может решить мою проблему, так как мне необходима двусторонняя репликация.

MySQL и PostgreSQL слишком тяжеловесны для IoT-устройства, так что я даже не пытался их устанавливать. Но если вы все-таки решите это сделать, смело делитесь своим опытом в комментариях.

Последней в моем списке шла база данных Tarantool. Сразу скажу, что я являюсь коммитером в проект Tarantool, поэтому хорошо знаю сам проект и людей, которые его разрабатывают. К тому же, в Tarantool есть master-master репликация. В общем, для меня это был определенно лучший вариант. Вы же можете использовать в своем проекте другую базу данных. Основная идея, которую я пытаюсь донести, в том, что IoT-устройства могут использовать базы данных с master-master репликацией для обмена данными.

До настоящего момента я лишь поверхностно знакомил вас с проектом. Теперь давайте немного погрузимся в его технические аспекты.

Начну с проблем, с которыми я столкнулся при использовании Tarantool. Во-первых, Tarantool не запускалась на архитектуре ARMv7. Во-вторых, Tarantool не запускалась в 32-битном окружении, что только усугубляло ситуацию. В итоге я смог решить эти проблемы. Ниже приведу правила разработки, которые мне в этом помогли.

  1. Используйте toolchain-файлы для CMake. В противном случае вы, так же как и я, потратите много времени на исправление CMake-файлов.
  2. Не используйте беззнаковый тип и другие типы, для которых не указан размер. В libc для этого есть специальные типы, такие как uint32_t. Иначе можно получить неопределенное поведение. Это правило применимо только к C/C++.
  3. Портируйте ваши автотесты.

Ожидается, что ваши автотесты можно запустить на IoT-устройстве. Если это не так, есть риск убить много времени на отладку.

Итак, у меня есть работающая база данных с master-master репликацией. Замечательно! Следующий шаг — соединить устройства, на которых эта база данных установлена, по 6LoWPAN. Напомню, у меня есть сеть из множества IoT-устройств, соединенных друг с другом по 6LoWPAN, с которых мне необходимо собрать все телеметрические данные.

e24bdeeb75a24a1da75e66000a1e224c.png
Краткая схема работы готовой системы

Устройства с сенсорами передают телеметрические данные посредством радиоволн. Этот стандарт называется 6LoWPAN (IPv6 поверх маломощных беспроводных персональных сетей). Замечу, что я не использовал в проекте LoRaWAN. Возможно, я найду применение этой технологии в будущем, но в этой статье я сосредоточусь на 6LoWPAN. Итак, для сбора телеметрических данных я буду использовать шлюз, являющийся важной частью системы. Шлюз — это MIPS-устройство (MIPS — это семейство процессоров) с WAN-антенной для сбора данных, передаваемых посредством радиоволн. Кроме этого, на шлюзе установлено приложение 6LBR, конвертирующее полученные данные в IPv6-пакеты.

Приложение 6LBR


74d054962684467abf0170097c3304d3.png

Изображение выше иллюстрирует принцип работы 6LBR. Шлюз с установленным на него 6LBR служит конвертером между беспроводной сенсорной сетью и любой другой. На картинке изображена конвертация из беспроводной сенсорной сети в IP-сеть лишь потому, что так 6LBR работает по умолчанию. Немного позже я объясню, как изменить это поведение.

Более подробную информацию можно найти на странице 6LBR на GitHub.

Вы можете спросить, что же мне дает использование 6LBR. Во-первых, я получаю стек IP, так что я могу использовать функционал стеков TCP и UDP в моих приложениях 6LBR. Во-вторых, я могу использовать любое устройство ввода-вывода с 6LBR. Скажем, можно записать сырые данные прямо в bash. =) К сожалению, 6LBR не пишет напрямую в MQTT. MQTT-брокеры ничего не знают о сырых данных, и с этим приходится мириться.

Зачем же мне понадобилась прямая запись в MQTT-брокер? Ответ прост: дело в legacy-коде.
Здесь я бы хотел сказать пару слов о приложениях 6LBR. В общем случае приложение 6LBR — это написанный на С код с API, позволяющим использовать стек IP и делать некоторые другие вещи. Разработка такого приложения сопряжена как минимум с двумя трудностями: сложная модель потоков и сложная модель памяти. Поэтому запаситесь терпением и приготовьтесь к частым аварийным завершениям вашей программы. Ниже приведен небольшой кусок разработанного мной приложения 6LBR (заранее прошу прощения: могу выложить только картинку с нарочно запутанным кодом, потому что исходники закрыты):

8b205764393c4743b7bb7dda7291280f.png

Обратите внимание на одну интересную вещь — PROCESS_YIELD (). В 6LBR есть кооперативная многозадачность, а это значит, что приложения 6LBR должны возвращать управление в каждой итерации цикла. Код не должен выполняться слишком долго.

Итак, давайте еще раз посмотрим, на какой стадии находится наш проект. С помощью шлюза и установленного на него приложения 6LBR я создал mesh network для чтения и записи данных внутри нее. Мне также удалось обернуть IP-пакеты в MQTT-сообщения, каждое из которых содержит информацию об устройстве, включая телеметрические данные. Кроме того, у меня появилась возможность манипулировать устройствами ввода-вывода: скажем, я могу записывать MQTT-сообщения на UART. Но затем я столкнулся с новой проблемой: Tarantool не работает с MQTT-брокерами. Ниже расскажу, как мне удалось обойти это ограничение.

Я решил использовать libmosquitto, написанную на чистом С MQTT-библиотеку, потому что она позволяет довольно просто интегрировать MQTT в мое приложение. Ниже приведен пример использования этой библиотеки для работы с MQTT-сообщениями (ссылка):

static
int
mosq_poll_one_ctx(mosq_t *ctx, int revents, size_t timeout, int max_packets)
{
	/** XXX
	 * I'm confused: socket < 0 means MOSQ_ERR_NO_CONN
	 */
	int rc = MOSQ_ERR_NO_CONN;

	int fd = mosquitto_socket(ctx->mosq);

	if (fd >= 0) {

		/** Wait until event
		 */
		revents = coio_wait(fd, revents, timeout);

		if (revents != 0) {
			if (revents & COIO_READ)
				rc = mosquitto_loop_read(ctx->mosq, max_packets);
			if (revents & COIO_WRITE)
				rc = mosquitto_loop_write(ctx->mosq, max_packets);
		}

		/**
		 * mosquitto_loop_miss
		 * This function deals with handling PINGs and checking
		 * whether messages need to be retried,
		 * so should be called fairly _frequently_(!).
		 * */
		if (ctx->next_misc_timeout < fiber_time64()) {
			rc = mosquitto_loop_misc(ctx->mosq);
			ctx->next_misc_timeout = fiber_time64() + 1200;
		}
	}

    return rc;
}

Я могу взять ссылку на дескриптор сокета и использовать собственный событийный цикл для обработки некоторых событий. И это здорово! Хотел бы обратить ваше внимание на то, что в Tarantool, так же как и в 6LBR, есть кооперативная многозадачность. Для возвращения управления Tarantool использует coio_wait().

Ах да, забыл упомянуть, что Tarantool — это еще и сервер приложений на языке Lua. Сюрприз! Поэтому я портировал libmosquitto на Lua. Ниже привожу кусок кода, в котором вызывается функция, которую вы уже видели в предыдущем примере:

__poll_forever = function(self)
      local mq = self.mqtt
      while true do
        self.connected, _ = mq:poll_one()
        if not self.connected then
          if self.auto_reconect then
            self:__try_reconnect()
          else
            log.error(
              "mqtt: the client is not currently connected, error %s", emsg)
          end
        end
        fiber.sleep(self.POLL_INTERVAL)
      end
    end,

Я также портировал все функции из API libmosquitto. Посмотреть на результат можно здесь. По ссылке дан пример использования. Все что нужно сделать для сбора данных со всех устройств внутри mesh network — это вызвать функцию subscribe() из определенного места и опубликовать метод get()!

Заключение


Давайте посмотрим на то, что у нас получилось:

b21bbf03dcc3487d99b8049ccd687eb4.png

Соединение с сервером приложения установлено посредством предоставляемой Tarantool master-master репликации. Из этого вытекают два полезных свойства:

  1. Если сервер приложения изменяет какие-либо данные, эти обновленные данные доставляются на все IoT-устройства в сети.
  2. Если IoT-устройство изменяет какие-либо данные, эти обновленные данные доставляются на сервер приложения.

Именно эти свойства и являются решением моих проблем.

Я также могу соединить мои IoT-устройства посредством master-master репликации. Таким образом устройства и облако объединяются в кластер, который можно использовать для синхронизации всех данных. Все IoT-устройства и облако синхронизированы большую часть времени, за исключением случаев, когда между ними пропадает соединение. Как только соединение будет восстановлено, все данные снова синхронизируются. Просто замечательно!

Шлюз с установленным на него приложением 6LBR позволяет обмениваться данными между моими IoT-устройствами и другими IoT-устройствами. Он оборачивает каждое сообщение в MQTT-сообщение и передает его в канал UART.

IoT-устройство #N с установленным на него MQTT-брокером считывает эти сообщения из канала UART. MQTT-брокер перенаправляет сообщения в Tarantool по MQTT-соединению. Tarantool считывает их, затем для каждого сообщения сервер приложений Tarantool выполняет некоторый код.

IoT-устройство #N соединено со всеми остальными устройствами посредством предоставляемой Tarantool master-master репликации. Такая же репликация используется для соединения всех устройств с облаком.

На этом все! Я решил поставленную задачу и очень надеюсь, что мой опыт поможет вам в ваших собственных проектах в будущем. Подытожу: я использовал Tarantool и как основной фронтенд на моих выделенных серверах, и как сервер приложений. Если вас заинтересовала данная тема, рекомендую взглянуть на другую мою статью на английском языке. Оставайтесь на связи и следите на новостями!

Комментарии (0)

© Habrahabr.ru