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


На фото представлені пристрої, використані для прототипування. Як видно, за основу взята платформа Intel Edison, так як вона підтримує багато архітектури, в тому числі MIPS і ARM.

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

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



Є користувач, який через хмарний сервіс або безпосередньо (Wi-Fi) підключається до IoT — пристрою. Також десь в Інтернеті є хмарний сервер додатка. Хмарою може служити що завгодно: скажімо, інстанси AWS або Azure або виділений сервер. Для обміну даними між сервером додатка і IoT-пристроями встановлюється з'єднання з якогось протоколу. IoT-пристрої якимось чином з'єднані один з одним (наприклад, по Ethernet або Wi-Fi). Крім цього, є окрема група IoT-пристроїв, що генерують телеметричні дані (такі як показники освітленості або температура).

У загальній складності, може набратися більше 100 або навіть більше 1000 пристроїв. Моя основна задача полягала в тому, щоб забезпечити обмін даними між хмарою і цими IoT-пристроями. Перш ніж рухатися далі, варто згадати, які вимоги ставилися до системи:

  • Вона повинна синхронізувати дані між IoT-пристроями.
  • Вона повинна збирати дані з IoT-пристроїв.
  • Вона повинна синхронізувати дані між IoT-пристроями і хмарою.
Технічна реалізація


Тут все досить просто: користувач підключається до сервера додатка з HTTP(S), WebSocket або подібного протоколу. Невелика задачка для читачів: як ви думаєте, що можна використовувати для з'єднання між сервером додатка і IoT-пристроєм?



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



Ви можете запитати, навіщо мені реплікація. Відповідь проста: реплікація використовується для синхронізації даних, тому я можу всюди — включаючи хмара і 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, з яких мені необхідно зібрати всі телеметричні дані.


Коротка схема роботи готової системи

Пристрої з сенсорами передають телеметричні дані за допомогою радіохвиль. Цей стандарт називається 6LoWPAN (IPv6 поверх малопотужних бездротових персональних мереж). Зауважу, що я не використав у проекті LoRaWAN. Можливо, я знайду застосування цієї технології в майбутньому, але в цій статті я зупинюся на 6LoWPAN. Отже, для збору телеметричних даних я буду використовувати шлюз, який є важливою частиною системи. Шлюз — це MIPS-пристрій (MIPS — це сімейство процесорів) з WAN-антеною для збору даних, що передаються за допомогою радіохвиль. Крім цього, на шлюзі встановлено додаток 6LBR, конвертирующее отримані дані в IPv6-пакети.

Додаток 6LBR


Зображення вище ілюструє принцип роботи 6LBR. Шлюз з встановленим на нього 6LBR служить конвертером між бездротової сенсорної мережею і будь-який інший. На картинці зображено конвертація з бездротової сенсорної мережі IP-мережу лише тому, що так 6LBR працює за замовчуванням. Трохи пізніше я поясню, як це змінити поведінку.

Більш детальну інформацію можна знайти на сторінці 6LBR на GitHub.

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

Навіщо ж мені знадобилася прямий запис у MQTT-брокер? Відповідь проста: справа в legacy-коді.
Тут я б хотів сказати пару слів про додатки 6LBR. У загальному випадку додаток 6LBR — це написаний на С код з API, що дозволяє використовувати IP стек та робити деякі інші речі. Розробка такої програми пов'язана як мінімум з двома труднощами: складна модель потоків і складна модель пам'яті. Тому запасіться терпінням і приготуйтеся до частих аварійних завершениям вашої програми. Нижче наведено невеликий шматок розробленого мною додатки 6LBR (заздалегідь прошу вибачення: можу викласти тільки картинку з навмисно заплутаним кодом, тому що исходники закриті):



Зверніть увагу на одну цікаву річ — 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()
!

Висновок
Давайте подивимося на те, що у нас вийшло:



З'єднання з сервером додатка встановлено допомогою наданої 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 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.