Przewodnik po Apache Ignite

1. Wstęp

Apache Ignite to rozproszona platforma open source skoncentrowana na pamięci. Możemy go używać jako bazy danych, systemu buforowania lub do przetwarzania danych w pamięci.

Platforma wykorzystuje pamięć jako warstwę pamięci, dlatego ma imponującą wydajność. Mówiąc najprościej, jest to jedna z najszybszych platform przetwarzania danych atomowych, która jest obecnie używana w produkcji.

2. Instalacja i konfiguracja

Na początek zapoznaj się ze stroną wprowadzającą, aby uzyskać instrukcje dotyczące wstępnej konfiguracji i instalacji.

Zależności Mavena dla aplikacji, którą będziemy budować:

 org.apache.ignite ignite-core ${ignite.version}   org.apache.ignite ignite-indexing ${ignite.version} 

Ignite-core jest jedyną obowiązkową zależnością projektu . Ponieważ chcemy również współdziałać z SQL,jest tu również indeksowanie zapłonu . $ {ignite.version} to najnowsza wersja Apache Ignite.

Na koniec uruchamiamy węzeł Ignite:

Ignite node started OK (id=53c77dea) Topology snapshot [ver=1, servers=1, clients=0, CPUs=4, offheap=1.2GB, heap=1.0GB] Data Regions Configured: ^-- default [initSize=256.0 MiB, maxSize=1.2 GiB, persistenceEnabled=false]

Powyższe dane wyjściowe konsoli pokazują, że jesteśmy gotowi do pracy.

3. Architektura pamięci

Platforma oparta jest na trwałej architekturze pamięci . Umożliwia to przechowywanie i przetwarzanie danych zarówno na dysku, jak iw pamięci. Zwiększa wydajność dzięki efektywnemu wykorzystaniu zasobów pamięci RAM klastra.

Dane w pamięci i na dysku mają taką samą reprezentację binarną. Oznacza to brak dodatkowej konwersji danych podczas przenoszenia z jednej warstwy do drugiej.

Trwała architektura pamięci dzieli się na bloki o stałej wielkości zwane stronami. Strony są przechowywane poza stertą Java i zorganizowane w pamięci RAM. Posiada unikalny identyfikator: FullPageId .

Strony współdziałają z pamięcią przy użyciu abstrakcji PageMemory .

Pomaga w czytaniu, pisaniu strony, a także przydzielaniu identyfikatora strony. W pamięci Ignite kojarzy strony z buforami pamięci .

4. Strony pamięci

Strona może mieć następujące stany:

  • Unloaded - nie załadowano bufora strony do pamięci
  • Wyczyść - bufor strony jest ładowany i synchronizowany z danymi na dysku
  • Durty - bufor strony przechowuje dane różniące się od tych na dysku
  • Brudny w punkcie kontrolnym - kolejna modyfikacja rozpocznie się, zanim pierwsza pozostanie na dysku. Tutaj zaczyna się punkt kontrolny, a PageMemory przechowuje dwa bufory pamięci dla każdej strony.

Trwała pamięć alokuje lokalny segment pamięci zwany regionem danych . Domyślnie ma pojemność 20% pamięci klastra. Konfiguracja wielu regionów umożliwia przechowywanie użytecznych danych w pamięci.

Maksymalna pojemność regionu to Segment Pamięci. Jest to pamięć fizyczna lub ciągła tablica bajtów.

Aby uniknąć fragmentacji pamięci, jedna strona zawiera wiele wpisów klucz-wartość . Każdy nowy wpis zostanie dodany do najbardziej optymalnej strony. Jeśli rozmiar pary klucz-wartość przekracza maksymalną pojemność strony, Ignite przechowuje dane na więcej niż jednej stronie. Ta sama logika dotyczy aktualizacji danych.

Indeksy SQL i pamięci podręcznej są przechowywane w strukturach znanych jako B + Drzewa. Klucze pamięci podręcznej są uporządkowane według wartości kluczy.

5. Cykl życia

Każdy węzeł Ignite działa na jednej instancji maszyny JVM . Istnieje jednak możliwość skonfigurowania wielu węzłów Ignite działających w jednym procesie JVM.

Przyjrzyjmy się typom zdarzeń w cyklu życia:

  • BEFORE_NODE_START - przed uruchomieniem węzła Ignite
  • AFTER_NODE_START - odpala zaraz po uruchomieniu węzła Ignite
  • BEFORE_NODE_STOP - przed zainicjowaniem zatrzymania węzła
  • AFTER_NODE_STOP - po zatrzymaniu węzła Ignite

Aby uruchomić domyślny węzeł Ignite:

Ignite ignite = Ignition.start();

Lub z pliku konfiguracyjnego:

Ignite ignite = Ignition.start("config/example-cache.xml");

W przypadku, gdy potrzebujemy większej kontroli nad procesem inicjalizacji, istnieje inny sposób za pomocą interfejsu LifecycleBean :

