В продолжение темы «доступным языком про Ignite / GridGain», начатой в предыдущем посте (Для чего нужен Apache Ignite), давайте рассмотрим примеры использования продукта «для простых смертных».


Терабайты данных, кластеры на сотни машин, big data, high load, machine learning, микросервисы и прочие страшные слова — всё это доступно Ignite. Но это не значит, что он не годится для менее масштабных целей.


Сегодня мы рассмотрим, как Ignite может легко хранить любые ваши объекты, обмениваться ими по сети и обеспечивать взаимодействие .NET и Java.


Apache Ignite.NET



Говорить будем больше о .NET API, но какие-то вещи применимы и в Java.


Встраиваемая база данных


Ignite не требует установки и настройки, может запускаться прямо в вашем процессе, а с версии 2.1 умеет хранить данные на диске. Добавим к этому быструю и компактную встроенную сериализацию и получим самый простой способ сохранять любые ваши объекты на диск.


var cfg = new IgniteConfiguration
{
    // Включить хранение данных на диске
    PersistentStoreConfiguration = new PersistentStoreConfiguration()
};

var ignite = Ignition.Start(cfg);
ignite.SetActive(true);  // Явная активация необходима при включённом persistence
var cache = ignite.GetOrCreateCache<int, MyClass>("foo");
cache[cache.GetSize() + 1] = new MyClass();

Вот и весь код! Запускаем несколько раз и убеждаемся, что данные не теряются.


При этом к MyClass не предъявляется никаких требований! Это может быть что угодно, любой вложенности, включая делегаты, анонимные типы и методы, деревья выражений, динамические объекты и так далее. Может ли ваш сериализатор такое? Не думаю. Единственное исключение — нельзя делать IntPtr, но это для вашего же блага. Сериализовать указатели — плохая идея.


Более того, сериализованные объекты не являются «чёрным ящиком», IBinaryObject позволяет выборочно получать и модифицировать отдельные поля объектов.


Дикие трюки с сериализацией
using (var ignite = Ignition.Start())
{
    var cache = ignite.CreateCache<int, object>("c");

    // Сериализуем Type, обратно получаем тот же инстанс!
    cache[1] = typeof(int);
    Console.WriteLine(ReferenceEquals(typeof(int), cache[1]));  // true

    // Сериализуем делегат.
    var greeting = "Hi!";
    cache[2] = (Action) (() => { Console.WriteLine(greeting); });
    ((Action) cache[2])();  // Дезериализуем, выполняем: Hi!

    // Теперь отредактируем сериализованный делегат!
    // Переключаемся в режим Binary - работа в сериализованном виде.
    ICache<int, IBinaryObject> binCache = cache.WithKeepBinary<int, IBinaryObject>();
    // Читаем делегат в виде BinaryObject.
    IBinaryObject binDelegate = binCache[2];
    // Получим поле target0 - это класс, содержащий захваченные переменные.
    IBinaryObject target = binDelegate.GetField<IBinaryObject>("target0");
    // Меняем значение захваченной переменной greeting.
    target = target.ToBuilder().SetField("greeting", "Woot!").Build();
    // Собираем всё обратно и кладём в кэш.
    binCache[2] = binDelegate.ToBuilder().SetField("target0", target).Build();

    // Берём из кэша в обычном, десериализованном режиме, и запускаем.
    ((Action)cache[2])();  // Woot!

    // Анонимный тип.
    cache[3] = new { Foo = "foo", Bar = 42 };
    // Поля выглядят интересно.
    Console.WriteLine(binCache[3]);  // ...[<Bar>i__Field=42, <Foo>i__Field=foo]

    // Динамический объект.
    dynamic dynObj = new ExpandoObject();
    dynObj.Baz = "baz";
    dynObj.Qux = 1.28;

    cache[4] = dynObj;
    Console.WriteLine(binCache[4]); // _keys=[Baz, Qux], _dataArray=[baz, 1.28, ]
}

Разумеется, работать со всеми этими данными можно как в режиме ключ-значение (ICache.Put, ICache.Get), так и через SQL, LINQ, полнотекстовый поиск.


Замечу, что, помимо встраиваемого режима, с Ignite можно работать через ODBC и JDBC.


Межпроцессное взаимодействие


Браузер Google Chrome использует отдельный процесс для каждой вкладки. Реализовать такой подход с Ignite очень просто: данными легко и прозрачно можно обмениваться через кэш, синхронизировать выполнение кода через распределённые структуры данных, обмениваться сообщениями через Messaging.


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


var history = ignite.GetCache<Guid, HistoryItem>("history");
history.Put(Guid.NewGuid(), new HistoryItem(url, DateTime.UtcNow));

