В предыдущей статье "Дерево Киви для поиска шаблона по тексту" я рассказывал, как можно эффективно находить по входящему сообщению все удовлетворяющие "поисковые запросы", избегая их перебора "в лоб" и прямого сопоставления. Это можно применить реализации полноценной Pub/Sub-системы, дополняющей уже существующие, такие как Nats, Apache Kafka или AWS SNS новой возможностью поддерживать действительно масштабируемое число wildcard-подписок. В этой статье я расскажу о такой системе.

Авакари на текущий момент - backend сервис, который использует gRPC API по ряду причин:

  • Эффективное использование ресурсов (HTTP/2, бинарная сериализация Protobuf)

  • Streaming - необходимо для реализации push уведомлений от сервиса клиенту. Здесь отдельное спасибо Кириллу Гусакову за подсказку.

Получение и отправка сообщений

Потоковая передача сообщений используется как для отправки сообщений в систему (Publish), так и для получения сообщений на выходе.

При выборе формата сообщений я решил остановиться на нейтральном стандарте CloudEvents. Исходные требования к формату сообщений были следующие:

  • Поддержка произвольных key-value метаданных, по которым будет происходить сопоставление с подписками

  • Независимость от протокола

  • Удобство (де)сериализации с помощью Protobuf

Пример payload при получении/отправке сообщений выглядит следующим образом:

{
   "msgs": [
      {
         "id": "3426d090-1b8a-4a09-ac9c-41f2de24d5ac",
         "type": "example.type",
         "source": "example/uri",
         "spec_version": "1.0",
         "attributes": {
            "author": {
               "ce_string": "Obi-Wan Kenobi"
            },
            "time": {
               "ce_timestamp": "1985-04-12T23:20:50.52Z"
            }
         },
         "text_data": "I felt a great disturbance in the force"
      }
   ]
}

Подписки

Для работы с пользовательскими подписками достаточно простого набора CRUDL операций (также gRPC). Чтобы пользователь мог определять, как выбирать сообщения, подписки должны содержать условия выбора.

Kiwi-условия

Так как входящие сообщения имеют метаданные в виде набора "key: input value", то условия выбора сообщения можно выразить как: "key: wildcard", где key - это искомый ключ метаданных, а wildcard - шаблон. Kiwi-условие, если короче.

Дополнительно kiwi-условия имеют следующие свойства:

  • Not - условие является отрицанием, то есть совпадение исключает сообщение

  • Partial - искать совпадения также среди лексем из input, который разбивается

Пример:

   "cond": {
      "not": false,
      "ktc": {
        "key": "title",
        "pattern": "?usk", // musk, Musk, dusk, ...
        "partial": true
      }
    }

Такому условию должны удовлетворять сообщения, которые в метаданных по ключу "title" содержат отдельные слова как, например, "musk" или полные строки вроде "Elon Musk" или "Dusk and her embrace".

Группы условий

Для полноценного описания ��ого, какие сообщения требуется получать, kiwi-условий недостаточно. Необходимо иметь возможность описывать сразу множество условий в одной и той же подписке (например по разным ключам метаданных) и применять логические операции:

  • And

  • Or

  • Xor

Для этой цели служат группы условий. Так как группа тоже является условием, можно использовать вложенность.

Полный пример описания подписки с групповым условием:

{
   "md": {
      "description": "my subscription 1",
      "enabled": true
   }, 
   "cond": {
      "not": false,
      // "gc" means "group condition"
      "gc": { 
         "logic": 0, // 0 - And, 1 - Or, 2 - Xor
         "group": [
            {
               "not": false,
               // "ktc" means "kiwi-tree condition"
               "ktc": {
                  "key": "key0", 
                  "pattern": "pattern?", 
                  "partial": false
               }
            }, 
            {
               "not": true,
               "ktc": {
                  "key": "key1", 
                  "pattern": "pattern1", 
                  "partial": true
               }
            }
         ]
      }
   }
}

Как это попробовать

На текущий момент есть два варианта:

  1. Самостоятельно раздеплоить Core систему, например, на minikube у себя дома.

  2. Использовать демо сервис в облаке.

В облачном варианте уже используется producer-rss компонент, который регулярно поглощает RSS из разных источников, конвертирует данные в сообщения и отправляет в систему. То есть можно подписаться на новости по какому-либо критерию и получать их.

Планы на будущее

В первую очередь, конечно же нужно добиться выполнения критериев production-ready и запустить боевой экземпляр сервиса. Тут ещё довольно много рутинной работы, такой как автоматизация выдачи сертификатов.

В перспективе Авакари можно будет использовать в довольно широком наборе сфер, таких как инвестиции, СМИ, реклама.

Можно представить интеграцию с чем-то вроде Авито, где новое объявление о продаже уведомляет всех, кто подписан на специфический товар, например iphone 42.

Вместо изобретения собственного UI есть идея реализации чат-бота, который будет позволять управлять подписками, отправлять и получать сообщения в/из Авакари.