Thinking In Data我爱编程

(三)apache ignite-架构概述

2018-08-04  本文已影响445人  席梦思

为了更好地理解Apache Ignite和用例的功能,理解它的体系结构和拓扑结构非常重要。通过更好地理解Ignite的体系结构,可以决定拓扑或缓存模式,以解决企业体系结构场景中的不同问题,并从内存计算中获得最大的益处。与主从设计不同,Ignite使用了一个完全对等的架构。ignite集群中的每个节点都可以接受读写,无论数据在哪里写入。本章主要内容:

功能概述

Ignite体系结构具有足够的灵活性和高级特性,可用于大量不同的体系结构模式和风格。您可以将Ignite看作是一组独立的、集成良好的内存中组件的集合,以提高应用程序的性能和可伸缩性。下面的示意图表示Apache Ignite的基本功能:


image.png

Ignite是以模块化的方式进行组织,并为每个功能提供一个jar(库)。您只需将所需的库应用到项目中,就可以使用Ignite。

集群拓扑

Ignite的设计表明整个系统本身就具有固有的可用性和可扩展性。ignite节点间通信允许所有节点接收更新,无需一个快速的主协调器。节点可以不受干扰地添加或删除,以增加可用RAM的数量。
Ignite数据结构具有完全的弹性,允许对单个服务器或多个服务器进行无干扰的自动检测和恢复。

客户端和服务端

Apache Ignite具有一个可选的服务概念,并提供了两种类型的节点:客户端和服务器节点。

ClusterGroup clientGroup = ignite.cluster().forClients(); IgniteCompute clientCompute = ignite.compute(clientGroup);
// Execute computation on the client nodes. clientCompute.broadcast(() -> System.out.println("sum of: " + (2+2)));
image.png

这种方法有一个缺点。使用这种方法,数据将被分配给独立的节点,并且为了计算这些数据,所有的客户端节点都需要从服务器节点检索数据。它可以产生大量的网络连接并产生延迟。但是,您可以在一个主机上单独的JVM上运行客户机和服务器节点,以减少网络延迟。从部署的角度来看,Apache Ignite服务器可以分为以下几个组:

内置应用程序

使用这种方法,Apache Ignite节点与应用程序在同一个JVM上运行。它可以是运行在应用服务器上的任何web应用程序,也可以是独立的Java应用程序。例如,我们的独立HelloIgnite应用程序是来自第一章—是一个嵌入式的Ignite。Ignite服务器与应用程序一起在相同的JVM中运行,并与网格的其他节点连接。如果应用程序宕机或被关闭,则Ignite server也将关闭。拓扑如下图所示:


image.png

服务器在单独的JVM

在这种方法中,服务器节点将在独立的JVM中运行,客户机节点将远程连接到服务器。服务器节点参与缓存、计算执行、流等等。客户端还可以使用REST API连接到任何单独的节点。默认情况下,所有的Ignite节点都作为服务器节点启动;客户端节点需要显式启用。


image.png

这是最常见的方法,因为它在集群机制方面提供了更大的灵活性。Ignite服务器可以被离线并重新启动,不会对整个应用程序或集群产生任何影响。

客户机和服务器分别位于单个主机上的JVM中

当您的数据节点上有大量事务并计划在该节点上执行一些计算时,您可以考虑这种方法。您可以在一个容器(例如Docker或OpenVZ)中单独的JVM中执行客户机和服务器。容器可以位于单个主机中。容器将隔离资源(cpu、ram、网络接口等),JVM只使用分配给这个容器的独立资源。


image.png

这种方法也有它自己的缺点。在执行过程中,客户端(compute)节点可以从驻留在其他主机上的任何其他数据节点检索数据,并且可以增加网络延迟。

缓存拓扑

Ignite提供了三种不同的缓存拓扑的方法::分区,复制和本地。缓存模式分别为每个缓存配置。每个缓存拓扑都有其优缺点。默认缓存拓扑是分区的,没有任何备份选项。

分区缓存拓扑

这种拓扑的目标是获得极好的可扩展性。在这种模式下,Ignite集群透明地对缓存的数据进行分区,以便在整个集群中均匀地分配负载。通过均匀地划分数据,缓存的大小和处理能力随集群的大小呈线性增长。管理数据的职责在整个集群中自动共享。集群中的每个节点或服务器都包含其主数据,如果定义了备份副本,则包含备份副本。


image.png

对于分区缓存拓扑,缓存上的DML操作非常快,因为每个键只需要更新一个主节点(可选为一个或多个备份节点)。对于高可用性,应该配置缓存条目的备份副本。备份副本是一个或多个主副本的冗余副本,该副本将驻留在另一个节点中。有一个简单的公式可以计算,为了实现集群的高可用性,需要多少备份。

备份拷贝数= N-1,其中N为集群中节点的总数

假设集群中总共有3个节点。如果您总是希望从集群获得响应(当某些节点不可用时),备份副本的数量应该不少于2。在这种情况下,存在3个缓存条目副本,2个备份副本和1个主副本。当处理大型数据集时或更新非常频繁,分区是适合的方式。备份过程可以是同步的,也可以是异步的。在同步模式下,客户端应该在完成提交或写入之前等待远程节点的响应。

缓存复制拓扑

这种方法的目标是获得极高的性能。使用这种方法,缓存数据被复制到集群的所有成员。由于数据被复制到每个集群节点,因此可以使用它而无需等待。这为读取提供了可能的最高速度。每个成员都从自己的内存访问数据。缺点是频繁的写操作非常昂贵。更新复制缓存需要将新版本推给所有其他集群成员。如果更新频率很高,这将限制可伸缩性。


image.png

在上图中,相同的数据存储在所有集群节点中;复制缓存的大小受每个节点上可用内存大小的限制。这种模式适用于缓存读取比缓存写入频繁得多,而且数据集很小的场景。复制的可伸缩性与成员数量、每个成员更新的频率和更新的大小成反比。

本地模式

这是缓存模式的一个非常原始的版本;使用这种方法,没有数据分布到集群中的其他节点。就本地缓存而言,没有任何复制或分区过程,数据获取非常便捷和快速。它为最近和经常使用的数据提供零延迟访问。本地缓存主要用于只读操作。它对于读/写传递行为也非常有效,在这种行为中,数据是在缓存丢失时从数据源中加载的。与分布式缓存不同,本地缓存仍然具有分布式缓存的所有特性;它提供查询缓存、自动数据删除等功能。


image.png

缓存策略

随着高交易量的web应用程序和移动应用程序的激增,数据存储已成为性能的主要瓶颈。在大多数情况下,持久性存储(如关系数据库)不能通过添加更多的服务器来完美地扩展。在这种情况下,内存中的分布式缓存为解决数据存储瓶颈提供了一个很好的解决方案。
它扩展了多个服务器(称为网格),将它们的内存集中在一起,并在所有服务器上保持缓存的同步。在分布式内存缓存中有两种主要策略:

Cache-aside

在这种方法中,应用程序负责从持久性存储区进行读写。缓存根本不与数据库交互。这叫做cache-aside。缓存的行为就像一个快速扩展的内存数据存储。应用程序在查询数据存储之前检查缓存中的数据。此外,应用程序在对持久性存储进行任何更改后更新缓存。


image.png

然而,尽管cache-aside的速度非常快,但是这种策略也有一些缺点。如果多个应用程序处理相同的数据存储,应用程序代码可能变得复杂,并可能导致代码重复。当缓存数据丢失时,应用程序将查询数据存储、更新缓存并继续处理。如果不同的应用程序线程同时执行此处理,则可能导致多个数据存储访问。

Read-through and Write-through

这就是应用程序对内存缓存作为主要的数据存储,并读取数据并将数据写入。内存内缓存负责在缓存丢失时将查询传播到数据存储。此外,数据在缓存中更新时将自动更新。所有通读和写操作都将参与整个缓存事务,并作为一个整体提交或回滚。


image.png

Read-through and Write-through比cache-aside有许多优势。首先,它简化了应用程序代码。读入允许缓存在自动过期时从数据库重新加载对象。这意味着您的应用程序不必在高峰时间访问数据库,因为最新的数据总是在缓存中。

Write behind

也可以使用write-behind来获得更好的写性能。Write-behind允许应用程序快速更新缓存并返回。然后,它聚合更新并将其作为批量操作异步刷新到持久性存储中。同样,对于Write-behind,您可以指定节流限制,因此数据库写入速度不如缓存更新快,因此对数据库的压力更小。此外,您可以安排数据库写操作在非高峰时间发生,这可以最小化对数据库的压力。


image.png

Apache Ignite通过实现Java JCache特性提供了上述所有缓存策略。此外,Ignite提供了Ignite Cassandra模块,它通过使用Cassandra作为过期缓存条目的持久存储来实现Ignite缓存的持久性存储。

数据模型

Apache Ignite实现了键值数据模型,特别是JCache (JSR 107)规范。JCache为Java应用程序与缓存交互提供了一种常见的方式。
从设计的角度来看,JCache提供了一个非常简单的键值存储。键值存储是一个简单的Hashtable或Map,主要用于通过主键访问数据库表。您可以将键值作为传统RDBMS中的一个简单表,其中包含两个列,如键和值。值列的数据类型可以是任何原始数据类型,如字符串、整数或任何复杂的Java对象(或Blob - in Oracle术语)。应用程序可以提供一个键和值并将它们持久化。如果键已经存在,将覆盖该值,否则将创建一个新值。为了清晰起见,我们可以将键值存储与Oracle术语进行比较:


image.png

它有非常原始的操作,比如从存储中放置、获取或删除值。因为它总是使用主键访问,所以它们通常有很好的性能和可扩展性。Java缓存API定义了五个核心接口:CachingProvider、CacheManager、Cache、Entry和Expire。

CAP定理

当第一次开始使用Apache Ignite时,想知道Ignite一方面支持ACID事务,另一方面,Ignite也是一个高度可用的分布式系统。在任何NoSQL数据存储中,支持ACID事务并同时提供高可用性都是一个具有挑战性的特性。要横向扩展,需要强大的网络分区容忍度,这需要放弃一致性或可用性。NoSQL系统通常通过放松关系可用性或事务语义来实现这一点。许多流行的NoSQL数据存储(如Cassandra和Riak)仍然没有事务支持,并被归类为AP系统。AP一词来源于著名的CAP定理10,意为可用性和分区容忍性,在NoSQL系统中,这比一致性更重要。


image.png

如上图所见,分布式系统只能具有以下三个属性中的两个:

Clustering

Apache Ignite的设计目标是在集群中跨多个节点处理高工作负载。集群被设计为一组节点。客户端可以向集群中的任何节点发送读/写请求。Ignite节点可以自动发现彼此,并且数据分布在集群中的所有节点上。这有助于在需要时扩展集群,而不需要一次重新启动整个集群。Ignite提供了一种简单的方法来在网格中创建集群节点的逻辑组,并将相关数据配置到类似的节点中,以提高应用程序的性能和可伸缩性。

Cluster group

Ignite ClusterGroup提供了在集群中创建一组逻辑节点的简单方法。按照设计,Ignite集群中的所有节点都是相同的。然而,Ignite允许出于特定目的对任何应用程序的节点进行逻辑分组。例如,您可以将所有节点集中在一起,使用名称myCache服务缓存,或者所有访问缓存myCache的客户端节点。此外,你可能希望仅仅在远程节点上部署服务。你可以限制作业执行、服务部署、消息传递、事件和其他任务只在一些集群组中运行。


image.png

Ignite提供了以下三种方法来在Ignite Grid中创建逻辑集群:

IgniteCluster cluster = ignite.cluster();
//名称为“myCache”缓存数据的所有数据节点。
ClusterGroup dataGroup = cluster.forDataNodes("myCache");
IgniteConfiguration cfg = new IgniteConfiguration();
Map<String, String> attrs = Collections.singletonMap("ROLE", "master");
cfg.setUserAttributes(attrs);
Ignite ignite = Ignition.start(cfg);

在声明节点之后,可以使用属性master对节点进行分组,如下所示:

IgniteCluster cluster = ignite.cluster();
ClusterGroup workerGroup = cluster.forAttribute("ROLE", "master");
Collection<GridNode> workerNodes = workerGroup.nodes();
IgniteCluster cluster = ignite.cluster();
//使用的堆内存小于256MB的节点
ClusterGroup readyNodes = cluster.forPredicate((node) -> node.metrics().getHeapMemoryUsed(\ ) < 256);

Data collocation数据配置

数据配置术语是指将相同的相关数据分配到相同的节点。例如,如果我们有一个缓存为客户属性相关数据和另一个缓存为客户的事务类型的数据。我们可以将他们相关联的数据分配到相同的节点上。在此方法中,相关数据的网络往返次数减少,客户端应用程序可以从单个节点获取数据。在这种情况下,具有相同字段集的多个缓存被分配给相同的节点。


image.png

例如,客户信息及其帐户信息位于同一个Ignite主机上。为了实现这一点,用于缓存客户端对象的缓存键应该有一个带有@AffinityKeyMapped注释的字段或方法,这样为帐户对象的key配置提供value。为了方便,您还可以选择使用AffinityKey类,如下所示:

Object clientKey1 = new AffinityKey("Client1", "accId"); 
Object clientKey2 = new AffinityKey("Client2", "accId ");
Client c1 = new Client (clientKey1, ...); 
Client c2 = new Client (clientKey2, ...);
cache.put("accId ", new Account(“credit card”));  
cache.put(clientKey1, c1); 
cache.put(clientKey2, c2);

要计算关联函数,可以使用任何一组字段,不需要使用任何类型的唯一键。例如,要计算客户端帐户关联函数,可以使用拥有account ID的 client ID。

Compute collocation with Data 数据计算配置

Apache Ignite还提供了将数据计算单元路由到所需数据缓存的节点的能力。这个概念被称为计算和数据的配置。它允许将整个工作单元路由到某个节点。要将计算与数据配置在一起,应该使用IgniteCompute.affinityrun(…)IgniteCompute.affinitycall(…)方法。

image.png

下面是如何在客户信息及其帐户信息分配的同一集群节点上配置计算。

String accId = "acountId";
ignite.compute().affinityRun("myCache", accId, () -> { Account account = cache.get(accId);
Client c1 = cache.get(clientKey1);
Client c2 = cache.get(clientKey2);
... });

计算单元以本地方式访问客户数据,这种方法大大降低了数据在集群中的网络往返,提高了数据处理的性能。
Apache Ignite通过两种关联函数实现:

Zero SPOF 零单点故障

在任何分布式系统中,节点故障应该能够被预判的,特别是当集群的规模增大时。零单点故障(SPOF)设计模式确保系统的单个节点或部分的失效不能阻碍整个集群或系统工作。使用主-从复制或混合主-主系统的系统设计属于这一类。在Hadoop 2.0.0之前,Hadoop NameNode是HDFS集群中的一个SPOF。大多数企业不希望单点失败,原因显而易见。
Apache Ignite作为一个水平可伸缩的分布式系统,其设计方式是使集群中的所有节点都相等,你可以从集群中的任何节点进行读写。在Ignite集群中没有主-从通信。


image.png

数据在集群中备份或复制,因此任何节点的失败都不会导致整个集群或应用程序崩溃。通过这种方式,Ignite提供了一种动态的高可用性。这种方法的另一个好处是可以轻松地添加新节点。当新节点加入集群时,它们可以从现有节点上接管一部分数据。因为所有节点都是相同的,所以这种通信可以在运行的集群中无缝地进行。

在Ignite集群中使用SQL

在Ignite中处理SQL查询有两种主要方法:

多数据中心复制

当前,多数据中心复制是任何数据库的主要需求之一,包括RDBMS和NoSQL。简单地说,多数据中心复制意味着在不同的数据中心之间复制数据。多个数据中心复制可以有几个场景:

异步支持

通常,任何普通的(同步的)put/get或执行调用都会阻塞应用程序的执行,直到从服务器获得结果为止。之后,可以根据客户的需要对结果进行迭代和使用。这可能是处理任何持久性存储的最常见方式。但是,异步方法执行可以提供显著的好处,并且是现代系统的要求。Apache Ignite提供了在所有分布式api上使用异步和同步方法灵活调用的范例。它可以从存储中put/get任何条目,也可以在计算网格中执行作业。Ignite异步方法执行是一个非阻塞操作,并返回一个IgniteFuture对象而不是实际结果。可以通过调用IgniteFuture.get()方法获得结果。以下是一个使用异步调用从Ignite缓存中获取条目的非常简单的例子:

// 获得或者创建缓存
IgniteCache<Integer, String> cache = ignite.getOrCreateCache("testCache"); 
// 得到一个异步的缓存
IgniteCache<Integer, String> asynCache = cache.withAsync();
// 放一些数据进去
for(int i = 1; i <= 100; i++){ 
  cache.put(i, Integer.toString(i));
}
//异步获取第一条记录
asynCache.withAsync().get(1);
// 获得 future promise
IgniteFuture<String> igniteFuture = asynCache.future();
// java 8 lamda expression
igniteFuture.listen(f-> System.out.println("Cache Value:" + f.get()));

在上面的代码中,我们将100个条目同步插入到Ignite缓存testCache中。然后,异步请求第一个条目并获得调用的future。接下来,异步地侦听要完成的操作。注意,Java main()方法的主线程并不等待任务完成,而是将任务交给另一个线程,然后继续。

安全

Apache Ignite是一个开源项目,它不提供任何安全特性。

结论

我们介绍了内存数据网格的基本概念。我们首先简要地描述了Ignite函数概述和具有不同拓扑结构的各种缓存策略。我们还介绍了一些基于标准数据访问模式的技术,如缓存、通读和写入。我们介绍了Ignite数据配置技术,并简要介绍了Ignite键值数据存储。

上一篇 下一篇

猜你喜欢

热点阅读