4年前 (2021-03-20)  相关技术 |   抢沙发  1611 
文章评分 0 次,平均分 0.0

内存级高性能分布式数据库Apache Ignite

介绍

Apache Ignite是一个开源的以内存为中心的分布式平台。我们可以将其用作数据库、缓存系统或内存中的数据处理。

该平台使用内存作为存储层,因此具有令人印象深刻的性能。简单地说,这是目前生产中使用的最快的原子数据处理平台之一

官网地址:https://ignite.apache.org/

安装

我们将要构建的应用程序的Maven依赖关系:

<dependency>
    <groupId>org.apache.ignite</groupId>
    <artifactId>ignite-core</artifactId>
    <version>${ignite.version}</version>
</dependency>
<dependency>
    <groupId>org.apache.ignite</groupId>
    <artifactId>ignite-indexing</artifactId>
    <version>${ignite.version}</version>
</dependency>

ignite-core是项目唯一的强制依赖项。由于我们还想与SQL交互,ignite索引也在这里。${ignite.version}是Apache Ignite的最新版本。

最后一步,我们启动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]

上面的控制台输出显示我们已经准备好了。

内存体系结构

该平台基于持久的内存体系结构。这使得可以存储和处理磁盘和内存中的数据。它通过有效地利用集群的RAM资源来提高性能。

内存和磁盘上的数据具有相同的二进制表示。这意味着在从一个层移动到另一层时,数据没有额外的转换。

持久内存体系结构将分解为固定大小的块,称为页面。页面存储在Java堆之外,并在RAM中组织。它有一个唯一的标识符:FullPageId

页面使用PageMemory抽象与内存交互。

它有助于读取、编写页面,也可以分配页面id。在内存中,Ignite与内存缓冲区关联的页面。

内存页

页面可以有以下状态:

  • 已卸载–内存中没有加载页面缓冲区
  • 清除–页面缓冲区已加载并与磁盘上的数据同步
  • Durty–页面缓冲区保存的数据与磁盘中的数据不同
  • 检查点中脏-在第一个修改继续到磁盘之前,还有另一个修改开始。这里开始一个检查点,页面内存为每个页面保留两个内存缓冲区。

持久内存分配一个名为Data Region的本地内存段。默认情况下,它的容量为集群内存的20%。多区域配置允许将可用数据保存在内存中。

区域的最大容量是内存段。它是物理内存或连续字节数组。

为了避免内存碎片,单个页面包含多个键值条目。每个新条目都将添加到最优化的页面。如果键值对大小超过页面的最大容量,Ignite将数据存储在多个页面中。同样的逻辑也适用于更新数据。

SQL和缓存索引存储在称为B+树的结构中。缓存密钥按其键值排序。

生命周期

每个Ignite节点运行在一个JVM实例上。但是,可以配置为在单个JVM进程中运行多个Ignite节点。

让我们看一下生命周期事件类型:

  • BEFORE_NODE_START–在点火节点启动之前
  • AFTER_NODE_START–点火节点启动后立即点火
  • BEFORE_NODE_STOP–在启动节点停止之前
  • AFTER_NODE_STOP–点火节点停止之后

要启动默认Ignite节点,请执行以下操作:

Ignite ignite = Ignition.start();

或从配置文件:

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

如果我们需要对初始化过程进行更多的控制,有另一种方法可以借助LifecycleBean接口:

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

在这里,我们可以使用生命周期事件类型在节点启动/停止之前或之后执行操作。

为此,我们将带有CustomLifecycleBean的配置实例传递给start方法:

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

内存数据网格

Ignite数据网格是一个分布式键值存储,非常熟悉分区HashMap。它是水平缩放的。这意味着我们添加的集群节点越多,缓存或存储在内存中的数据就越多。

它可以为第三方软件(如NoSql、RDMS数据库)提供显著的性能改进,作为缓存的附加层。

缓存支持

数据访问API基于JCache JSR 107规范。

例如,让我们使用模板配置创建缓存:

IgniteCache<Employee, Integer> cache = ignite.getOrCreateCache(
  "baeldingCache");

让我们来看看这里发生了什么。首先,Ignite找到缓存存储的内存区域。

然后,将基于密钥散列码定位B+树索引页。如果索引存在,则会找到相应键的数据页。

当索引为空时,平台使用给定的键创建新的数据条目。

接下来,让我们添加一些Employee对象:

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

同样,持久内存将查找缓存所属的内存区域。基于缓存键,索引页将位于B+树结构中。

当索引页不存在时,会请求一个新的索引页并将其添加到树中。

接下来,将数据页分配给索引页。

要从缓存中读取employee,只需使用键值:

Employee employee = cache.get(1);

Streaming支持

内存数据流为基于磁盘和文件系统的数据处理应用程序提供了一种替代方法。流式API将高负载数据流拆分为多个阶段,并路由它们进行处理。

我们可以修改我们的示例并从文件流式传输数据。首先,我们定义一个数据流:

IgniteDataStreamer<Integer, Employee> streamer = ignite
  .dataStreamer(cache.getName());

接下来,我们可以注册一个流转换器,将接收到的员工标记为已雇用:

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

作为最后一步,我们迭代employees.txt文件行并将其转换为Java对象:

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)));

使用streamer.addData()employee对象放入流中。

SQL支持

该平台提供了以内存为中心的容错SQL数据库

我们可以使用纯SQL API或JDBC进行连接。这里的SQL语法是ANSI-99,因此支持查询、DML、DDL语言操作中的所有标准聚合函数。

JDBC

为了更实用,让我们创建一个雇员表并向其中添加一些数据。

为此,我们注册一个JDBC驱动程序并打开一个连接作为下一步:

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

在标准DDL命令的帮助下,我们填充Employee表:

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

WITH关键字之后,我们可以设置缓存配置模板。这里我们使用复制的。默认情况下,模板模式是分区的。要指定数据的副本数,还可以在此处指定BACKUPS参数,默认值为0。

然后,让我们使用INSERT DML语句添加一些数据:

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

之后,我们选择记录:

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

查询对象

还可以对存储在缓存中的Java对象执行查询。Ignite将Java对象视为单独的SQL记录:

IgniteCache<Integer, Employee> cache = ignite.cache("baeldungCache");

SqlFieldsQuery sql = new SqlFieldsQuery(
  "select name from Employee where isEmployed = 'true'");

QueryCursor<List<?>> cursor = cache.query(sql);

for (List<?> row : cursor) {
    // do something with the row
}

摘要

在本文中,我们快速了解了Apache Ignite项目。本指南强调了该平台相对于其他同类产品的优势,如性能提升、耐用性、轻量级API。

因此,我们学习了如何使用SQL语言和javaapi在持久性网格或内存网格中存储、检索和流化数据。

本文的完整代码GitHub:https://github.com/eugenp/tutorials/tree/master/libraries-data/

 

除特别注明外,本站所有文章均为老K的Java博客原创,转载请注明出处来自https://javakk.com/1736.html

关于

发表评论

表情 格式

暂无评论

登录

忘记密码 ?

切换登录

注册