// Вывести последние 10 посещённых страниц
history.AsCacheQueryable()
          .Select(x => x.Value)
          .OrderByDescending(x => x.Date)
          .Take(10);

Ignite обеспечивает потокобезопасный доступ к данным в рамках всего кластера.


Кросс-платформенное взаимодействие


В Ignite есть полноценные API на Java, .NET, C++. Запускаться можно на Windows, Linux, Mac. К примеру, часть вашего приложения может быть написана на Java и запущена на Linux, другая часть — на .NET и под Windows.


Протокол сериализации универсален, объекты, записанные на одной платформе, могут быть прочитаны на другой. Структуры данных, упомянутые выше, также кроссплатформенные.


Более того, есть возможность прозрачно вызывать со стороны .NET сервисы написанные на Java, :


public class MyJavaService implements Service {
  public String sayHello(String x) {
    return "Hello, " + x;
  }
}

ignite.services().deployClusterSingleton("myJavaSvc", new MyJavaService());

interface IJavaService  // имя не имеет значения
{
  string sayHello(string str);  // имя идентично, сигнатура должна быть совместима
}

var prx = ignite.GetServices().GetServiceProxy<IJavaService>("myJavaSvc");
string result = prx.sayHello("Vasya");  

Как и везде, вместо строк можно передавать любые объекты.


Подробное руководство по построению кроссплатформенного Ignite-кластера, на примере простого чатика, есть тут (на английском).


Заключение


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

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


  1. jam31
    20.10.2017 10:01

    Как по-вашему, когда Ignite станет полностью production-ready? Всё же, он ещё довольно молод. И вообще, это ведь прямая замена Cassandra, верно?


    1. kefirr Автор
      20.10.2017 11:21

      Ignite вполне себе production-ready и работает в продакшне у разных серьёзных чуваков :)
      https://www.gridgain.com/customers/featured-customers


      Cassandra довольно сильно отличается от Ignite.


    1. VanquisherWinbringer
      20.10.2017 22:27

      В смысле замена Cassandra? Она же вообще про другое.


    1. dmagda
      21.10.2017 02:17

      Да, после выпуска распределенного дискового хранилища, Ignite дошел до того этапа, когда он может полностью заменить Cassandra. По большому счету, в Ignite есть все то, что дает Cassandra + SQL with JOINs, ACID transaction и полноценное in-memory storage для данных и индексов.


  1. Yo1
    20.10.2017 11:51
    +1

    вроде ignite это распределенный кластер, а тут в каком-то хитром режиме embeded поднимается?


    1. kefirr Автор
      20.10.2017 11:54

      Это не хитрый режим. Выполняя код Ignition.Start() в .NET/Java/C++ мы запускаем ноду Ignite внутри текущего процесса.


      В предыдущем посте подробнее: https://habrahabr.ru/company/gridgain/blog/325830/


  1. 4robots
    20.10.2017 12:24
    +1

    Подскажите а для Python полноценная библиотека планируется?


    1. kefirr Автор
      20.10.2017 12:29

      Да, сейчас идёт разработка открытого клиентского протокола, который позволит писать клиентов на любых языках.


      Насколько я знаю, Python в планах, особенно в связи с активностью по ML.


  1. blanabrother
    20.10.2017 17:11

    На сайте написано ".NET starts the JVM in the same process and communicates with it via JNI & C++". Это именно то, что Вы описали в Межпроцессном взаимодействии?


    1. kefirr Автор
      20.10.2017 18:00
      +1

      Не совсем так; in-process JVM — это детали реализации.


      Межпроцессное взаимодействие осуществляется через различные API Ignite — Cache, Messaging, Compute, и так далее. Эти API есть в Java, .NET, C++. Таким образом, приложения в разных процессах, написанные на разных языках, могут взаимодействовать друг с другом.


      1. DocDVZ
        23.10.2017 14:54
        +1

        Можно ли строить микросервисную архитектуру, используя IgniteQueue в качестве транспорта? И есть ли внутри Ignite AP инструменты ootb для реализации request-response взаимодействия между нодами?


        1. kefirr Автор
          23.10.2017 15:03

          строить микросервисную архитектуру

          Для этого есть Ignite Services API (упомянут в этой статье, кстати):
          https://habrahabr.ru/company/gridgain/blog/327380/
          https://apacheignite.readme.io/docs/service-grid


          request-response взаимодействия между нодами

          Да, это всё те же services, а так же Compute, который помимо map-reduce функционала позволяет выборочно выполнить код на конкретном узле.
          https://apacheignite-net.readme.io/docs/compute-grid


  1. xystarcha
    23.10.2017 14:54
    +1

    А планируется ли .NET API для k-means?


    1. kefirr Автор
      23.10.2017 14:57

      Да, без сомнения, но сроков пока нет. Думаю, в следующем году.