В продолжение темы «доступным языком про Ignite / GridGain», начатой в предыдущем посте (Для чего нужен Apache Ignite), давайте рассмотрим примеры использования продукта «для простых смертных».
Терабайты данных, кластеры на сотни машин, big data, high load, machine learning, микросервисы и прочие страшные слова — всё это доступно Ignite. Но это не значит, что он не годится для менее масштабных целей.
Сегодня мы рассмотрим, как Ignite может легко хранить любые ваши объекты, обмениваться ими по сети и обеспечивать взаимодействие .NET и Java.
Говорить будем больше о .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)
Yo1
20.10.2017 11:51+1вроде ignite это распределенный кластер, а тут в каком-то хитром режиме embeded поднимается?
kefirr Автор
20.10.2017 11:54Это не хитрый режим. Выполняя код
Ignition.Start()
в .NET/Java/C++ мы запускаем ноду Ignite внутри текущего процесса.
В предыдущем посте подробнее: https://habrahabr.ru/company/gridgain/blog/325830/
4robots
20.10.2017 12:24+1Подскажите а для Python полноценная библиотека планируется?
kefirr Автор
20.10.2017 12:29Да, сейчас идёт разработка открытого клиентского протокола, который позволит писать клиентов на любых языках.
Насколько я знаю, Python в планах, особенно в связи с активностью по ML.
blanabrother
20.10.2017 17:11На сайте написано ".NET starts the JVM in the same process and communicates with it via JNI & C++". Это именно то, что Вы описали в Межпроцессном взаимодействии?
kefirr Автор
20.10.2017 18:00+1Не совсем так; in-process JVM — это детали реализации.
Межпроцессное взаимодействие осуществляется через различные API Ignite — Cache, Messaging, Compute, и так далее. Эти API есть в Java, .NET, C++. Таким образом, приложения в разных процессах, написанные на разных языках, могут взаимодействовать друг с другом.
DocDVZ
23.10.2017 14:54+1Можно ли строить микросервисную архитектуру, используя IgniteQueue в качестве транспорта? И есть ли внутри Ignite AP инструменты ootb для реализации request-response взаимодействия между нодами?
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
jam31
Как по-вашему, когда Ignite станет полностью production-ready? Всё же, он ещё довольно молод. И вообще, это ведь прямая замена Cassandra, верно?
kefirr Автор
Ignite вполне себе production-ready и работает в продакшне у разных серьёзных чуваков :)
https://www.gridgain.com/customers/featured-customers
Cassandra довольно сильно отличается от Ignite.
VanquisherWinbringer
В смысле замена Cassandra? Она же вообще про другое.
dmagda
Да, после выпуска распределенного дискового хранилища, Ignite дошел до того этапа, когда он может полностью заменить Cassandra. По большому счету, в Ignite есть все то, что дает Cassandra + SQL with JOINs, ACID transaction и полноценное in-memory storage для данных и индексов.