public class CustomLifecycleBean implements LifecycleBean { @Override public void onLifecycleEvent(LifecycleEventType lifecycleEventType) throws IgniteException { if(lifecycleEventType == LifecycleEventType.AFTER_NODE_START) { // ... } } }

Tutaj możemy użyć typów zdarzeń cyklu życia do wykonywania działań przed lub po uruchomieniu / zatrzymaniu węzła.

W tym celu przekazujemy instancję konfiguracyjną z CustomLifecycleBean do metody startowej:

IgniteConfiguration configuration = new IgniteConfiguration(); configuration.setLifecycleBeans(new CustomLifecycleBean()); Ignite ignite = Ignition.start(configuration);

6. Siatka danych w pamięci

Ignite Data Grid to rozproszony magazyn klucz-wartość , bardzo dobrze znany z podzielonej na partycje HashMap . Jest skalowany w poziomie. Oznacza to, że dodajemy więcej węzłów klastra, więcej danych jest buforowanych lub przechowywanych w pamięci.

It can provide significant performance improvement to the 3rd party software, like NoSql, RDMS databases as an additional layer for caching.

6.1. Caching Support

The data access API is based on JCache JSR 107 specification.

As an example, let's create a cache using a template configuration:

IgniteCache cache = ignite.getOrCreateCache( "baeldingCache");

Let's see what's happening here for more details. First, Ignite finds the memory region where the cache stored.

Then, the B+ tree index Page will be located based on the key hash code. If the index exists, a data Page of the corresponding key will be located.

When the index is NULL, the platform creates the new data entry by using the given key.

Next, let's add some Employee objects:

cache.put(1, new Employee(1, "John", true)); cache.put(2, new Employee(2, "Anna", false)); cache.put(3, new Employee(3, "George", true));

Again, the durable memory will look for the memory region where the cache belongs. Based on the cache key, the index page will be located in a B+ tree structure.

When the index page doesn't exist, a new one is requested and added to the tree.

Next, a data page is assigning to the index page.

To read the employee from the cache, we just use the key value:

Employee employee = cache.get(1);

6.2. Streaming Support

In memory data streaming provides an alternative approach for the disk and file system based data processing applications. The Streaming API splits the high load data flow into multiple stages and routes them for processing.

We can modify our example and stream the data from the file. First, we define a data streamer:

IgniteDataStreamer streamer = ignite .dataStreamer(cache.getName());

Next, we can register a stream transformer to mark the received employees as employed:

streamer.receiver(StreamTransformer.from((e, arg) -> { Employee employee = e.getValue(); employee.setEmployed(true); e.setValue(employee); return employee; }));

As a final step, we iterate over the employees.txt file lines and convert them into Java objects:

Path path = Paths.get(IgniteStream.class.getResource("employees.txt") .toURI()); Gson gson = new Gson(); Files.lines(path) .forEach(l -> streamer.addData( employee.getId(), gson.fromJson(l, Employee.class)));

With the use of streamer.addData() put the employee objects into the stream.

7. SQL Support

The platform provides memory-centric, fault-tolerant SQL database.

We can connect either with pure SQL API or with JDBC. SQL syntax here is ANSI-99, so all the standard aggregation functions in the queries, DML, DDL language operations are supported.

7.1. JDBC

To get more practical, let's create a table of employees and add some data to it.

For that purpose, we register a JDBC driver and open a connection as a next step:

Class.forName("org.apache.ignite.IgniteJdbcThinDriver"); Connection conn = DriverManager.getConnection("jdbc:ignite:thin://127.0.0.1/");

With the help of the standard DDL command, we populate the Employee table:

sql.executeUpdate("CREATE TABLE Employee (" + " id LONG PRIMARY KEY, name VARCHAR, isEmployed tinyint(1)) " + " WITH \"template=replicated\"");

After the WITH keyword, we can set the cache configuration template. Here we use the REPLICATED. By default, the template mode is PARTITIONED. To specify the number of copies of the data we can also specify BACKUPS parameter here, which is 0 by default.

Then, let's add up some data by using INSERT DML statement:

PreparedStatement sql = conn.prepareStatement( "INSERT INTO Employee (id, name, isEmployed) VALUES (?, ?, ?)"); sql.setLong(1, 1); sql.setString(2, "James"); sql.setBoolean(3, true); sql.executeUpdate(); // add the rest 

Afterward, we select the records:

ResultSet rs = sql.executeQuery("SELECT e.name, e.isEmployed " + " FROM Employee e " + " WHERE e.isEmployed = TRUE ")

7.2. Query the Objects

It's also possible to perform a query over Java objects stored in the cache. Ignite treats Java object as a separate SQL record:

IgniteCache cache = ignite.cache("baeldungCache"); SqlFieldsQuery sql = new SqlFieldsQuery( "select name from Employee where isEmployed = 'true'"); QueryCursor
    
      cursor = cache.query(sql); for (List row : cursor) { // do something with the row }
    

8. Summary

In this tutorial, we had a quick look at Apache Ignite project. This guide highlights the advantages of the platform over other simial products such as performance gains, durability, lightweight APIs.

W rezultacie nauczyliśmy się, jak używać języka SQL i interfejsu API Java do przechowywania, pobierania, przesyłania strumieniowego danych w siatce trwałości lub w pamięci.

Jak zwykle, pełny kod tego artykułu jest dostępny w serwisie GitHub.