Используем Apache Ignite в быту

kefirr 20 октября в 09:45 6,4k

В продолжение темы «доступным языком про 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 делает работу с любыми данными быстрой и приятной: сериализовать, сохранить в памяти или на диск, передать по сети, обеспечить консистентность и потокобезопасность. Даже самые маленькие проекты могут извлечь пользу из продукта, не беспокоясь о возможном увеличении нагрузки в будущем.

Проголосовать:
+21
Сохранить: