Ignite的数据网格(重点!!)

1.Data Grid(数据网格)

在集群中复制或分区您的数据。
ignite内存中的数据网格是基于水平扩展的概念和实时添加节点的能力构建的;它被设计成线性扩展到数百个节点,为数据本地化和关联数据路由提供强大的语义,以减少冗余的数据噪声。
ignite数据网格是一个内存分布式key-value存储,他可以被视为一个分布式分区哈希映射,每个集群节点拥有所有数据的一部分。这样,我们添加的集群节点越多,我们可以缓存的数据就越多。
与其他键值存储不同,ignite用可插拔哈希算法来确定数据位置。每个客户端都可以通过将其插入到哈希函数中来确定哪个是key所在的节点,而不需要任何特殊的映射服务器或名称节点。
ignite数据网格支持本地的,重复的,分区的数据集,并允许自由的使用标准的SQL语法在那些数据之间交叉查询。ignite支持用标准SQL来查询内存数据,包括对分布式SQL联接的支持。
ignite数据网格是快速的,他是在今天的集群中,最快速的事务或原子数据实现之一。
PS:Data Consistency(数据一致性)
    只要您的集群还活着,ignite就可以保证不同集群节点之间的数据始终保持一致,无论崩溃或拓扑变化如何。
PS:JCache (JSR 107)
    ignite数据网格实现JCache(JSR 107)规范。

这里写图片描述
特性:
1.Page Memory(内存页,参考我翻译的ignite架构,那里讲的内存页)
2.JCache and Beyond
3.Cache Modes(缓存模式)
4.Primary & Backup Copies
5.Near Caches
6.Cache Queries
7.SQL Queries
8.Continuous Queries
9.ACID Transactions
10.Off-Heap Memory
11.Affinity Collocation
12.Persistent Store
13.Automatic Persistence
14.Data Loading
15.Eviction Policies
16.Expiry Policies
17.Data Rebalancing
18.Web Session Clustering
19.Hibernate L2 Cache
20.JDBC Driver
21.Spring Caching
22.Topology Validation

2.JCache and Beyond

2.1 概述

apache ignite数据网格是一个JCache(JSR 107)规范的实现。JCache提供了非常易用但是功能强大的访问数据的API。然而,该规范故意省略了有关数据分布和一致性的任何细节,从而允许供应商在自己的实现中获得足够的自由。
使用JCache支持,您可以得到以下内容:
>基本的缓存操作
>并发map api
>集中的处理(EntryProcessor)
除了JCache,ignite还提供ACID事务、数据查询功能(包括SQL)、各种内存模型、查询、事务等。

2.2 IgniteCache

IgniteCache接口是一个通往ignite缓存实现的网关。并且提供存储和检索数据的方法,执行查询,包括SQL、迭代和扫描等。
IgniteCache 基于JCcache,所以最基本水平的API可以变成javax.cache.Cache接口。但是IgniteCache API也提供在JCache规范之外提供功能的功能,如数据加载、查询、异步模式等。
你可以通过Ignite实例获取一个IgniteCache实例:
Ignite ignite = Ignition.ignite();

// Obtain instance of cache named "myCache".
// Note that different caches may have different generics.
IgniteCache<Integer, String> cache = ignite.cache("myCache");

2.2.1 Dynamic Cache(动态缓存)

你可以在满足缓存节点过滤器的集群内的服务器节点上创建缓存实例。在启动了动态缓存之后,它也会自动部署到所有匹配缓存节点过滤器的 新加入的 服务器集群成员中。
Ignite ignite = Ignition.ignite();

CacheConfiguration cfg = new CacheConfiguration();

cfg.setName("myCache");
cfg.setAtomicityMode(TRANSACTIONAL);

// Create cache with given name, if it does not exist.
IgniteCache<Integer, String> cache = ignite.getOrCreateCache(cfg);
PS:XML Configuration
    在任何集群成员中在被spring XML配置文件定义了的所有的缓存也将在所有集群服务器上自动创建和部署(不需要在每个集群成员上指定相同的配置)。

2.3 Basic Operations(基本操作)

下面是一些基本的JCache原子操作示例。
//PUT & GET
try (Ignite ignite = Ignition.start("examples/config/example-cache.xml")) {
    IgniteCache<Integer, String> cache = ignite.cache(CACHE_NAME);

    // Store keys in cache (values will end up on different cache nodes).
    for (int i = 0; i < 10; i++)
        cache.put(i, Integer.toString(i));

    for (int i = 0; i < 10; i++)
        System.out.println("Got [key=" + i + ", val=" + cache.get(i) + ']');
}
//ATOMIC操作
// Put-if-absent which returns previous value.
Integer oldVal = cache.getAndPutIfAbsent("Hello", 11);

// Put-if-absent which returns boolean success flag.
boolean success = cache.putIfAbsent("World", 22);

// Replace-if-exists operation (opposite of getAndPutIfAbsent), returns previous value.
oldVal = cache.getAndReplace("Hello", 11);

// Replace-if-exists operation (opposite of putIfAbsent), returns boolean success flag.
success = cache.replace("World", 22);

// Replace-if-matches operation.
success = cache.replace("World", 2, 22);

// Remove-if-matches operation.
success = cache.remove("Hello", 1);
PS:Deadlock死锁
如果批处理操作(如IgniteCache#putAll、IgniteCache#invokeAll等)都并行执行,那么应该以按同样的方式排序,以避免死锁。建议使用TreeMap而不是HashMap来保证一致的排序。请注意,这对ATOMIC和TRANSACTIONAL缓存都是正确的。

2.4 EntryProcessor

无论在缓存中操作puts还是updates,您通常通过网络发送完整的对象状态。EntryProcessor 允许直接在主节点上处理数据。通常只传送δ函数而不是整个状态。
此外,您可以将自己的逻辑嵌入到EntryProcessor中,例如,使用之前缓存的值并将其增加1
IgniteCache<String, Integer> cache = ignite.cache("mycache");

// Increment cache value 10 times.
for (int i = 0; i < 10; i++)
  cache.invoke("mykey", (entry, args) -> {
    Integer val = entry.getValue();

    entry.setValue(val == null ? 1 : val + 1);

    return null;
  });
PS:原子性:
    在给定的缓存的键的锁中,EntryProcessors  可以原子的执行。

2.5 Asynchronous Support(现在已经不支持了)

就像所有在ignite的分布式api,IgniteCache继承自IgniteAsynchronousSupport接口,可用于异步模式。
// Enable asynchronous mode.
IgniteCache<String, Integer> asyncCache = ignite.cache("mycache").withAsync();

// Asynhronously store value in cache.
asyncCache.getAndPut("1", 1);

// Get future for the above invocation.
IgniteFuture<Integer> fut = asyncCache.future();

// Asynchronously listen for the operation to complete.
fut.listenAsync(f -> System.out.println("Previous cache value: " + f.get()));

3. Cache Modes(缓存模式)

设置不同的分布模型、备份副本和近缓存

3.1 概述

点火提供三种不同的缓存操作模式:PARTITIONED、REPLICATED和LOCAL。为每个缓存配置缓存模式。缓存模式定义在CacheMode枚举中。

3.2 Partitioned Mode(分区模式)

分区模式是最具有可伸缩性的分布式缓存模式。在这种模式下,整个数据集平均分割到分区中,并且所有分区在参与节点之间平均分配,本质上为缓存数据而创建了一个大的分布式内存存储系统。这种方式允许你可以存储像你所有节点的总内存那样大的数据,因此,允许在所有集群节点上的缓存内存中有多兆兆字节的数据。本质上,节点越多,缓存的数据就越多。
不像REPLICATED (复制)模式那样,在复制模式下,由于集群中的每个节点都需要更新,因此更新是昂贵的,但是在分区模式下不需要,因此更新变得很便宜,因为只有一个主节点(以及可选的一个或多个备份节点)需要更新每个键。但是,读取变得更加昂贵,因为只有某些节点具有缓存的数据。
为了避免额外的数据移动,必须始终访问到恰恰有这个数据缓存的节点上的数据。这种方法称为affinity colocation(亲和性colocation),强烈推荐使用分区缓存。
PS:
    分区缓存在处理大型数据集和更新时非常理想。
下面的图展示了分区缓存的一个简单视图。本质上,我们有一个分配给在JVM1上运行的节点的key A,分配给在JVM3运行的节点上的key B,等等

这里写图片描述

有关如何配置缓存模式的示例,请参见下面的配置部分。

3.3 Replicated Mode(复制模式)

在复制模式中,所有数据都复制到集群中的每个节点。这种高速缓存模式提供了最大的数据可用性,因为它可以在每个节点上使用。但是,在这种模式下,每个数据更新必须传播到所有其他节点,这些节点可能对性能和可伸缩性产生影响。
在ignite中,复制的缓存是使用分区缓存实现的,其中每个key都有一个主副本,并且备份在集群中的所有其他节点上。例如,在下面的图中,在JVM1中运行的节点是key a的一个主节点,但是它也为所有其他键存储备份副本(B、C、D)。

这里写图片描述

由于相同的数据存储在所有的集群节点上,复制缓存的大小受节点上可用内存的大小限制。这种模式适合于缓存读取比缓存写的频繁,而且数据集很小的场景。如果您的系统在80%的时间内缓存查找,那么您应该考虑使用复制缓存模式。
PS:
    当数据集小且更新不频繁时,应该使用复制的缓存。

3.4 Local Mode(本地模式)

本地模式是最轻量的缓存操作模式,因为没有数据被分配到其他缓存节点。对于数据是只读的,或者可以以一些特定的频率定期的更新的场景来说,它是理想的。它也可以很好地与read-through工作,其中数据从持久存储中被加载到遗漏。除了分布之外,本地缓存仍然具有分布式缓存的所有特性,如自动数据清除、过期、磁盘交换、数据查询和事务。

3.5 Configuration

为每个缓存配置缓存模式,通过设置像这样的CacheConfiguration 的cacheMode属性:
<bean class="org.apache.ignite.configuration.IgniteConfiguration">
    ...
    <property name="cacheConfiguration">
        <bean class="org.apache.ignite.configuration.CacheConfiguration">
            <!-- Set a cache name. -->
            <property name="name" value="cacheName"/>

            <!-- Set cache mode. -->
                    <property name="cacheMode" value="PARTITIONED"/>
                    ... 
        </bean>
    </property>
</bean>

3.6 Atomic Write Order Mode(注意:2.0已经没有了)

当在CacheAtomicityMode.ATOMIC模式下使用分区缓存的时候,一个可以配置原子缓存写命令模式。原子写顺序模式决定哪一个节点将分配写版本(发送者或主节点)和由CacheAtomicWriteOrderMode枚举定义。有两种模式,CLOCK 和PRIMARY模式
在CLOCK顺序写模式下,写的版本被分配在发送器节点上。CLOCK模式只有在CacheWriteSynchronizationMode.FULL_SYNC被使用的时候会自动启动,因为它通常会导致更好的性能,因为写请求到主节点和备份节点是同时发送的。
在PRIMARY 顺序写模式下,缓存版本只分配在主节点上。在这种模式下,发送方只会向主节点发送写请求,而主节点则会分配写版本并将其转发给备份。
原子顺序写模式可以通过CacheConfiguration的atomicWriteOrderMode属性进行配置:
<bean class="org.apache.ignite.configuration.IgniteConfiguration">
    ...
    <property name="cacheConfiguration">
        <bean class="org.apache.ignite.configuration.CacheConfiguration">
            <!-- Set a cache name. -->
            <property name="name" value="cacheName"/>

            <!-- Atomic write order mode. -->
                    <property name="atomicWriteOrderMode" value="PRIMARY"/>
                    ... 
        </bean>
    </property>
</bean>
有关原子模式的更多信息,请参考事务部分。
<bean class="org.apache.ignite.configuration.IgniteConfiguration">
    ...
    <property name="cacheConfiguration">
        <bean class="org.apache.ignite.configuration.CacheConfiguration">
            <!-- Set a cache name. -->
            <property name="name" value="cacheName"/>

            <!-- Set cache mode. -->
                    <property name="cacheMode" value="PARTITIONED"/>

            <!-- Number of backup nodes. -->
                    <property name="backups" value="1"/>
                    ... 
        </bean>
    </property>
</bean>

4. Near Caches

创建本地客户端缓存。
分区缓存也可以被一个near缓存所支持,这是一个较小的本地缓存,存储最近或最频繁访问的数据。就像分区缓存一样,用户可以控near缓存的大小及其回收策略。
near缓存可以在客户端节点上直接创建,创建的方式是将NearCacheConfiguration 传给Ignite.createNearCache(String cacheName, NearCacheConfiguration<K, V> nearCfg),或者Ignite.getOrCreateNearCache(String cacheName, NearCacheConfiguration<K, V> nearCfg)节点。如果你步进需要创建一个分布式的缓存,并想为它创建一个near-cache,那么就使用Ignite.getOrCreateCache(CacheConfiguration, NearCacheConfiguration)方法。
// Create near-cache configuration for "myCache".
NearCacheConfiguration<Integer, Integer> nearCfg = 
    new NearCacheConfiguration<>();

// Use LRU eviction policy to automatically evict entries
// from near-cache, whenever it reaches 100_000 in size.
nearCfg.setNearEvictionPolicy(new LruEvictionPolicy<>(100_000));

// Create a distributed cache on server nodes and 
// a near cache on the local node, named "myCache".
IgniteCache<Integer, Integer> cache = ignite.getOrCreateCache(
    new CacheConfiguration<Integer, Integer>("myCache"), nearCfg);
在绝大多数的用例中,无论何时使用affinity colocation(客观翻译就是亲和力的搭配,它的功能就是将相关联的数据,都放在一个数据节点上)了的ignite,都不应该再使用near缓存。如果计算与相应的分区缓存节点相一致,则不需要近缓存,因为所有数据都在分区缓存中可用。
然而,也有一些情况是不可能向远程节点发送计算的。对于这样的情况,类似的缓存可以显著提高可伸缩性和应用程序的整体性能。
PS:Transactions
    near缓存是完全事务性的,只要服务器上的数据发生变化,就会自动更新或失效。
PS:Near Caches on Server Nodes
    当访问数据从分区缓存的服务器端,您可能需要在服务器通过CacheConfiguration.setNearConfiguration(...)的属性来配置near缓存。

4.1 配置

在CacheConfiguration 上可用的大多数配置参数都是从服务器配置继承而来的。例如,如果服务器缓存有一个ExpiryPolicy(过期策略),那么near缓存中的条目将基于相同的策略过期。
下面的表中列出的参数不是从服务器配置继承而来的,而是通过NearCacheConfiguration 对象单独提供的。
方法描述默认值
setNearEvictionPolicy(CacheEvictionPolicy)近缓存的收回策略None
setNearStartSize(int)near缓存的满足清除的数据的多少。375,000

5. Primary & Backup Copies(主/备复制)

5.1 概述

在PARTITIONED(分片)模式下,分配键的节点称为这些键的主节点。因此,您可以选择配置任意数量的备份节点缓存数据。如果备份的数量大于 0,然后ignite将自动分配为每个单独的键的备份节点。例如,如果备份数目是 1,那么每个键都缓存在数据网格中将有 2 份、 1 份主和 1 份备份。
默认情况下,对备份进行关闭以获得更好的性能。

5.2 Configuring Backups(配置备份)

可以通过设置CacheConfiguration的backups()属性来配置备份:
<bean class="org.apache.ignite.configuration.IgniteConfiguration">
    ...
    <property name="cacheConfiguration">
        <bean class="org.apache.ignite.configuration.CacheConfiguration">
            <!-- Set a cache name. -->
            <property name="name" value="cacheName"/>

            <!-- Set cache mode. -->
                    <property name="cacheMode" value="PARTITIONED"/>

            <!-- Number of backup nodes. -->
                    <property name="backups" value="1"/>
                    ... 
        </bean>
    </property>
</bean>
CacheConfiguration cacheCfg = new CacheConfiguration();

cacheCfg.setName("cacheName");

cacheCfg.setCacheMode(CacheMode.PARTITIONED);

cacheCfg.setBackups(1);

IgniteConfiguration cfg = new IgniteConfiguration();

cfg.setCacheConfiguration(cacheCfg);

// Start Ignite node.
Ignition.start(cfg);

5.3 Synchronous and Asynchronous Backups(同步和异步备份)

CacheWriteSynchronizationMode 这个enum可以被用于配置主和从节点是使用同步还是异步备份。编写同步模式的话,在完成写或提交之前,客户端都应该等待远程节点的响应。
写同步模式可以设置为以下三种模式:
序号模式描述
1FULL_SYNC客户端节点将等待写或提交完成所有参与的远程节点(主和备份)。
2FULL_ASYNC在这种模式下,客户端节点不等待参与节点的响应,在这种情况下,远程节点可能会在任何缓存写方法完成后或在transaction . commit()方法完成后,稍微更新状态。
3PRIMARY_SYNC这是默认模式。客户端节点将等待在主节点上完成写入或提交,但不会等待更新的备份。

PS:Cache Data Consistency
请注意,无论写同步模式下,缓存数据将总是跨所有参与节点保持完全一致。
写入同步模式可以通过设置cacheconfig化的writeSynchronizationMode属性来配置,例如:

<bean class="org.apache.ignite.configuration.IgniteConfiguration">
    ...
    <property name="cacheConfiguration">
        <bean class="org.apache.ignite.configuration.CacheConfiguration">
            <!-- Set a cache name. -->
            <property name="name" value="cacheName"/>

            <!-- Set write synchronization mode. -->
                    <property name="writeSynchronizationMode" value="FULL_SYNC"/>       
                    ... 
        </bean>
    </property>
</bean>
CacheConfiguration cacheCfg = new CacheConfiguration();

cacheCfg.setName("cacheName");

cacheCfg.setWriteSynchronizationMode(CacheWriteSynchronizationMode.FULL_SYNC);

IgniteConfiguration cfg = new IgniteConfiguration();

cfg.setCacheConfiguration(cacheCfg);

// Start Ignite node.
Ignition.start(cfg);

6. Affinity Collocation(Affinity并置)

将数据与计算并置或者将数据与数据并置,,来提升你的系统的性能和可拓展性。

6.1 Collocate Data with Data

在许多情况下,如果不同的缓存一起被访问,那么将其组合在一起,则是有益的。您的业务逻辑会经常需要访问多个缓存键。通过将它们组合在一起,可以确保使用相同的affinityKey的所有键都被缓存到相同的处理节点上,从而避免昂贵的网络访问,以从远程节点获取数据。
举个栗子,假设你有一个Person和Conpany对象,你想搭配Person对象和这个Person对象工作所属于的Company。为了实现这样的情况,用于缓存Person对象的缓存键,应该有一个注解了@AffinityKeyMapped的属性或者方法,这个属性/方法为排列搭配提供了company键的值。为了方便,还可以选择使用AffinityKey类
PS:Annotations in Scala
Note that if Scala case class is used as a key class and one of its constructor parameters is annotated with @AffinityKeyMapped, by default the annotation will not be properly applied to the generated field, and therefore will not be recognized by Ignite. To override this behavior, use @field meta annotation in addition to @AffinityKeyMapped (see example below).
//使用personKey
public class PersonKey {
    // Person ID used to identify a person.
    private String personId;

    // Company ID which will be used for affinity.
    @AffinityKeyMapped
    private String companyId;
    ...
}

// Instantiate person keys with the same company ID which is used as affinity key.
Object personKey1 = new PersonKey("myPersonId1", "myCompanyId");
Object personKey2 = new PersonKey("myPersonId2", "myCompanyId");

Person p1 = new Person(personKey1, ...);
Person p2 = new Person(personKey2, ...);

// Both, the company and the person objects will be cached on the same node.
cache.put("myCompanyId", new Company(...));
cache.put(personKey1, p1);
cache.put(personKey2, p2);
//scala版本
case class PersonKey (
    // Person ID used to identify a person.
    personId: String,

    // Company ID which will be used for affinity.
    @(AffinityKeyMapped @field)
    companyId: String
)

// Instantiate person keys with the same company ID which is used as affinity key.
val personKey1 = PersonKey("myPersonId1", "myCompanyId");
val personKey2 = PersonKey("myPersonId2", "myCompanyId");

val p1 = new Person(personKey1, ...);
val p2 = new Person(personKey2, ...);

// Both, the company and the person objects will be cached on the same node.
compCache.put("myCompanyId", Company(...));
perCache.put(personKey1, p1);
perCache.put(personKey2, p2);
//使用 AffinityKey的版本
Object personKey1 = new AffinityKey("myPersonId1", "myCompanyId");
Object personKey2 = new AffinityKey("myPersonId2", "myCompanyId");

Person p1 = new Person(personKey1, ...);
Person p2 = new Person(personKey2, ...);

// Both, the company and the person objects will be cached on the same node.
cache.put("myCompanyId", new Company(..));
cache.put(personKey1, p1);
cache.put(personKey2, p2);
以这种方式缓存的数据,person和company会在一个数据节点上
PS:SQL join
    在对驻留在分区缓存中的数据进行SQL分布式连接时,必须确保连接键是想上述的那样被分配过了的。

6.2 Collocating Compute with Data

还可以将计算路由到缓存数据的节点。这个概念被称为计算和数据的搭配,他允许你路由整个工作单元到特定的节点上。
排列搭配计算和数据,你需要使用IgniteCompute.affinityRun(...)和IgniteCompute.affinityCall(...)方法。
下面是如何用相同的集群节点将您的计算组合在一起,上面的示例中的公司和人员被缓存。
//java1.8版本
String companyId = "myCompanyId";

// Execute Runnable on the node where the key is cached.
ignite.compute().affinityRun("myCache", companyId, () -> {
  Company company = cache.get(companyId);

  // Since we collocated persons with the company in the above example,
  // access to the persons objects is local.
  Person person1 = cache.get(personKey1);
  Person person2 = cache.get(personKey2);
  ...  
});
//``java1.7版本
final String companyId = "myCompanyId";

// Execute Runnable on the node where the key is cached.
ignite.compute().affinityRun("myCache", companyId, new IgniteRunnable() {
  @Override public void run() {
    Company company = cache.get(companyId);

    Person person1 = cache.get(personKey1);
    Person person2 = cache.get(personKey2);
    ...
  }
};

6.3 IgniteCompute vs EntryProcessor

IgniteCompute.affinityRun(...)和IgniteCache.invoke(...)方法提供了排列搭配计算和数据的能力。最主要的区别是invoke(...)方法是原子的,并且在执行的时候会拿着缓存键的锁。在EntryProcessor 逻辑内,你不应该再访问其他的键,否则容易导致死锁。
affinityRun(...)和affinityCall(...),另一方面,他不会持有缓存键的锁。例如:从这些方法启动多个事务或执行缓存查询,而不必担心死锁,这是绝对合法的。在这种情况下。ignite将会自动的侦测到这个处理是排列搭配的,将使用一个最优的轻量级的1-Phase-Commit(一阶段提交)来做事务处理(提点 2-Phase-Commit)。
你可以去参考JCache EntryProcessor 的文档来更深入的了解IgniteCache.invoke(...)方法。

6.4 Affinity Function

一个分区的亲和力控制着哪个网格节点或一组节点将被缓存。AffinityFunction 是一个可插拔的API,被用于确定网格中对节点分区的一个理想映射。在网格中。当集群拓扑改变时,分区-节点映射可能与affinity function 提供的理想分布不同,直到重新平衡完成为止。
ignite是附带RendezvousAffinityFunction ,该类允许在分区-节点的映射有些许差异(有些节点可能比其他节点有更大的分区数)。但是,它保证了当拓扑发生变化时,分区只会迁移到一个联接节点或仅从左节点迁移。在集群中的现有节点之间不会发生数据交换。
注意,cache affinity function不直接映射键到节点上,它映射键到分区。分区仅仅是一个从有限集合(默认值为0到1024)的数字。在将缓存键映射到它们的分区(也就是说,它们得到分区号)之后,当前的拓扑版本将使用现有的分区-节点映射。键-分区映射不能在时间上发生变化。
下面的代码片段展示了如何自定义和设置Affinity Function:
<bean class="org.apache.ignite.configuration.IgniteConfiguration">
    <property name="cacheConfiguration">
    <list>
        <!-- Creating a cache configuration. -->
      <bean class="org.apache.ignite.configuration.CacheConfiguration">
        <property name="name" value="myCache"/>

        <!-- Creating the affinity function with custom setting. -->
        <property name="affinity">
            <bean         class="org.apache.ignite.cache.affinity.rendezvous.RendezvousAffinityFunction">
                <property name="excludeNeighbors" value="true"/>
            <property name="partitions" value="2048"/>
          </bean>
        </property>
      </bean>
    </list>
  </property>
</bean>
// Preparing Apache Ignite node configuration.
IgniteConfiguration cfg = new IgniteConfiguration();

// Creating a cache configuration.
CacheConfiguration cacheCfg = new CacheConfiguration("myCache");

// Creating the affinity function with custom setting.
RendezvousAffinityFunction affFunc = new RendezvousAffinityFunction();

affFunc.setExcludeNeighbors(true);

affFunc.setPartitions(2048);

// Applying the affinity function configuration.
cacheCfg.setAffinity(affFunc);

// Setting the cache configuration.
cfg.setCacheConfiguration(cacheCfg);
PS:Crash-safe Affinity
    在集群中安排分区是很有用的,这样主和备份副本就不会位于同一物理机器上。为了确保这个属性,用户可以设置在RendezvousAffinityFunction 的excludeNeighbors标识。
    有时在不同的机架上有一个分区的主备份副本也是很有用的。在这种情况下,用户可以为每个节点分配一个特定的属性,然后使用RendezvousAffinityFunction 的AffinityBackupFilter 属性排除来自相同机架的节点,这些节点是用于备份复制作业的候选者。

AffinityFunction 是一个可插拔的API,用户可以提供自己的实现,而AffinityFunction的3个主要方法:
>partitions:获取缓存的总分区书。集群在运行时不能更改。
>partition(...):给定一个键,这个方法决定一个键属于哪个分区。映射不能随时间变化。
>assignPartitions(...):这种方法在每次集群拓扑变化时被调用。该方法为给定的集群拓扑返回一个分区到节点的映射。

6.5 Affinity Key Mapper

AffinityKeyMapper 是一个可插入的API,负责为缓存键获取关联键。通常缓存键本身是用于关联的,然而有时改变缓存键的关联键,这是非常重要的,以便将它与其他缓存键组合在一起。
AffinityKeyMapper 接口的最重要的方法是affinityKey(key),它将返回一个缓存键的affinityKey ,ignite将查找任何带有@ cacheaffinitykeymapped注释的字段或方法。如果没有找到这样的字段或方法,则将缓存键本身用于affinity(关联)。如果找到这样的字段或方法,那么这个字段或者方法的的值将由AffinityKeyMapper.affinityKey(key)方法返回。这就允许你无论什么时候,除了缓存键自身之外,可以指定一个可替换的关联的键。

7. Memory & Cache Metrics

7.1 Memory Metrics(内存度量)

Apache ignite的虚拟内存可以通过使用MemoryMetrics接口和JMX bean公开的几个参数进行分析。访问内存指标可以帮助跟踪总体内存利用率,度量其性能,如果发现瓶颈,则执行所需的优化。
MemoryMetrics是提供特定Apache ignite节点的内存页的相关度量的主要入口点。由于可以在节点上配置多个内存区域,因此每个区域的指标都是通过这个API单独收集和获得的。
目前,MemoryMetrics接口支持以下方法:
方法名方法描述
getName()返回度量信息所属的内存区域的名称。
getTotalAllocatedPages()获取内存区域中已分配内存页的总数。
getAllocationRate()在这个内存区域中获取页面分配速率。
getEvictionRate()在给定的内存区域中获取页面回收率
getLargeEntriesPagesPercentage()获取页面大小超过页面大小的页面的百分比。大型实体被分割成碎片,每一个片段都可以放入一个内存页中。
getPagesFillFactor()获得仍然是自由的,并且可以被填充的空间百分比。

调用Ignite.memoryMetrics()方法获取最新的度量快照并对其进行迭代,如下面的示例所示:

// Get the metrics of all the memory regions defined on the node.
Collection<MemoryMetrics> regionsMetrics = ignite.memoryMetrics();

// Print some of the metrics' probes for all the regions.
for (MemoryMetrics metrics : regionsMetrics) {
  System.out.println(">>> Memory Region Name: " + metrics.getName());
  System.out.println(">>> Allocation Rate: " + metrics.getAllocationRate());
  System.out.println(">>> Fill Factor: " + metrics.getPagesFillFactor());
}
PS:Enabling Metrics Gathering(开启度量信息收集)
    内存指标集合不是一个免费操作,可能会影响应用程序的性能。因此,默认情况下,内存度量收集是关闭的。
    要将度量标准打开,请使用以下方法之一:
    1.为您想要收集的每个内存区域,设置MemoryPolicyConfiguration.setMetricsEnabled(boolean)为true。
    2.由下面文档会讲到的JMX bean的MemoryMetricsMXBean.enableMetrics()
或者,可以使用MemoryMetricsMXBean接口来观察virtaul内存状态。您可以从任何jmx兼容的工具或API连接到bean。
JMX bean公开了MemoryMetrics接口所包含的相同的度量集,以及下面列出的一些额外的指标。
方法名方法描述
getInitialSize()获取内存策略配置定义的初始内存区域大小。
getMaxSize()获取内存策略配置定义的最大内存区域大小。
getSwapFilePath()获取内存-映射文件的路径,如果有的话,内存区域将被映射到哪里。
enableMetrics()允许在Apache ignite节点上收集特定内存区域的内存指标
disableMetrics()禁用用于特定内存区域的Apache点火节点上的内存度量。
rateTimeInterval(int)为页面分配和回收率监控设置一个时间间隔。例如,在设置间隔为60秒之后,对getAllocationRate()的后续调用将在最后一分钟返回平均分配速率(每秒页面数)。
subIntervals(int)设置一些子间隔,整个rateTimeInterval(int)将被拆分为计算页配置和回收率率(默认情况下是5)。将此参数设置为更大的值将导致更精确的计算。

这些特定于bean的方法中的一些将在未来的版本中添加到MemoryMetrics接口中。

7.2 Cache Metrics

除了上面解释的低级内存相关的度量,Apache ignite还允许通过CacheMetrics 接口关注分布式缓存的度量信息。
CacheMetrics接口有各种度量标准,如:一个缓存的总数量,以及由缓存处理的操作、平均的put或get时间、被驱逐的总数、当前的缓存存储缓冲区大小以及更多。请参考CacheMetrics javadoc查看可用的所有指标的完整列表。
有几种方法可以获取特定缓存的最新数据快照:
1.IgniteCache.metrics():获取部署缓存的整个集群的度量快照
2.IgniteCache.metrics(ClusterGroup grp):获取属于给定集群组的Apache点火节点的度量快照。
3.IgniteCache.localMetrics():获取缓存的本地节点的度量快照。
另外,您还可以通过CacheMetricsMXBean接口访问缓存度量指标。您可以从任何jmx兼容的工具或API连接到bean。如果您需要使用您的应用程序中的bean,请使用IgniteCache.mxBean()或IgniteCache.localMxBean()来获取bean引用。
PS:Enabling Cache Metrics
    若要启用缓存度量收集,请将 CacheConfiguration.setStatisticsEnabled(boolean) 设置为您想要收集的度量值的每个缓存 true。

8. Eviction Policies(回收策略)

8.1 概述

Apache ignite支持两种不同的数据清除策略:
1.page-based eviction,该方式处理栈外内存页
2.cache entries based eviction:该方式处理栈内内存页

8.2 Page-Based Eviction

该回收策略是配置MemoryPolicyConfiguration来做的,我们在Ignite架构篇有讲。虚拟内存是由一个或者多个的由MemoryPolicyConfigurations所配置的内存区域所组成的。默认情况下,一个区域不断的增长它的大小,知道到达我们所配置的最大内存为止。为了避免可能的区域耗竭,您可能需要设置一个数据页清除模式--通过设置MemoryPolicyConfiguration.setPageEvictionMode(...)的参数,或者是random-LRU,或者是Random-2-LRU。回收策略会追踪数据页的使用,根据模式的实现将其中一些删除

8.2.1 Random-LRU

要弃用random-LRU回收算法,那么需要将DataPageEvictionMode.RANDOM_LRU传递给各自的MemoryPolicyConfiguration,如下面的例子所示:
<bean class="org.apache.ignite.configuration.MemoryConfiguration">
  <!-- Defining additional memory poolicies. -->
  <property name="memoryPolicies">
    <list>
      <!--
          Defining a policy for 20 GB memory region with RANDOM_LRU eviction.
      -->
      <bean class="org.apache.ignite.configuration.MemoryPolicyConfiguration">
        <property name="name" value="20GB_Region_Eviction"/>
        <!-- Initial size is 5 GB. -->
        <property name="initialSize" value="#{5 * 1024 * 1024 * 1024}"/>
        <!-- Maximum size is 20 GB. -->
        <property name="maxSize" value="#{20 * 1024 * 1024 * 1024}"/>
        <!-- Enabling RANDOM_LRU eviction. -->
        <property name="pageEvictionMode" value="RANDOM_LRU"/>
      </bean>
    </list>
    ...
  </property>
  ...
</bean>
// Defining additional memory poolicies.
MemoryConfiguration memCfg = new MemoryConfiguration();

// Defining a policy for 20 GB memory region with RANDOM_LRU eviction.
MemoryPolicyConfiguration memPlc = new MemoryPolicyConfiguration();

memPlc.setName("20GB_Region_Eviction");

// Initial size is 5 GB.
memPlc.setInitialSize(5L * 1024 * 1024 * 1024);

// Maximum size is 5 GB.
memPlc.setMaxSize(20L * 1024 * 1024 * 1024);

// Enabling RANDOM_LRU eviction.
memPlc.setPageEvictionMode(DataPageEvictionMode.RANDOM_LRU);

// Setting the new memory policy.
memCfg.setMemoryPolicies(memPlc);
Random-LRU算法工作如下:
1.一旦配置了内存策略定义的内存区域,就会分配一个非堆数组来跟踪每个单独的数据页的“最后一次使用”时间戳。
2.当访问数据页时,它的时间戳会在跟踪数组中更新。
3.当需要将内存页回收时,该算法从跟踪数组中随机选择5个索引,根据距离当前最近的时间戳将内存页进行回收。如果有的索引指向了非数据页(即索引页或者系统页),那么算法就跳向另外一个页继续算法操作。

8.2.2 Random-2-LRU

该算法是一个scan-resistant版本的Random-LRU, 如果想启用它,那么就需要将DataPageEvictionMode.RANDOM_2_LRU传递给MemoryPolicyConfiguration,如下面的例子所示:
<bean class="org.apache.ignite.configuration.MemoryConfiguration">
  <!-- Defining additional memory poolicies. -->
  <property name="memoryPolicies">
    <list>
      <!--
          Defining a policy for 20 GB memory region with RANDOM_2_LRU eviction.
      -->
      <bean class="org.apache.ignite.configuration.MemoryPolicyConfiguration">
        <property name="name" value="20GB_Region_Eviction"/>
        <!-- Initial size is 5 GB. -->
        <property name="initialSize" value="#{5 * 1024 * 1024 * 1024}"/>
        <!-- Maximum size is 20 GB. -->
        <property name="maxSize" value="#{20 * 1024 * 1024 * 1024}"/>
        <!-- Enabling RANDOM_2_LRU eviction. -->
        <property name="pageEvictionMode" value="RANDOM_2_LRU"/>
      </bean>
    </list>
    ...
  </property>
  ...
</bean>
// Defining additional memory poolicies.
MemoryConfiguration memCfg = new MemoryConfiguration();

// Defining a policy for 20 GB memory region with RANDOM_LRU eviction.
MemoryPolicyConfiguration memPlc = new MemoryPolicyConfiguration();

memPlc.setName("20GB_Region_Eviction");

// Initial size is 5 GB.
memPlc.setInitialSize(5L * 1024 * 1024 * 1024);

// Maximum size is 5 GB.
memPlc.setMaxSize(20L * 1024 * 1024 * 1024);

// Enabling RANDOM_2_LRU eviction.
memPlc.setPageEvictionMode(DataPageEvictionMode.RANDOM_2_LRU);

// Setting the new memory policy.
memCfg.setMemoryPolicies(memPlc);
在Random-2-LRU 中,最近的两个访问时间戳存储在每个数据页中。当被回收的时候,该算法从跟踪数组中随机选择5个索引,并在两个最新的时间戳中进行最小值,以进一步与被选为回收候选的其他4个页面的最小值进行比较。
在处理one-hit wonder问题时候,Random-2-LRU优于LRU算法,one-hit-wonder问题就是:如果一个数据页面很少被访问,但偶尔访问一次,它就会被长期保护。
PS:Random-LRU vs. Random-2-LRU
    在Random-LRU中,最近的时间戳保存在数据页里。但是在Random-2-LRU,则是保存最近的两次访问的时间戳。
PS:Eviction Triggering
    默认情况下,当总内存区域的消耗达到90%时,将触发数据页面的驱逐算法。使用MemoryPolicyConfiguration.setEvictionThreshold参数(…)如果您需要启动回收的事件,是早点还是晚点。

8.3 On-Heap Cache Entries Based Eviction

如果通过 CacheConfiguration.setOnheapCacheEnabled(...)设置启动了on-heap缓存特性,那么ignite的内存是允许你在Java堆中存储热数据的。一旦 on-heap 缓存开启了,您可以使用一个缓存条目回收策略来管理不断增长的堆缓存。
回收策略控制可以存储在缓存的堆内存中的元素的最大数量。当到达最大的堆缓存大小时,将从Java堆中逐出条目。
PS:清除策略只从Java堆中删除缓存条目。存储在非堆页内存中的条目保持不变。
一些驱逐策略支持通过内存大小限制进行批量回收。如果启动了批量回收策略,那么当缓存大小变成了batchSize的那个数值的时候,就会启动回收。如果根据内存大小限制的驱逐策略被启动了,那么当缓存条目超了这个数值时候,内存回收旧会开始。
PS:只有在不设置最大内存限制的情况下,才支持批量清除。
在Apache回收策略是可插拔的,并通过EvictionPolicy接口控制。每个缓存更改都通知回收策略的实现,并定义了从页面内存的堆缓存中选择要回收的条目的算法。

8.3.1 Least Recently Used (LRU)

LRU回收策略基于最近使用的(LRU)算法,确保最近最少使用的条目(即在最长时间内未被触摸的条目)被首先逐出。
PS:LRU回收策略很好地满足了堆缓存的大部分用例。在拿不准的时候使用它。
这种驱逐策略由LruEvictionPolicy 实现,可以通过CacheConfiguration配置。它支持通过内存大小限制进行批量驱逐。
<bean class="org.apache.ignite.cache.CacheConfiguration">
  <property name="name" value="myCache"/>

  <!-- Enabling on-heap caching for this distributed cache. -->
  <property name="onheapCacheEnabled" value="true"/>

  <property name="evictionPolicy">
    <!-- LRU eviction policy. -->
    <bean class="org.apache.ignite.cache.eviction.lru.LruEvictionPolicy">
        <!-- Set the maximum cache size to 1 million (default is 100,000). -->
      <property name="maxSize" value="1000000"/>
    </bean>
  </property>

    ...
</bean>

8.3.2 First In First Out (FIFO)

FIFO收回策略基于先出先出(FIFO)算法,确保在堆缓存中最长时间的条目将首先被逐出。它与LruEvictionPolicy 不同,因为它忽略了条目的访问顺序。
这一驱逐策略是通过FifoEvictionPolicy 实现的,可以通过CacheConfiguration配置。它支持通过内存大小限制进行批量回收。
<bean class="org.apache.ignite.cache.CacheConfiguration">
  <property name="name" value="myCache"/>

  <!-- Enabling on-heap caching for this distributed cache. -->
  <property name="onheapCacheEnabled" value="true"/>

  <property name="evictionPolicy">
    <!-- FIFO eviction policy. -->
    <bean class="org.apache.ignite.cache.eviction.fifo.FifoEvictionPolicy">
        <!-- Set the maximum cache size to 1 million (default is 100,000). -->
      <property name="maxSize" value="1000000"/>
    </bean>
  </property>

    ...
</bean>

8.3.3 Sorted

排序驱逐策略类似于FIFO驱逐策略,其区别在于,条目的顺序是由默认的或由用户定义的comparator定义的,并且确保最小的条目(即具有最小值的整数键的条目)首先被逐出。
默认的比较器使用缓存条目的键来进行比较,这就要求缓存的类需要实现Comparable 接口。用户可以提供他们自己的比较器实现,可以使用键、值或两者进行条目比较。
这种驱逐策略由SortedEvictionPolicy实现,可以通过CacheConfiguration配置。它支持通过内存大小限制进行批量驱逐。
<bean class="org.apache.ignite.cache.CacheConfiguration">
  <property name="name" value="myCache"/>

  <!-- Enabling on-heap caching for this distributed cache. -->
  <property name="onheapCacheEnabled" value="true"/>

  <property name="evictionPolicy">
    <!-- Sorted eviction policy. -->
    <bean class="org.apache.ignite.cache.eviction.sorted.SortedEvictionPolicy">
        <!--
                    Set the maximum cache size to 1 million (default is 100,000)
                    and use default comparator.
            -->
      <property name="maxSize" value="1000000"/>
    </bean>
  </property>

  ...
</bean>

8.3.4 Random

随机驱逐策略随机选择要驱逐的条目。这种驱逐策略主要用于调试和基准测试。
这个策略由RandomEvictionPolicy 实现,并通过CacheConfiguration配置
<bean class="org.apache.ignite.cache.CacheConfiguration">
  <property name="name" value="myCache"/>

  <!-- Enabling on-heap caching for this distributed cache. -->
  <property name="onheapCacheEnabled" value="true"/>

  <property name="evictionPolicy">
    <!-- Random eviction policy. -->
    <bean class="org.apache.ignite.cache.eviction.random.RandomEvictionPolicy">         <!-- Set the maximum cache size to 1 million (default is 100,000). -->
      <property name="maxSize" value="1000000"/>
    </bean>
  </property>

    ...
</bean>

9.Expiry Policies

过期策略指定在考虑缓存条目过期之前必须经过的时间。时间可以从创建、最后访问或修改时间计数开始计算。
可以使用任何ExpiryPolicy预定义的实现设置过期策略:
className创建时间最后访问时间最后修改时间
CreatedExpiryPolicy使用
AccessedExpiryPolicy使用使用
ModifiedExpiryPolicy使用使用
TouchedExpiryPolicy使用使用使用
EternalExpiryPolicy

还可以自定义ExpiryPolicy 的实现
过期策略可以在CacheConfiguration设置。此策略将用于缓存内的所有条目

cfg.setExpiryPolicyFactory(CreatedExpiryPolicy.factoryOf(Duration.ZERO))
此外,可以更改或设置缓存中的单个操作的过期策略。
IgniteCache<Object, Object> cache = cache.withExpiryPolicy(
        new CreatedExpiryPolicy(new Duration(TimeUnit.SECONDS, 5)));
他的策略将用于在返回的缓存实例上调用的每个操作。

9.1 Eager TTL

过期的条目可以从缓存中删除,或者当它们被不同的缓存操作访问时。如果至少有一个配置为Eagar TTL的缓存,那么ignite将创建一个线程来清除后台的过期条目。
Eager  TTL可以通过cCacheConfiguration.eagerTtl来启用或禁用。(默认值为true):
<bean class="org.apache.ignite.configuration.CacheConfiguration">
    <property name="eagerTtl" value="true"/>
</bean>

10. Transactions(事务)

ACID兼容事务确保保证一致性。

10.1 Atomicity Mode

ignite支持2种模式用于缓存操作、transactional(事务)和atomic(原子)。在事务模式中,您可以在事务中组合多个缓存操作,而原子模式支持多个原子操作,一次一个。
这些原子性模式是在CacheAtomicityMode enum中定义的:
>TRANSACTIONAL
>ATOMIC
事务模式允许完全符合acid的事务,但是,当需要原子语义时,建议使用原子模式来提高性能.
原子模式通过避免事务性锁,提供更好的性能,同时仍然提供数据原子性和一致性。原子模式的另一个不同之处在于,像putAll(…)和removeAll(…)方法不再在一个事务中执行,并且可以部分失败。如果发生部分故障,CachePartialUpdateException将会抛出,它会包含更新失败的键的列表。
原子性模式是在CacheAtomicityMode enum中定义的,可以通过CacheConfiguration的atomicityMode属性进行配置。
<bean class="org.apache.ignite.configuration.IgniteConfiguration">
    ...
    <property name="cacheConfiguration">
        <bean class="org.apache.ignite.configuration.CacheConfiguration">
            <!-- Set a cache name. -->
                    <property name="name" value="myCache"/>

            <!-- Set atomicity mode, can be ATOMIC or TRANSACTIONAL. 
                                 ATOMIC is default. -->
                    <property name="atomicityMode" value="TRANSACTIONAL"/>
            ... 
        </bean>
    </property>

    <!-- Optional transaction configuration. -->
    <property name="transactionConfiguration">
        <bean class="org.apache.ignite.configuration.TransactionConfiguration">
            <!-- Configure TM lookup here. -->
        </bean>
    </property>
</bean>
CacheConfiguration cacheCfg = new CacheConfiguration();

cacheCfg.setName("cacheName");

cacheCfg.setAtomicityMode(CacheAtomicityMode.ATOMIC);

IgniteConfiguration cfg = new IgniteConfiguration();

cfg.setCacheConfiguration(cacheCfg);

// Optional transaction configuration. Configure TM lookup here.
TransactionConfiguration txCfg = new TransactionConfiguration();

cfg.setTransactionConfiguration(txCfg);

// Start Ignite node.
Ignition.start(cfg);
PS:Performance性能
    当使用原子模式时,事务会被禁用,这就可以在不需要事务的情况下,实现高性能和吞吐量。

10.2 IgniteTransactions

IgniteTransactions接口包含启动和完成事务的功能,以及订阅侦听器或获取度量。
PS:Cross-Cache Transactions
    你可以组合多个从不同缓存的操作,到一个事务中。这允许你在一个事务中更新不同类型的缓存,比如像REPLICATED 和PARTITIONED 缓存
PS:Near Cache Transactions
    near缓存是完全事务性的,只要服务器上的数据发生变化,就会自动更新或失效。
你可以像下面这样得到一个IgniteTransactions的实例:
Ignite ignite = Ignition.ignite();

IgniteTransactions transactions = ignite.transactions();
下面是一个如何在ignite中执行事务的例子:
try (Transaction tx = transactions.txStart()) {
    Integer hello = cache.get("Hello");

    if (hello == 1)
        cache.put("Hello", 11);

    cache.put("World", 22);

    tx.commit();
}
PS:Transactional Methods
        当事务模式对于缓存开启时,通过IgniteCache API公开的大多数方法都是完全事务性的。但是,有一种方法可以精确地知道方法是否满足ACID原则——看方法签名,如果看到它抛出TransactionException,那么它就可以安全地用于分布式事务中。在这样的方法中,您可以看到cache.put(…)、cache.get(…)、cache.invoke(…)等等。

10.3 Two-Phase-Commit (2PC)

在适用的情况下,ignite使用了2PC协议进行许多单阶段提交优化。每当在事务中更新数据时,ignite就会将事务状态保持在本地事务映射中,直到commit()被调用,此时,如果需要,则将数据传输到参与的远程节点。
有关ignite 2PC如何工作的更多信息,你可以看看这些博客:
注:自己去官方文档找吧
PS:ACID Compliance
    IGNITE提供完全的ACID(Atomicity, Consistency, Isolation, Durability)确保一致性的兼容事务。

10.4 Concurrency Modes and Isolation Levels

当TRANSACTIONAL 模式被配置了,ignite就支持OPTIMISTIC 和PESSIMISTIC 的并发模式的事务。并发级别决定何时应该获取entry-level事务锁---在数据访问的时候或者prepare(准备)阶段。锁定以阻止对于对象的并发访问。例如,当您尝试使用悲观锁定更新ToDo列表项时,服务器在对象上放置一个锁,直到您提交或回滚事务,以便不允许其他事务或操作更新相同的条目。不管事务中使用的并发级别是什么,都存在一个时刻,在提交之前,所有参与事务的条目都被锁定。
隔离级别定义了并发事务将如何“发现看到”并处理相同的缓存键的操作。ignite支持READ_COMMITTED、REPEATABLE_READ 和SERIALIZABLE隔离级别。
并发模式和隔离级别的所有组合都可以同时使用。下面是对ignite行为的描述和每个并发隔离组合提供的保证。

10.5 Pessimistic Transactions(悲观锁事务)

在悲观的事务中,锁是在第一次读或写访问(取决于隔离级别)中获得的,并在事务被提交或回滚之前保存。在此模式中,锁首先在主节点上获得,然后在准备阶段发展到备份节点。以下隔离级别可以配置为悲观并发模式:
>READ_COMMITTED 数据被读取而没有锁,并且永远不会被缓存到事务本身中。如果在缓存配置中允许,可以从备份节点读取数据。在这种隔离中,您可
                以使用所谓的不可重复读取,因为当你在事务中读取两次数据,那么并发的树屋可以在这两次读取操作的间隙,将数据修改掉。该锁
                只在第一次写入访问时被获取(这包括EntryProcessor调用)。这意味着在事务提交时,在事务中读取的条目可能具有不同的值。
                在这种情况下不会抛出异常。
>REPEATABLE_READ 第一个读或写访问时候,获取到条目的锁,数据从主节点获取,并存储在本地事务映射中。对相同数据的所有连续访问都是本地的,
                并将返回上一次读取或更新的事务值。这意味着没有其他并发事务可以对锁定的数据进行更改,并且您将为您的事务获取可重复的读
                取。
>SERIALIZABLE  在悲观模式下,这种隔离级别的工作方式与可重复读取相同。

请注意,在悲观模式下,锁定的顺序很重要。此外,ignite将顺序排列,并且完全按照用户提供的顺序。
PS:Performance Considerations
    假设拓扑中有3个节点(A,B,C),在你的是事务操作中,你为键【1,2,3,4,5,6】执行了putAll操作,假设这些键以如下方式映射到节点:
    {A:1,4},{B:2,5},{C:3,6},由于ignite无法在悲观模式下重新安排锁的获取顺序,它必须进行6次连续的网络往返:[A、B、C、A、B、C]。
    当键的锁定顺序对事务的语义不重要时,在同一个分区,按照分区来对缓存键分组,并锁定这些键,这种方式是可取的。这可能大大减少大型事务中
    的网络消息数量。在本例中,如果按照下列方式对一个putAll排序:[1、4、2、5、3、6],那么只需要3次连续的往返。
PS:Topology Change Restrictions
    请注意,如果获得了至少一个悲观的事务锁,则在事务提交或回滚之前,将不可能更改缓存拓扑。因此,不建议持有事务锁很长时间。

10.6 Optimistic Transactions(乐观锁事务)

在乐观的事务中,在准备步骤期间,在主节点上获取条目的锁,然后发展到备份节点,并在事务提交后释放。如果事务被用户回滚,并且没有提交尝试,则永远不会获得锁。下面的隔离级别可以配置为乐观的并发模式:
>READ_COMMITTED 在原始节点上收集那些应该应用到缓存的更改,并且应用于事务提交。事务数据读取是没有锁的,并且永远不会在事务中缓存。
                如果在缓存配置中允许,可以从备份节点读取数据。在这种隔离中,您可以使用所谓的不可重复读取,因为当你在事务中读取
                两次数据,那么并发的树屋可以在这两次读取操作的间隙,将数据修改掉。这种模式组合不检查自第一次读或写访问之后是否已经修
                改了条目值,并且永远不会产生乐观的异常。
>REPEATABLE_READ 这个隔离级别的事务工作类似于乐观的READ_COMMITTED (就是上面这个),只有一点不同-----读取值被缓存到原始节点上,所
                有后续的读取都保证是本地的。这种模式组合不检查自第一次读或写访问之后是否已经修改了条目值,也不会增加一个乐观的异常。
>SERIALIZABLE 在第一次读取访问时存储一个条目版本。如果ignite引擎检测到至少在发起事务中使用的一个条目已经被修改,ignite将会在提交阶
                段失败。这是通过内部检查在提交时实际在网格中的一个条目的版本的内部检查来实现的。简而言之,这意味着,如果点燃检测到有一
                个冲突在事务提交阶段,我们会将这个事务失败,并扔出TransactionOptimisticException &回滚任何更改。用户应该处理这
                个异常并重试事务。
IgniteTransactions txs = ignite.transactions();

// Start transaction in optimistic mode with serializable isolation level.
while (true) {
    try (Transaction tx =  
         ignite.transactions().txStart(TransactionConcurrency.OPTIMISTIC,
                                       TransactionIsolation.SERIALIZABLE)) {
                // Modify cache entires as part of this transacation.
            ....

            // commit transaction.  
            tx.commit();

        // Transaction succeeded. Leave the while loop.
        break;
    }
    catch (TransactionOptimisticException e) {
            // Transaction has failed. Retry.
    }
}
这里需要注意的另一个重要问题是,即使只是简单地读取了条目(没有修改,cache.put(…)),事务仍然会失败,因为条目的值可能对初始事务中的逻辑很重要。
请注意,键顺序对于READ_COMMITTED和REPEATABLE_READ事务非常重要,因为这些模式中锁仍然以顺序方式获取。

10.7 Deadlock Detection

在处理分布式事务时,任何人必须遵循的一个主要规则是,必须以相同的顺序来获取键的锁,参与事务。违反此规则可能导致分布式死锁。
ignite并不能避免分布式死锁,但它具有内置的功能,可以使调试和修复这种情况变得更容易。
如下面的代码片段所示,事务启动,并伴随设置了事务的超时时间。如果超时过期,死锁检测程序将尝试寻找可能导致超时的死锁。当超时过期,不管是否产生了死锁,都将TransactionTimeoutException作为死锁产生的原因,并传播到应用程序代码中。然而,如果检测到死锁,那么返回的TransactionTimeoutException 将会是TransactionDeadlockException (至少在一个事务中涉及到死锁)。
try (Transaction tx = ignite.transactions().txStart(TransactionConcurrency.PESSIMISTIC,
    TransactionIsolation.READ_COMMITTED, 300, 0)) {
    cache.put(1, 1);

    cache.put(2, 1);

    tx.commit();
}
catch (CacheException e) {
    if (e.getCause() instanceof TransactionTimeoutException &&
        e.getCause().getCause() instanceof TransactionDeadlockException)    

        System.out.println(e.getCause().getCause().getMessage());
}
TransactionDeadlockException消息包含有用的信息,可以帮助你找到死锁的原因。
Deadlock detected:

K1: TX1 holds lock, TX2 waits lock.
K2: TX2 holds lock, TX1 waits lock.

Transactions:

TX1 [txId=GridCacheVersion [topVer=74949328, time=1463469328421, order=1463469326211, nodeOrder=1], nodeId=ad68354d-07b8-4be5-85bb-f5f2362fbb88, threadId=73]
TX2 [txId=GridCacheVersion [topVer=74949328, time=1463469328421, order=1463469326210, nodeOrder=1], nodeId=ad68354d-07b8-4be5-85bb-f5f2362fbb88, threadId=74]

Keys:

K1 [key=1, cache=default]
K2 [key=2, cache=default]
死锁检测是一种多步过程,可能需要多次迭代,具体取决于在可能的死锁中涉及的群集、键和事务的节点数量。死锁检测启动程序是一个节点,一个事务开始并以TransactionTimeoutException异常失败。该节点将调查如果死锁发生,通过交换与其他远程节点请求/响应,并准备包含TransactionDeadlockException异常的死锁相关的报告。每个这样的消息(请求/响应)称为迭代。
由于事务在死锁检测过程完成之前不会被回滚,所以如果您希望对事务的回滚有一个可预测的时间,那么调整参数(如下所示)是有意义的。
>IgniteSystemProperties.IGNITE_TX_DEADLOCK_DETECTION_MAX_ITERS指定死锁检测程序的最大迭代次数。如果该属性的值小于或等于零,死锁检测将被禁用(默认为1000)
>IgniteSystemProperties.IGNITE_TX_DEADLOCK_DETECTION_TIMEOUT指定死锁检测机制的超时(默认为1分钟)。

注意,如果迭代次数太少,您可能会得到一个不完整的死锁报告。
如果您想完全避免死锁,请参考下面的Deadlock-free事务部分。

10.8 Deadlock-Free Transactions

对于OPTIMISTIC 乐观锁并发模式下的SERIALIZABLE(序列化) 隔离级别,锁不按顺序获得。在这种模式下,键可以按任何顺序访问,因为事务锁是并行的获取的并且附带着检查,这可以避免死锁的发生。
我们需要引入一些概念来描述SERIALIZABLE隔离级别下事务中的锁是如何工作的。在ignite中,每个事务都分配一个可比较的的版本,称为XidVersion。在事务提交时,在事务中执行写入的每个条目被分配一个新的可比较版本,称为EntryVersion。一个OPTIMISTIC 乐观锁并发模式下的SERIALIZABLE隔离级别的事务,带有版本XidVersion,将会在下面的条件下以TransactionOptimisticException异常失败:
    1.有一种正在进行的PESSIMISTIC(悲观锁) 或非序列化的OPTIMISTIC(乐观锁)事务,持有了在SERIALIZABLE(可序列化)事务的条目的锁。
    2.还有另一个正在进行的OPTIMISTIC (乐观)的SERIALIZABLE (可序列化)事务,携带有版本XidVersionB ,且XidVersionB > XidVersionA,该事务持有了SERIALIZABLE (可序列化)事务的一个条目的锁。
    3.当OPTIMISTIC (乐观的)SERIALIZABLE (可序列化)事务获得所有所需的锁时,现在存在一个条目,它带有与当前版本在提交之前,存在一个条目,该条目的当前版本不同于观察的版本。

PS:在高度并发的环境中,乐观锁定可能导致较高的事务失败率,但是如果锁以事务的不同顺序获得,悲观锁定会导致死锁。 
然而,在一个自由竞争的环境中,乐观的可序列化的锁定可以为大型事务提供更好的性能,因为网络访问的数量只取决于事务跨越的节点的数量,而不依赖于事务中键的数量。

10.9 Integration With JTA

通过使用TransactionConfiguration # setTxManagerFactory方法,可以使用JTA事务管理器查找类配置ignite。事务管理器工厂是提供一个带有JTA事务管理器的ignite的工厂。
Ignite提供了CacheJndiTmFactory 工厂。它是开箱即用的事务管理器工厂实现,使用JNDI名称来查找TM。
当设置时,在ignite事务缓存上的每个缓存操作将检查是否有正在进行的JTA事务。如果JTA事务启动,ignite将启动一个事务,并使用它自己的XAResource的内部实现,以将自己的事务加入进JTA事务。ignite的事务将会一致的与JTA事务一起准备,提交或者回滚。
下面是一个使用JTA事务管理器和ignite集成的示例
// Get an instance of JTA transaction manager.
TMService tms = appCtx.getComponent(TMService.class);

// Get an instance of Ignite cache.
IgniteCache<String, Integer> cache = cache();

UserTransaction jtaTx = tms.getUserTransaction();

// Start JTA transaction.
jtaTx.begin();

try {
    // Do some cache operations.
    cache.put("key1", 1);
    cache.put("key2", 2);

    // Commit the transaction.
    jtaTx.commit();
}
finally {
    // Rollback in a case of exception.
    if (jtaTx.getStatus() == Status.STATUS_ACTIVE)
        jtaTx.rollback();
}

11.Locks

对缓存对象执行相互排斥。
缓存事务将隐式地获取锁。然而,有些情况下显式锁更有用。IgniteCache API的lock()方法返回一个java.util.concurrent.locks的实例,这使您可以为任何给定的键定义显式的分布式锁。锁也可以通过使用IgniteCache.lockAll()方法来获取。
IgniteCache<String, Integer> cache = ignite.cache("myCache");

// Create a lock for the given key.
Lock lock = cache.lock("keyLock");
try {
    // Aquire the lock.
    lock.lock();

    cache.put("Hello", 11);
    cache.put("World", 22);
}
finally {
    // Release the lock.
    lock.unlock();
}
PS:Atomicity Mode
    在ignite中,锁只支持TRANSACTIONAL 模式,可以通过CacheConfiguration的atomicityMode属性配置。

11.1Locks and Transactions

显式锁不是事务性的,不能从事务中使用(异常将被抛出)。如果您确实需要在事务中显式锁定,那么您应该使用TransactionConcurrency.PESSIMISTIC对事务并行控制,TransactionConcurrency.PESSIMISTIC这种模式将为相关缓存操作获取显式锁。

12. Cache Queries

12.1 概述

ignite支持一个非常优雅的查询API,支持基于谓词的扫描查询、SQL查询(ansi-99兼容),和文本查询。对于SQL查询,ignites支持内存索引,因此所有数据查找都是非常快的.如果在非堆内存中缓存数据,则查询索引也将被缓存到非堆内存中.
ignite还通过IndexingSpi 和SpiQuery类提供了对自定义索引的支持。

12.2 Main Abstractions

IgniteCache有几个查询方法,这些方法都接收Query类的子类和返回QueryCursor。

12.2.1 Query

Query抽象类表示在分布式缓存上执行的抽象的分页查询。您可以通过Query.setPageSize(…)方法为返回的游标设置页面大小(默认为1024)。

12.2.2 QueryCursor

QueryCursor 代表查询结果集,它允许透明的一页页的迭代。每当用户开始迭代最后一页时,它会自动请求后台的下一页。对于不需要分页的情况,您可以使用QueryCursor.getAll()方法,它将获取整个查询结果并将其存储在集合中。
PS:Closing Cursors
    如果调用方法QueryCursor.getAll(),光标将自动关闭。如果您在for循环中迭代游标或显式获取迭代器,则必须显式地关闭()光标或使用自动关闭语法。

12.3 Scan Queries

扫描查询允许根据一些用户定义的谓词来查询分布表单中的缓存。
//java8
IgniteCache<Long, Person> cache = ignite.cache("mycache");

// Find only persons earning more than 1,000.
try (QueryCursor cursor = cache.query(new ScanQuery((k, p) -> p.getSalary() > 1000)) {
  for (Person p : cursor)
    System.out.println(p.toString());
}
//java7
IgniteCache<Long, Person> cache = ignite.cache("mycache");

// Find only persons earning more than 1,000.
IgniteBiPredicate<Long, Person> filter = new IgniteBiPredicate<>() {
  @Override public boolean apply(Long key, Perons p) {
    return p.getSalary() > 1000;
    }
};

try (QueryCursor cursor = cache.query(new ScanQuery(filter)) {
  for (Person p : cursor)
    System.out.println(p.toString());
}
扫描查询也支持可选的变压器闭包,在发送给客户端之前,它允许转换在服务器节点上的条目。例如,当您想要从大型对象中获取几个字段并希望最小化网络流量时,这是很有用的。下面的示例展示了如何只获取键,而不发送值对象。
//java8
IgniteCache<Long, Person> cache = ignite.cache("mycache");

// Get only keys for persons earning more than 1,000.
List<Long> keys = cache.query(new ScanQuery<Long, Person>(
    (k, p) -> p.getSalary() > 1000), // Remote filter.
    Cache.Entry::getKey              // Transformer.
).getAll();
//java7
IgniteCache<Long, Person> cache = ignite.cache("mycache");

// Get only keys for persons earning more than 1,000.
List<Long> keys = cache.query(new ScanQuery<>(
    // Remote filter.
    new IgniteBiPredicate<Long, Person>() {
        @Override public boolean apply(Long k, Person p) {
            return p.getSalary() > 1000;
        }
    }),
    // Transformer.
    new IgniteClosure<Cache.Entry<Long, Person>, Long>() {
        @Override public Long apply(Cache.Entry<Long, Person> e) {
            return e.getKey();
        }
    }
).getAll();

12.4 SQL Queries

ignite SQL查询将被包含在单独的SQL查询中。(请关注后面翻译的SQL 查询)

12.5 Text Queries

ignite还支持基于Lucene索引的基于文本的查询。
IgniteCache<Long, Person> cache = ignite.cache("mycache");

// Query for all people with "Master Degree" in their resumes.
TextQuery txt = new TextQuery(Person.class, "Master Degree");

try (QueryCursor<Entry<Long, Person>> masters = cache.query(txt)) {
  for (Entry<Long, Person> e : cursor)
    System.out.println(e.getValue().toString());
}

12.6 Query Configuration by Annotations

可以通过使用@querysqlfield注释来配置索引。告诉ignite这类型应该是索引,键值对可以传递给CacheConfiguration.setIndexedTypes(MyKey.class, MyValue.class)方法。注意,这个方法只接受键值对的类型,一个用于键类,另一个用于值类。
public class Person implements Serializable {
  /** Person ID (indexed). */
  @QuerySqlField(index = true)
  private long id;

  /** Organization ID (indexed). */
  @QuerySqlField(index = true)
  private long orgId;

  /** First name (not-indexed). */
  @QuerySqlField
  private String firstName;

  /** Last name (not indexed). */
  @QuerySqlField
  private String lastName;

  /** Resume text (create LUCENE-based TEXT index for this field). */
  @QueryTextField
  private String resume;

  /** Salary (indexed). */
  @QuerySqlField(index = true)
  private double salary;

  ...
}

12.7 Query Configuration using QueryEntity

索引和字段也可以以org.apache.ignite.cache.QueryEntity来配置,便于使用Spring进行XML配置。详情请参考javadoc。它等价于使用@ querysqlfield注释,因为类注释在内部转换为查询实体。
<bean class="org.apache.ignite.configuration.CacheConfiguration">
    <property name="name" value="mycache"/>
    <!-- Configure query entities -->
    <property name="queryEntities">
        <list>
            <bean class="org.apache.ignite.cache.QueryEntity">
                <property name="keyType" value="java.lang.Long"/>
                <property name="valueType" value="org.apache.ignite.examples.Person"/>

                <property name="fields">
                    <map>
                        <entry key="id" value="java.lang.Long"/>
                        <entry key="orgId" value="java.lang.Long"/>
                        <entry key="firstName" value="java.lang.String"/>
                        <entry key="lastName" value="java.lang.String"/>
                        <entry key="resume" value="java.lang.String"/>
                        <entry key="salary" value="java.lang.Double"/>
                    </map>
                </property>

                <property name="indexes">
                    <list>
                        <bean class="org.apache.ignite.cache.QueryIndex">
                            <constructor-arg value="id"/>
                        </bean>
                        <bean class="org.apache.ignite.cache.QueryIndex">
                            <constructor-arg value="orgId"/>
                        </bean>
                        <bean class="org.apache.ignite.cache.QueryIndex">
                            <constructor-arg value="salary"/>
                        </bean>
                    </list>
                </property>
            </bean>
        </list>
    </property>
</bean>
CacheConfiguration<Long, Person> cacheCfg = new CacheConfiguration<>();
...
cacheCfg.setName("mycache");

// Setting up query entity.
QueryEntity queryEntity = new QueryEntity();

queryEntity.setKeyType(Long.class.getName());
queryEntity.setValueType(Person.class.getName());

// Listing query fields.
LinkedHashMap<String, String> fields = new LinkedHashMap();

fields.put("id", Long.class.getName());
fields.put("orgId", Long.class.getName());
fields.put("firstName", String.class.getName());
fields.put("lastName", String.class.getName());
fields.put("resume", String.class.getName());
fields.put("salary", Double.class.getName());

queryEntity.setFields(fields);

// Listing indexes.
Collection<QueryIndex> indexes = new ArrayList<>(3);

indexes.add(new QueryIndex("id"));
indexes.add(new QueryIndex("orgId"));
indexes.add(new QueryIndex("salary"));

queryEntity.setIndexes(indexes);
...
cacheCfg.setQueryEntities(Arrays.asList(queryEntity));
...

13. Continuous Queries

持续获取实时查询结果。

13.1 Continuous Queries

持续的查询使您能够侦听在ignite缓存中发生的数据修改。一旦开始了一个连续的查询,您就会收到所有的数据更改的通知。
持续查询功能可通过下面详细说明的连续性查询类提供。

13.1.1 Initial Query

在准备执行一个连续查询时,您有一个选项,可以指定连续查询在集群中注册并在您开始接收更新之前执行的初始查询。
ContinuousQuery.setInitialQuery可以设置初始查询(查询)方法,可以是任何查询类型:scan,SQL,或者text。

12.1.2 Remote Filter

这个过滤器在一个给定键的主节点和备份节点上执行,并评估是否应该将更新作为事件传播给查询的本地侦听器。
如果这个过滤器返回true,那么本地监听器将会被通知,否则,将会忽略掉通知。当发生的时候,对指定主节点和备份节点的更新过滤,允许减少主/备份节点之间不必要的网络流量,并且本地侦听器在应用程序端执行。
可以通过 ContinuousQuery.setRemoteFilter(CacheEntryEventFilter<K, V>) 设置一个远程过滤器。

12.1.3 Local Listener

当缓存被修改(插入、更新或删除的条目)时,与更新相关的事件将发送到连续查询的本地侦听器,以便您的应用程序能够相应地作出反应。
当事件通过远程过滤器时,它们将被发送到客户端,以通知本地侦听器。
本地侦听器是通过ContinuousQuery.setLocalListener(CacheEntryUpdatedListener<K, V>)方法设置的。
IgniteCache<Integer, String> cache = ignite.cache("mycache");

// Creating a continuous query.
ContinuousQuery<Integer, String> qry = new ContinuousQuery<>();

// Setting an optional initial query. 
// The query will return entries for the keys greater than 10.
qry.setInitialQuery(new ScanQuery<Integer, String>((k, v) -> k > 10)):

// Local listener that is called locally when an update notification is received.
qry.setLocalListener((evts) -> 
    evts.stream().forEach(e -> System.out.println("key=" + e.getKey() + ", val=" + e.getValue())));

// This filter will be evaluated remotely on all nodes.
// Entry that pass this filter will be sent to the local listener.
qry.setRemoteFilter(e -> e.getKey() > 10);

// Executing the query.
try (QueryCursor<Cache.Entry<Integer, String>> cur = cache.query(qry)) {
  // Iterating over existing data stored in cache.
  for (Cache.Entry<Integer, String> e : cur)
    System.out.println("key=" + e.getKey() + ", val=" + e.getValue());

  // Adding a few more cache entries.
  // As a result, the local listener above will be called.
  for (int i = 5; i < 15; i++)
    cache.put(i, Integer.toString(i));
}
//java7
IgniteCache<Integer, String> cache = ignite.cache(CACHE_NAME);

// // Creating a continuous query.
ContinuousQuery<Integer, String> qry = new ContinuousQuery<>();

// Setting an optional initial query. 
// The query will return entries for the keys greater than 10.
qry.setInitialQuery(new ScanQuery<Integer, String>(
  new IgniteBiPredicate<Integer, String>() {
  @Override public boolean apply(Integer key, String val) {
    return key > 10;
  }
}));

// Local listener that is called locally when an update notification is received.
qry.setLocalListener(new CacheEntryUpdatedListener<Integer, String>() {
  @Override public void onUpdated(Iterable<CacheEntryEvent<? extends Integer, ? extends String>> evts) {
    for (CacheEntryEvent<Integer, String> e : evts)
      System.out.println("key=" + e.getKey() + ", val=" + e.getValue());
  }
});

// This filter will be evaluated remotely on all nodes.
// Entry that pass this filter will be sent to the local listener.
qry.setRemoteFilter(new CacheEntryEventFilter<Integer, String>() {
  @Override public boolean evaluate(CacheEntryEvent<? extends Integer, ? extends String> e) {
    return e.getKey() > 10;
  }
});

// Execute query.
try (QueryCursor<Cache.Entry<Integer, String>> cur = cache.query(qry)) {
  // Iterating over existing data stored in cache.
  for (Cache.Entry<Integer, String> e : cur)
    System.out.println("key=" + e.getKey() + ", val=" + e.getValue());

  // Adding a few more cache entries.
  // As a result, the local listener above will be called.
  for (int i = keyCnt; i < keyCnt + 10; i++)
    cache.put(i, Integer.toString(i));
}

12.1.4 Events Delivery Guarantees

持续的查询实现保证向客户端的本地侦听器交付事件。
这是可行的,因为每个备份节点(s)除了主节点之外都维护一个更新队列。如果主节点崩溃或拓扑由于其他原因发生了更改,那么每个备份节点都会将其内部队列的内容流到客户端,从而确保没有向客户端本地侦听器发送的事件。
为了避免重复的通知,当所有的备份节点都将队列刷新到客户端时,ignite管理一个分区更新计数器。在某个分区中的条目被更新,在主备份和备份中对这个分区的计数器就会递增。此计数器的值也会连同事件通知一起发送到客户端,该客户端还维护这个映射的副本。如果客户端收到的计数器小于其本地映射的更新,则此更新将被视为重复和丢弃。
一旦客户机确认接收到事件,主节点和备份节点将从备份队列中删除此事件的记录。

12.2 Example

地址如下:自己看
https://github.com/apache/ignite/blob/master/examples/src/main/java/org/apache/ignite/examples/datagrid/CacheContinuousQueryExample.java

13. Data Loading

将大量数据装载到缓存中。

13.1 概述

数据加载通常与启动时初始化缓存数据有关。在加载大量数据时,使用标准缓存put(…)或putAll(…)操作通常是低效的。ignite提供IgniteDataStreamer API和CacheStore API,可以以更有效的方式帮助您加载大量数据进ignite缓存。

13.2 IgniteDataStreamer

数据流是由IgniteDataStreamer API定义的,它的构建是为了将大量的连续数据注入到ignite缓存中。数据流以可伸缩和容错的方式构建,并在将条目发送到相应的集群成员之前,通过批处理条目在来实现高性能。
PS:数据流应该用于在任何时候加载大量数据到缓存中,包括启动时的预加载。
查看数据流那一章,了解详情。

13.3 IgniteCache.loadCache()

另一种加载大量数据集合的方式使通过CacheStore.loadcache() 方法。它允许即使不传递所有需要加载的键,缓存数据就可以开始加载。
IgniteCache.loadCache()方法将会在每一个缓存节点上执行localLoadCache方法,这个方法又会委托给CacheStore.loadCache()方法。调用加载只能在本地节点上,通过IgniteCache.localLoadCache()方法。
PS:对于分区缓存,未映射到该节点的键(无论是主节点还是备份节点)将被缓存自动丢弃。
下面是一个关于CacheStore.loadCache()实现的示例。有关CacheStore如何实现的完整示例是指持久存储那一章。
public class CacheJdbcPersonStore extends CacheStoreAdapter<Long, Person> {
    ...
  // This method is called whenever "IgniteCache.loadCache()" or
  // "IgniteCache.localLoadCache()" methods are called.
  @Override public void loadCache(IgniteBiInClosure<Long, Person> clo, Object... args) {
    if (args == null || args.length == 0 || args[0] == null)
      throw new CacheLoaderException("Expected entry count parameter is not provided.");

    final int entryCnt = (Integer)args[0];

    Connection conn = null;

    try (Connection conn = connection()) {
      try (PreparedStatement st = conn.prepareStatement("select * from PERSONS")) {
        try (ResultSet rs = st.executeQuery()) {
          int cnt = 0;

          while (cnt < entryCnt && rs.next()) {
            Person person = new Person(rs.getLong(1), rs.getString(2), rs.getString(3));

            clo.apply(person.getId(), person);

            cnt++;
          }
        }
      }
    }
    catch (SQLException e) {
      throw new CacheLoaderException("Failed to load values from cache store.", e);
    }
  }
  ...
}

13.3.1 Partition-aware data loading

在上述场景中,将在所有节点上执行相同的查询。每个节点将遍历整个结果集,跳过不属于节点的键,这不是很有效。
如果将分区ID与数据库中的每个记录一起存储,则情况可能会有所改善。您可以使用org.apache.ignite.cache.affinity.Affinity接口,用于为存储在缓存中的任何键获取分区ID。
下面是一个示例代码片段,用于确定存储在缓存中的每个人对象的分区ID。
IgniteCache cache = ignite.cache(cacheName);
Affinity aff = ignite.affinity(cacheName);

for (int personId = 0; personId < PERSONS_CNT; personId++) {
    // Get partition ID for the key under which person is stored in cache.
    int partId = aff.partition(personId);

    Person person = new Person(personId);
    person.setPartitionId(partId);
    // Fill other fields.

    cache.put(personId, person);
}
当Person对象可以感知到分区ID,每个节点只能查询属于该节点的那些分区。为了做到这一点,您可以在缓存存储中插入一个ignite实例,并使用它来确定属于本地节点的分区。
下面是一个示例代码片段,演示如何使用Affinity 只加载本地分区。注意,示例代码是单线程的,但是可以通过分区ID实现非常有效的并行化。
public class CacheJdbcPersonStore extends CacheStoreAdapter<Long, Person> {
  // Will be automatically injected.
  @IgniteInstanceResource
  private Ignite ignite;

    ...
  // This mehtod is called whenever "IgniteCache.loadCache()" or
  // "IgniteCache.localLoadCache()" methods are called.
  @Override public void loadCache(IgniteBiInClosure<Long, Person> clo, Object... args) {
    Affinity aff = ignite.affinity(cacheName);
    ClusterNode locNode = ignite.cluster().localNode();

    try (Connection conn = connection()) {
      for (int part : aff.primaryPartitions(locNode))
        loadPartition(conn, part, clo);

      for (int part : aff.backupPartitions(locNode))
        loadPartition(conn, part, clo);
    }
  }

  private void loadPartition(Connection conn, int part, IgniteBiInClosure<Long, Person> clo) {
    try (PreparedStatement st = conn.prepareStatement("select * from PERSONS where partId=?")) {
      st.setInt(1, part);

      try (ResultSet rs = st.executeQuery()) {
        while (rs.next()) {
          Person person = new Person(rs.getLong(1), rs.getString(2), rs.getString(3));

          clo.apply(person.getId(), person);
        }
      }
    }
    catch (SQLException e) {
      throw new CacheLoaderException("Failed to load values from cache store.", e);
    }
  }

  ...
}
注意key-to-partition映射的数量取决于分区配置关联函数(参见org.apache.ignite.cache.affinity.AffinityFunction)。如果关联函数配置更改,则必须相应地更新数据库中的分区ID记录。

14 Data Rebalancing

预加载来自其他网格节点的数据以维护数据一致性。

14.1 概述

当一个新的节点加入拓扑时,现有的节点会放弃主节点或备份节点上新节点的一些键的所有权,以便在所有的时间内键在网格中保持平衡。
如果这个新的节点成为了某些分区的主节点或者备份节点,它将从先前的主节点获取数据,或者从该分区的一个备份节点中获取数据。一旦一个分区被完全加载到新节点上,它将在旧节点上被标记为过时,并且在该节点的所有当前事务完成后最终将被逐出。因此,在一段很短的时间内,在拓扑更改之后,可能会出现一个情况,即缓存将有比配置的还要更多的备份副本。然而,一旦再平衡完成,将从节点缓存中删除额外的备份副本。

14.2 Rebalance Modes

在CacheRebalanceMode enum中定义了再平衡模式。
CacheRebalanceModeDescription
SYNC同步平衡模式。在所有必需的数据从其他可用的网格节点加载之前,分布式缓存将不会启动。这意味着任何对公共API的调用都将被阻塞,直到重新平衡为止
ASYNC异步平衡模式。分布式缓存将立即启动,并将从后台的其他可用网格节点加载所有必需的数据。
NONE在这种模式下,不需要进行再平衡,这意味着当访问数据时,缓存将被加载到持久存储的需求中,或者将被显式地填充

默认情况下,异步平衡模式是启用的。要使用另一种模式,您可以设置CacheConfiguration的rebalanceMode属性,例如:

<bean class="org.apache.ignite.configuration.IgniteConfiguration">
    ...
    <property name="cacheConfiguration">
        <bean class="org.apache.ignite.configuration.CacheConfiguration">               
            <!-- Set synchronous rebalancing. -->
                    <property name="rebalanceMode" value="SYNC"/>
            ... 
        </bean
    </property>
</bean>
CacheConfiguration cacheCfg = new CacheConfiguration();

cacheCfg.setRebalanceMode(CacheRebalanceMode.SYNC);

IgniteConfiguration cfg = new IgniteConfiguration();

cfg.setCacheConfiguration(cacheCfg);

// Start Ignite node.
Ignition.start(cfg);

14.3 Rebalance Thread Pool Tuning

IgniteConfiguration 提供了setRebalanceThreadPoolSize 方法,它允许设置从ignite系统的线程池中取出,用于重新平衡需要的线程数量。 每当一个节点需要发送一个批数据到一个远程节点上时候,就会从系统线程池中取出一个线程,这个节点可能是分区额主或者备份,或者是需要处理来自相反方向的批处理。每次发送或接收和处理该批处理时,线程都会交出给线程池。
默认下,只有一个线程被用来做再平衡操作。它意味着在一个特定的时间点,只有一个线程将会被用于从一个节点到另一个节点进行批量转移,或从远端处理批次。作为一个例子,如果该集群有两个节点和一个缓存,然后所有的缓存分区将依次重新平衡,一个接一个。如果集群有两个节点和两个不同的缓存,那么这些缓存将以并行的方式重新平衡,但是在特定的时间点,只会处理属于某个特定缓存的批处理,如上所述。
PS:每个缓存的分区数不影响平衡性能。有意义的是数据的总数,平衡线程池大小和下面各部分列出的其他参数。

根据系统中缓存的数量和存储在缓存中的数据量的不同,如果平衡线程池的大小等于1,那么在所有数据重新平衡到一个节点之前,可以花费大量的时间。为了加速预加载过程,可以增加IgniteConfiguration.setRebalanceThreadPoolSize值适用于你的情况。
假设IgniteConfiguration.setRebalanceThreadPoolSize设置为4,考虑上面的示例中,那么再平衡的行为将会像如下这样:
    1.如果集群有两个节点和一个缓存,那么缓存的分区将被逻辑地放置在4个不同的组中,这些组将被4个线程中的一个重新平衡。属于某个特定组的分区将按顺序重新平衡。
    2.如果集群有两个节点和两个不同的缓存,那么每个缓存的分区将被逻辑地放入4个不同的组(每个缓存将有自己的4个组,总共提供8组),并且这些组将被4个不同的线程并行地重新平衡。然而,在一个特定的时间点,只有属于一个组(总共8个)的批次将被处理,如上所述。

系统线程池在所有缓存相关操作(put,get,etc .)、SQL引擎和其他模块中广泛使用。设置IgniteConfiguration。setRebalanceThreadPoolSize为一个大值可能会显著增加平衡性能,影响您的应用程序吞吐量

14.4 Rebalance Message Throttling

当再平衡器将数据从一个节点转移到另一个节点时,它将整个数据集分解成批,并将每个批发送到一个单独的消息中。如果你的数据集很大,有很多信息要发送,CPU或网络可以被过度消耗。在这种情况下,在重新平衡的消息之间等待是合理的,这样,再平衡过程导致的负面性能影响最小化。这个时间间隔由CacheConfiguration的rebalanceThrottle 配置属性控制。它的默认值为0,这意味着消息之间不会有暂停。注意,单个消息的大小也可以由rebalanceBatchSize配置属性定制(默认大小是512K)。
例如,如果您希望rebalancer以100 ms间隔发送2MB的数据,您应该提供以下配置:
<bean class="org.apache.ignite.configuration.IgniteConfiguration">
    ...
    <property name="cacheConfiguration">
        <bean class="org.apache.ignite.configuration.CacheConfiguration">               
            <!-- Set batch size. -->
                    <property name="rebalanceBatchSize" value="#{2 * 1024 * 1024}"/>

                    <!-- Set throttle interval. -->
                    <property name="rebalanceThrottle" value="100"/>
            ... 
        </bean
    </property>
</bean> 
CacheConfiguration cacheCfg = new CacheConfiguration();

cacheCfg.setRebalanceBatchSize(2 * 1024 * 1024);

cacheCfg.setRebalanceThrottle(100);

IgniteConfiguration cfg = new IgniteConfiguration();

cfg.setCacheConfiguration(cacheCfg);

// Start Ignite node.
Ignition.start(cfg);

14.5 Configuration

缓存再平衡行为可以通过设置以下配置属性来定制:
CacheConfiguration
方法描述默认值
setRebalanceMode分布式缓存的再平衡模式CacheRebalanceMode.ASYNC
setRebalancePartitionedDelay当一个节点加入或者离开拓扑,应该自动进行再平衡操作的延迟时间。如果你打算重启节点,那么这个延迟时间是必须设置的,或者如果你计划启动多个节点同时或一个接一个,不想重新分区和重新平衡直到所有节点启动起来0 (no delay)
setRebalanceBatchSize在一个重新平衡消息中加载的大小(字节)。再平衡算法会在发送数据之前将每个节点上的数据集拆分为多个批。512K
setRebalanceThrottle详情请参见重新平衡消息节流部分。0 (throttling disabled)
setRebalanceOrder重新平衡的优先级。重新平衡可以对那些以SYNC or ASYNC的再平衡模式的缓存设置一个非零的优先级值 值越小,先进行再平衡。默认情况下,不进行重新平衡。0
setRebalanceBatchesPrefetchCount为了获得更好的再平衡性能,供应商节点可以在重新启动时提供多个批次,并为每一个下一个请求提供一个新的请求。设置在重新启动时由供应节点生成的批数。2
setRebalanceTimeout节点之间交换信息时候,等待重新平衡信息的超时时间10秒

IgniteConfiguration

方法描述默认值
setRebalanceThreadPoolSize可用于重新平衡的最大的线程数1(对网格操作的影响最小)

15.Topology Validation(拓扑验证)

拓扑验证器用于验证集群拓扑对于进一步的缓存操作是有效的。
每次集群拓扑发生变化时,都会调用拓扑验证器(一个新节点加入或现有节点失败或离开)。如果没有配置拓扑验证器,那么集群拓扑总是被认为是有效的。
每当TopologyValidator.validate(Collection)方法返回true,那么拓扑结构被认为是对某个缓存时有效的,并且缓存中的所有操作都将被允许继续进行。否则,缓存中的所有更新操作都受到以下异常的限制:
>CacheException :所有更新操作(put、remove等)将抛出CacheException 。
>IgniteException :事务提交尝试时候将会被抛出。
返回false并声明拓扑无效时,拓扑验证器可以在下一次拓扑更改发生时返回到正常状态。
Example:
...
for (CacheConfiguration cCfg : iCfg.getCacheConfiguration()) {
    if (cCfg.getName() != null) {
        if (cCfg.getName().equals(CACHE_NAME_1))
            cCfg.setTopologyValidator(new TopologyValidator() {
                @Override public boolean validate(Collection<ClusterNode> nodes) {
                    return nodes.size() == 2;
                }
            });
        else if (cCfg.getName().equals(CACHE_NAME_2))
            cCfg.setTopologyValidator(new TopologyValidator() {
                @Override public boolean validate(Collection<ClusterNode> nodes) {
                    return nodes.size() >= 2;
                }
            });
    }
}
...
在本例中,更新操作将被允许以名称缓存
>CACHE_NAME_1假设集群正好有两个节点
>CACHE_NAME_2:假设集群包含至少两个节点
配置 拓扑验证器可以从代码或配置XML,通过CacheConfiguration.setTopologyValidator(TopologyValidator)方法。

PS:其实还有两个小节,但是太重要了,所以单独抽出,作为单独的文章.

  • 1
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值