1. IgniteDataStream数据流
后面会单独的做数据存储的快速学习章节,这里只是带着体验下数据流网格
public static void main(String[] args) {
checkMinMemory(MIN_MEMORY);
try(Ignite ignite = Ignition.start("examples/config/example-ignite.xml")){
try(IgniteCache<Integer,Integer> cache = ignite.getOrCreateCache(CACHE_NAME)){
try(IgniteDataStreamer<Object, Object> stream = ignite.dataStreamer(CACHE_NAME)){
stream.perNodeBufferSize(1024);
stream.perNodeParallelOperations(8);
for(int i = 1 ; i <= ENTRY_COUNT ;i++){
stream.addData(i, i);
if(i % 100000 == 0){
System.out.println(i + "条数据已经被缓存!");
}
}
}
}
}
}
代码并不长,首行只是保护代码,避免内存不足问题。
1.1 IgniteDataStream
代码中,我们通过Ignite#dataStream(cacheName)方法,获取与给定的缓存名称关联的IgniteDataStream数据流的新实例。数据流对象负责将外部数据加载到内存数据网格中。它通过适当的缓冲更新、正确地将键映射到负责该数据的存储的节点,以确保尽可能少的数据移动和最优的网络和内存利用率。
注意:streamer将通过多个内部线程并发地流数据,所以数据可能以不同于我们添加数据的顺序而进入远程节点。
IgniteDataStream并不是唯一的将数据加载进内存的方式。你可以使用IgniteCache#loadCache(IgniteBiPredicate, Object...)方法来讲数据从底层数据存储加载到内存里。你还可以使用标准的put()/putAll()方法.但时不会比通过数据流的性能来的快。数据可以从底层数据存储需求,添加访问时不需要显式的数据添加步骤。
配置:(直接放在方法里讲吧)
方法 | 参数 | 介绍 |
---|---|---|
public void perNodeBufferSize(int bufSize); | bufSize:每个节点缓冲区大小 | 设置每个节点键值对缓冲区的大小。在调用addData(Object, Object)方法添加数据进内存时候,他们不是马上发送到内存数据网格,而是为更好的性能和网络利用率而在内部进行缓冲。这个设置控制在数据发送到远程节点之前,内部每个节点缓冲区的大小。 |
public void perNodeParallelOperations(int parallelOps); | parallelOps:单个节点的并行流操作的最大数量。 | 有时候通过addData(Object,Object)将数据添加进内存,速度要比put方法快。在这种情况下,在收到之前收到的回复之前,新的缓冲流消息也是会被发送出去的。这可能会导致在本地和远程节点上无限堆内存利用率增长。为了控制内存利用,这个设置限制了在远程节点上处理的最大允许的并行缓冲流消息数量。如果超过这个数,ddData(Object, Object)方法会阻塞控制内存利用率。默认的他是用DFLT_MAX_PARALLEL_OPS这个值,即16 |
public void autoFlushFrequency(long autoFlushFreq); | autoFlushFreq:刷新频率,设置为0时候表示禁用 | 设置自动刷新频率,以毫秒为单位。本质上来讲,就是streamer尝试向远程节点提交到目前为止的所有数据之后的时间长度。注意:这个方法并不会确保在这个具体的尝试之后,数据将被交付,但它不会消失。 |
public void allowOverwrite(boolean allowOverwrite) | allowOverwrite:设置可以覆盖缓存中的现有值的标记。 | 该方法是设置可以覆盖缓存中的现有值的标记,如果禁用此标志,数据流将执行得更好。注意,当此标志为false时,更新将不会传播到缓存存储。(skipStore()方法会暗中将allowOverwrite设置为True)。默认情况下,是false,即关闭的。但是通过receiver(StreamReceiver)方法,使用自动以接收器的时候,设置这个是什么就没有作用了。 |
public void receiver(StreamReceiver rcvr) | rcvr:自定义的流接收器 | 为这个数据流设置定制的流接收器。它定义如何用添加的条目更新缓存。它允许提供用户定义的自定义逻辑,以最有效和灵活的方式更新缓存 |
deployClass(Class) | class:为给定数据提供装载的类加载器 | 对等部署的可选的部署配置类 |
1.2 添加数据 addData
方法 | 参数 | 介绍 |
---|---|---|
public IgniteFuture addData(K key, @Nullable V val) | key:键。val:值 | 为远程节点上的流添加数据。这个方法啊可以被多线程调用,以加速流的速度。注意:streamer将通过多个内部线程并发流数据,添加到stream中的数据可能以不同的顺序到达远程节点。注意:如果IgniteDataStreamer#allowOverwrite()方法设置了false(就是默认值),那么数据流将不会覆盖掉已有的键值对数据,以保证性能。 |
public IgniteFuture addData(Map.Entry entry) | entry:还是键值对,只是以Entry形式展示 | 基本同上 |
public IgniteFuture addData(Collection> entries) | 表达键值对的Entry的集合 | 基本同上 |
public IgniteFuture addData(Map entries) | entries:Map形式的键值对 | 基本同上 |
2. 持续查询 ContinuousQuery
public static void main(String[] args) {
try(Ignite ignite = Ignition.start("examples/config/example-ignite.xml")){
try(IgniteCache<Integer,Integer> cache = ignite.getOrCreateCache("DEFAULT_CACHE")){
for(int i = -10;i < 10;i++){
cache.put(i, i);
}
ContinuousQuery<Integer, Integer> continuousQuery = new ContinuousQuery<>();
//第一步:设置初始化查询(值取偶数)
continuousQuery.setInitialQuery(new ScanQuery<>(new IgniteBiPredicate<Integer, Integer>(){
@Override
public boolean apply(Integer key, Integer val) {
if(key > 0 && key % 2 == 0){
return true;
}
return false;
}
}));
//第二步:远程过滤器(只返回大于0的数值)
continuousQuery.setRemoteFilterFactory(new Factory<CacheEntryEventFilter<Integer, Integer>>(){
@Override
public CacheEntryEventFilter<Integer, Integer> create() {
return new CacheEntryEventSerializableFilter<Integer, Integer>() {
@Override
public boolean evaluate(CacheEntryEvent<? extends Integer, ? extends Integer> evts)
throws CacheEntryListenerException {
if(evts.getKey() > 0){
return true;
}
return false;
}
};
}
});
//第三步:设置本地监听器
continuousQuery.setLocalListener(new CacheEntryUpdatedListener<Integer, Integer>() {
@Override
public void onUpdated(Iterable<CacheEntryEvent<? extends Integer, ? extends Integer>> evts)
throws CacheEntryListenerException {
for (CacheEntryEvent<? extends Integer, ? extends Integer> entry : evts) {
Integer key = entry.getKey();
Integer value = entry.getValue();
System.out.println("此时,键 : " + key + "的值变为了: " + value);
}
}
});
//第四步:执行查询
QueryCursor<Entry<Integer, Integer>> crusor = cache.query(continuousQuery);
for (Entry<Integer, Integer> entry : crusor) {
System.out.println("初始化时候:键: " + entry.getKey() + "的值是: " + entry.getValue() );
}
//因为数据的更改以及添加可以影响本地监听器,所以我们做一次测试
for (int i = -10; i < 20; i++)
cache.put(i, i + 10);
}
}
}
输出打印
初始化时候:键: 2的值是: 2
初始化时候:键: 4的值是: 4
初始化时候:键: 6的值是: 6
初始化时候:键: 8的值是: 8
此时,键 : 1的值变为了: 11
此时,键 : 2的值变为了: 12
此时,键 : 3的值变为了: 13
此时,键 : 4的值变为了: 14
此时,键 : 5的值变为了: 15
此时,键 : 6的值变为了: 16
此时,键 : 7的值变为了: 17
此时,键 : 8的值变为了: 18
此时,键 : 9的值变为了: 19
此时,键 : 10的值变为了: 20
此时,键 : 11的值变为了: 21
此时,键 : 12的值变为了: 22
此时,键 : 13的值变为了: 23
此时,键 : 14的值变为了: 24
此时,键 : 15的值变为了: 25
此时,键 : 16的值变为了: 26
此时,键 : 17的值变为了: 27
此时,键 : 18的值变为了: 28
此时,键 : 19的值变为了: 29
除了apidiamante之外,其他代码难度并不高。
首先对获取到的缓存,插入键为-10~10的键值对。然后我们初始化一个持续查询的ContiousQuery对象。
对于该Query子对象,我们重点需要做的就是三步:
1.初始化查询:即在query(Query query)方法执行时候率先执行的查询操作,我们从输出打印可看到,我们存储了-10~9的数字,但是初始化时候返回的却只有2,4,6,8这四行,说明这个操作时在查询时候执行的。
2.远程过滤器:通过代码可以看到,我们最后,对-10到19做了一次插入,由于-10~9是原本就存在的,那么既然被修改了,就需要给我们这边监听器发送报告,但是日志显示,负数的-10~-1都没有,因为被这个远程过滤器过滤了。
3.本地监听:这个比较易懂,当我们所监听的数据被新建或者修改了,且通过了远程监听器的谓词过滤,那么就会输出到本地监听器上。你可以想象成rabbitMQ中,我们监听自己所感兴趣的队列是一个道理。
2.1 setInitialQuery 设置初始化查询
这个方法会在持续监听器被注册之前就执行,因此,它并不会触发本地监听。
在这里需要传给入参一个Query对象。Query查询在下面会讲,这里先不多言。可以参考Query接口的实现类,共有多种查询对象。
我们在这里使用的是Query接口的实现类:ScanQuery.它对缓存条目进行扫描查询。如果没有设置谓词,将接受所有的条目。
2.2 setRemoteFilterFactory 设置远程拦截器工厂
首先呢,其实这里是有一个简便方法的,就是setRemoteFilter方法,但是它已经过时了,因此暂时不讲它了。
该方法是设置可选的键值过滤工厂。这个工厂生产过滤器在缓存条目被发送到主节点之前都被调用。
该方法的入参需要一个Factory的对象,匿名实现它,我们需要实现其create方法
@Override public CacheEntryEventFilter<Integer, String> create() {}
这个create方法的目的是创建一个过滤器,而这个过滤器也是一个接口,即:CacheEntryEventFilter。需要我们自己实现它
ignite有一个注解,叫做@IgniteAsyncCallback,上面我们自己实现的CacheEntryEventFilter实现类,如果我们以这个注解对该类注解,那么就是异步操作。否则就是同步操作,下一个小节我们会将异步持续查询。
2.3 setLocalListener 本地监听
设置本地回调。这个回调只在接收新更新时在本地节点中调用。
如果本地监听器被@IgniteAsyncCallback注解,那么他会异步执行。
异步回调池可以以IgniteConfiguration#getAsyncCallbackPoolSize()方法查看。
3. 异步持续查询
基本步骤与上面类似,只做部分讲解
public static void main(String[] args) {
try(Ignite ignite = Ignition.start("examples/config/example-ignite.xml")){
try(IgniteCache<Integer,Integer> cache = ignite.getOrCreateCache("DEFAULT_CACHE")){
for(int i = -10;i < 10;i++){
cache.put(i, i);
}
ContinuousQuery<Integer, Integer> continuousQuery = new ContinuousQuery<>();
int asyncCallbackPoolSize = ignite.configuration().getAsyncCallbackPoolSize();
System.out.println("异步池的大小为:" + asyncCallbackPoolSize);
//step1:初始化查询(全部都查询)
continuousQuery.setInitialQuery(new ScanQuery<>(new IgniteBiPredicate<Integer, Integer>() {
@Override
public boolean apply(Integer key, Integer val) {
return true;
}
}));
//step2:远程过滤器
continuousQuery.setRemoteFilterFactory( new Factory<CacheEntryEventFilter<Integer, Integer>>(){
@Override
public CacheEntryEventFilter<Integer, Integer> create() {
return new MyCacheEntryEventFilter();
}
});
//step3:本地监听器
continuousQuery.setLocalListener(new MyCacheEntryUpdatedListener());
QueryCursor<Entry<Integer, Integer>> cursor = cache.query(continuousQuery);
for (Entry<Integer, Integer> entry : cursor) {
System.out.println("初始化查询结果:key : " + entry.getKey() + " 值:" + entry.getValue());
}
//做数据修改
for(int i = -10;i < 10;i++){
cache.put(i, i + 10);
}
}
}
}
@IgniteAsyncCallback
public static class MyCacheEntryEventFilter implements CacheEntryEventFilter<Integer,Integer>{
@Override
public boolean evaluate(CacheEntryEvent<? extends Integer, ? extends Integer> evts)
throws CacheEntryListenerException {
if(evts.getKey() > 0){
return true;
}else{
return false;
}
}
}
@IgniteAsyncCallback
public static class MyCacheEntryUpdatedListener implements CacheEntryUpdatedListener<Integer,Integer>{
@Override
public void onUpdated(Iterable<CacheEntryEvent<? extends Integer, ? extends Integer>> evts)
throws CacheEntryListenerException {
for (CacheEntryEvent<? extends Integer, ? extends Integer> entry : evts) {
System.out.println("更改后,key:" + entry.getKey() + "的值变为了==》" + entry.getValue());
}
}
}
输出日志:
异步池的大小为:8
初始化查询结果:key : -1 值:-1
初始化查询结果:key : 0 值:0
初始化查询结果:key : -2 值:-2
初始化查询结果:key : 1 值:1
初始化查询结果:key : -3 值:-3
初始化查询结果:key : 2 值:2
初始化查询结果:key : -4 值:-4
初始化查询结果:key : 3 值:3
初始化查询结果:key : -5 值:-5
初始化查询结果:key : 4 值:4
初始化查询结果:key : -6 值:-6
初始化查询结果:key : 5 值:5
初始化查询结果:key : -7 值:-7
初始化查询结果:key : 6 值:6
初始化查询结果:key : -8 值:-8
初始化查询结果:key : 7 值:7
初始化查询结果:key : -9 值:-9
初始化查询结果:key : 8 值:8
初始化查询结果:key : -10 值:-10
初始化查询结果:key : 9 值:9
更改后,key:2的值变为了==》12
更改后,key:1的值变为了==》11
更改后,key:3的值变为了==》13
更改后,key:5的值变为了==》15
更改后,key:6的值变为了==》16
更改后,key:4的值变为了==》14
更改后,key:7的值变为了==》17
更改后,key:8的值变为了==》18
更改后,key:9的值变为了==》19
通过日志可以看到我们的持续查询起作用了。
至于异步操作,我们在上一节讲了,只需一个注解:@IgniteAsyncCallback
4. 过期策略Expiry Policies
过期策略指定在考虑缓存条目过期之前必须经过的时间。时间可以从创建、最后访问或修改时间计数开始计算。
可以使用任何ExpiryPolicy预定义的实现设置过期策略:
public static void main(String[] args) {
//一共5种回收策略,故分5个缓存对象来测试
try (Ignite ignite = Ignition.start("examples/config/example-ignite.xml")) {
System.out.println();
System.out.println(">>> Cache continuous query example started.");
CacheConfiguration cfg1 = new CacheConfiguration<Integer, Integer>(CACHE_NAME1);
cfg1.setExpiryPolicyFactory(CreatedExpiryPolicy.factoryOf(new Duration(TimeUnit.SECONDS,10)));
CacheConfiguration cfg2 = new CacheConfiguration<Integer, Integer>(CACHE_NAME2);
cfg2.setExpiryPolicyFactory(AccessedExpiryPolicy.factoryOf(new Duration(TimeUnit.SECONDS,10)));
CacheConfiguration cfg3 = new CacheConfiguration<Integer, Integer>(CACHE_NAME3);
cfg3.setExpiryPolicyFactory(ModifiedExpiryPolicy.factoryOf(new Duration(TimeUnit.SECONDS,10)));
CacheConfiguration cfg4 = new CacheConfiguration<Integer, Integer>(CACHE_NAME4);
cfg4.setExpiryPolicyFactory(TouchedExpiryPolicy.factoryOf(new Duration(TimeUnit.SECONDS,10)));
CacheConfiguration cfg5 = new CacheConfiguration<Integer, Integer>(CACHE_NAME5);
cfg5.setExpiryPolicyFactory(EternalExpiryPolicy.factoryOf());
// Auto-close cache at the end of the example.
try (IgniteCache<Integer, Integer> cache1 = ignite.getOrCreateCache(cfg1);
IgniteCache<Integer, Integer> cache2 = ignite.getOrCreateCache(cfg2);
IgniteCache<Integer, Integer> cache3 = ignite.getOrCreateCache(cfg3);
IgniteCache<Integer, Integer> cache4 = ignite.getOrCreateCache(cfg4);
IgniteCache<Integer, Integer> cache5 = ignite.getOrCreateCache(cfg5)) {
createdExpiryPolicyTest(cache1);
accessedExpiryPolicyTest(cache2);
modifiedExpiryPolicyTest(cache3);
touchedExpiryPolicyTest(cache4);
eternalExpiryPolicyTest(cache5);
}
}
}
public static void thread(int second){
try {
Thread.sleep(second * 1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public static void createdExpiryPolicyTest(IgniteCache<Integer, Integer> cache1) {
cache1.put(1, 1);
thread(5);
//中间穿插修改和读取操作
cache1.put(1, 11);
Integer v1 = cache1.get(1);
assert v1 == 11;
//到首次插入数据已经11秒了,我们设置的是10秒的存在时间,而且策略是created,因此是读不出数据的
thread(6);
v1 = cache1.get(1);
assert v1 == null;
}
public static void accessedExpiryPolicyTest(IgniteCache<Integer, Integer> cache2) {
cache2.put(2, 2);
thread(5);
//中间穿插着读取操作
Integer v2 = cache2.get(2);
assert v2 == 2;
thread(6);
//两次睡眠时间超了10秒,但是因为期间发生了一次读取,那么现在读取的话,还是有值的
v2 = cache2.get(2);
assert v2 == 2;
thread(5);
//在上次读取后,穿插一次修改操作
cache2.put(2, 22);
thread(6);
//同上次的修改操作过了6秒,但是同上次读取过了11秒,而且是accessed策略,所以读取不到值了。
v2 = cache2.get(2);
assert v2 == null;
}
public static void modifiedExpiryPolicyTest(IgniteCache<Integer, Integer> cache3) {
cache3.put(3, 3);
thread(5);
//期间做了一次修改操作
cache3.put(3, 33);
thread(6);
//距离新建数据已经过了11秒,但是期间做了一次修改操作,且是modified模式,所以现在是可以取到值的
Integer v3 = cache3.get(3);
assert v3 == 33;
thread(5);
//期间做一次查询
v3 = cache3.get(3);
assert v3 == 33;
thread(6);
//距离上次查询过去了5秒,但是距离上次修改过了11,因为是modified模式,所以现在取不到值
v3 = cache3.get(3);
assert v3 == null;
}
public static void touchedExpiryPolicyTest(IgniteCache<Integer, Integer> cache4) {
cache4.put(4, 4);
thread(5);
//期间做了一次修改操作
cache4.put(4, 44);
thread(6);
//距离新建数据已经过了11秒,但是期间做了一次修改操作,且是touched模式,所以现在是可以取到值的
Integer v4 = cache4.get(4);
assert v4 == 44;
thread(5);
//期间做一次查询
v4 = cache4.get(4);
assert v4 == 44;
thread(6);
//距离上次查询过去了5秒,距离上次修改过了11,因为是touched模式,所以现在还是可以取到值
v4 = cache4.get(4);
assert v4 == 44;
}
public static void eternalExpiryPolicyTest(IgniteCache<Integer, Integer> cache5) {
//永久存在的,所以没怎么写
cache5.put(5, 5);
Integer v5 = cache5.get(5);
System.out.println(v5);
}
4.1 ExpiryPolicy 过期策略(接口)
下面是ignite所支持的过期策略
className | 创建时间 | 最后访问时间 | 最后修改时间 |
---|---|---|---|
CreatedExpiryPolicy | 使用 | ||
AccessedExpiryPolicy | 使用 | 使用 | |
ModifiedExpiryPolicy | 使用 | 使用 | |
TouchedExpiryPolicy | 使用 | 使用 | 使用 |
EternalExpiryPolicy |
解释下上面的表。标识使用的,标识该策略支持。我们以AccessedExpiryPolicy策略为例子:
PS:我们就以样例为准(10秒的过期时间)
该模式支持创建时间和最后访问时间两种,不支持最后修改时间。
也就是说:
1.一个cache新建一条缓存后,期间什么也没做,10秒后,是要过期的。
2.一个cache新建一条缓存后,在10秒内发生了访问,那么过期时间就重置
3.一个cache新建一条缓存后,在10秒内发生了修改,那么不影响过期时间
过期策略可以在CacheConfiguration设置。此策略将用于缓存内的所有条目
cfg.setExpiryPolicyFactory(CreatedExpiryPolicy.factoryOf(Duration.ZERO))
此外,可以更改或设置缓存中的单个操作的过期策略,我们所配置的的策略将用于在返回的缓存实例上所调用的每个操作。。
IgniteCache<Object, Object> cache = cache.withExpiryPolicy(
new CreatedExpiryPolicy(new Duration(TimeUnit.SECONDS, 5)));
4.2. Factory< ? extends ExpiryPolicy>
我们现在所讲的,是CacheConfiguration#setExpiryPolicyFactory方法的入参,这个工厂我们并不陌生,他在上面的持续查询中也出现了。
好在ignite提供了一系列的工具类,比如我们所使用的CreatedExpiryPolicy、AccessedExpiryPolicy、ModifiedExpiryPolicy、TouchedExpiryPolicy、EternalExpiryPolicy这些都是一些实现了ExpiryPolicy接口的实现类,我们可以使用其factory方法,实例化一个相应策略的工厂对象出来,但是factory方法需要一个过期时间对象,就是下面讲的Duration。
注意L如果确实有特殊需要,我们可以自定义这个Factory,下面以自定义自己的CreatedExpiryPolicy为例:
cfg1.setExpiryPolicyFactory(new Factory<ExpiryPolicy>() {
@Override
public ExpiryPolicy create() {
return new ExpiryPolicy() {
@Override
public Duration getExpiryForUpdate() {
// TODO Auto-generated method stub
return null;
}
@Override
public Duration getExpiryForCreation() {
// TODO Auto-generated method stub
return new Duration(TimeUnit.SECONDS,10);
}
@Override
public Duration getExpiryForAccess() {
// TODO Auto-generated method stub
return null;
}
};
}
});
4.3 javax.cache.expiry.Duration
该类是一个时间对象,用来表示过期的事件是多久。
该类有一些静态属性,方便使用,比如说Duration.FIVE_MINUTES、Duration.ZERO等,如果要自定义过期时间,那么可以通过创建对象来做,我们的代码就是这么做的。
4.4 Eager TTL
因为不是很值得作为一块讲,就放在这里一起讲了。
过期的条目可以从缓存中删除,或者当它们被不同的缓存操作访问时。如果至少有一个配置为Eager TTL的缓存,那么ignite将创建一个线程来清除后台的过期条目。
Eager TTL可以通过cacheconfiguration启用或禁用。eagerTtl属性(默认值为true):
//xml版本
<bean class="org.apache.ignite.configuration.CacheConfiguration">
<property name="eagerTtl" value="true"/>
</bean>
//java版本
cacheConfiguration.setEagerTtl(false)
5. 回收策略
Apache ignite支持两种不同的数据清除策略:
1.page-based eviction,该方式处理栈外内存页
2.cache entries based eviction:该方式处理栈内内存页
5.1 page-based eviction
该回收策略是配置MemoryPolicyConfiguration来做的,我们在Ignite架构篇有讲。虚拟内存是由一个或者多个的由MemoryPolicyConfigurations所配置的内存区域所组成的。默认情况下,一个区域不断的增长它的大小,直到到达我们所配置的最大内存为止。为了避免可能的区域耗竭,您可能需要设置一个数据页清除模式--通过设置MemoryPolicyConfiguration.setPageEvictionMode(...)的参数,或者是random-LRU,或者是Random-2-LRU。回收策略会追踪数据页的使用,根据模式的实现将其中一些删除。
5.1.1 random-LRU
//xml版本
<bean class="org.apache.ignite.configuration.MemoryConfiguration">
<!-- Defining additional memory poolicies. -->
<property name="memoryPolicies">
<list>
<!--
定义一个20GB内存区域的策略,以RANDOM_LRU 为其回收策略
-->
<bean class="org.apache.ignite.configuration.MemoryPolicyConfiguration">
<property name="name" value="20GB_Region_Eviction"/>
<!-- 初始化大小为5GB. -->
<property name="initialSize" value="#{5 * 1024 * 1024 * 1024}"/>
<!-- 最大内存大小为 20 GB. -->
<property name="maxSize" value="#{20 * 1024 * 1024 * 1024}"/>
<!-- 启用回收策略,并设置为 RANDOM_LRU . -->
<property name="pageEvictionMode" value="RANDOM_LRU"/>
</bean>
</list>
...
</property>
...
</bean>
// 定义额外的内存策略.
MemoryConfiguration memCfg = new MemoryConfiguration();
// 定义一个20GB内存区域的策略,以RANDOM_LRU 为其回收策略
MemoryPolicyConfiguration memPlc = new MemoryPolicyConfiguration();
memPlc.setName("20GB_Region_Eviction");
// 初始化大小为5GB.
memPlc.setInitialSize(5L * 1024 * 1024 * 1024);
//最大内存大小为 20 GB.
memPlc.setMaxSize(20L * 1024 * 1024 * 1024);
// 启用回收策略,并设置为 RANDOM_LRU .
memPlc.setPageEvictionMode(DataPageEvictionMode.RANDOM_LRU);
// 设置新的内存策略
memCfg.setMemoryPolicies(memPlc);
Random-LRU算法工作如下:
1.一旦配置了内存策略定义的内存区域,就会分配一个非堆数组来跟踪每个单独的数据页的“最后一次使用”时间戳。
2.当访问数据页时,它的时间戳会在跟踪数组中更新。
3.当需要将内存页回收时,该算法从跟踪数组中随机选择5个索引,根据距离当前最近的时间戳将内存页进行回收。
如果有的索引指向了非数据页(即索引页或者系统页),那么算法就跳向另外一个页继续算法操作。
5.1.2 Random-2-LRU
该算法是一个scan-resistant版本的Random-LRU, 如果想启用它,那么就需要将DataPageEvictionMode.RANDOM_2_LRU传递给MemoryPolicyConfiguration,基本与上面相似。
在Random-2-LRU 中,最近的两个访问时间戳存储在每个数据页中。当被回收的时候,该算法从跟踪数组中随机选择5个索引,并在两个最新的时间戳中进行最小值,以进一步与被选为回收候选的其他4个页面的最小值进行比较。
在处理one-hit-wonder问题时候,Random-2-LRU优于LRU算法,one-hit-wonder问题就是:如果一个数据页面很少被访问,但偶尔访问一次,它就会被长期保护。
5.1.3 Random-LRU vs. Random-2-LRU
在Random-LRU中,最近的时间戳保存在数据页里。但是在Random-2-LRU,则是保存最近的两次访问的时间戳。
PS:Eviction Triggering
默认情况下,当总内存区域的消耗达到90%时,将触发数据页面的驱逐算法。使用MemoryPolicyConfiguration.setEvictionThreshold(…),参数是一个double值,就是你希望的当内存达到多少时候启动回收算法。
5.2 On-Heap Cache Entries Based Eviction
如果通过 CacheConfiguration.setOnheapCacheEnabled(...)设置启动了on-heap缓存特性,那么ignite的内存是允许你在Java堆中存储热数据的。一旦 on-heap 缓存开启了,您可以使用一个缓存条目回收策略来管理不断增长的堆缓存。
回收策略控制可以存储在缓存的堆内存中的元素的最大数量。当到达最大的堆缓存大小时,将从Java堆中逐出条目。
PS:清除策略只从Java堆中删除缓存条目。存储在非堆页内存中的条目保持不变。
一些驱逐策略支持通过内存大小限制进行批量回收。如果启动了批量回收策略,那么当缓存大小变成了batchSize的那个数值的时候,就会启动回收。如果根据内存大小限制的驱逐策略被启动了,那么当缓存条目超了这个数值时候,内存回收旧会开始。
PS:只有在不设置最大内存限制的情况下,才支持批量清除。
在Apache回收策略是可插拔的,并通过EvictionPolicy接口控制。每个缓存更改都通知回收策略的实现,并定义了从页面内存的堆缓存中选择要回收的条目的算法。
EvictionPolicy接口的实现:
FifoEvictionPolicy
LruEvictionPolicy
SortedEvictionPolicy
IgfsPerBlockLruEvictionPolicy
5.2.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>
5.2.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>
5.2.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>
6.Data Rebalancing
预加载来自其他网格节点的数据以维护数据一致性。
//第一台服务器
public static void main(String[] args) throws IgniteException {
Ignition.start("examples/config/example-ignite.xml");
while(true){
}
}
//第二台服务器
public static void main(String[] args) {
try(Ignite ignite = Ignition.start("examples/config/example-ignite.xml")){
//设置缓存配置对象
CacheConfiguration<Integer, Integer> cacheConfiguration = new CacheConfiguration<Integer, Integer>("REBALANCE_CACHE");
cacheConfiguration.setRebalanceMode(CacheRebalanceMode.SYNC);
IgniteCache<Integer, Integer> cache = ignite.getOrCreateCache(cacheConfiguration);
List<Integer> l = new ArrayList<>();
for(int i = 0;i < 10;i++){
l.add(i);
cache.put(i, i + 100);
}
try {
Thread.sleep(4000);
} catch (InterruptedException e) {
e.printStackTrace();
}
Affinity<Object> affinity = ignite.<Object>affinity("REBALANCE_CACHE");
Map<ClusterNode, Collection<Object>> mapKeysToNodes = new HashMap<>();
System.out.println(mapKeysToNodes.size());
boolean flag = true;
while(flag){
mapKeysToNodes = affinity.mapKeysToNodes(l);
System.out.println("节点个数" + mapKeysToNodes.size());
if(mapKeysToNodes.size() > 1){
flag = false;
}
}
Set<Entry<ClusterNode, Collection<Object>>> entrySet = mapKeysToNodes.entrySet();
for (Entry<ClusterNode, Collection<Object>> entry : entrySet) {
ClusterNode node = entry.getKey();
System.out.println("=============start: " + node.hostNames() + "===================");
Collection<Object> values = entry.getValue();
for (Object object : values) {
System.out.println(object);
}
System.out.println("==================end===================");
}
while(true){
}
}
}
//第三台服务器
public static void main(String[] args) {
try(Ignite ignite = Ignition.start("examples/config/example-ignite.xml")){
//设置缓存配置对象
List<Integer> l = new ArrayList<>();
for(int i = 0;i < 10;i++){
l.add(i);
}
Affinity<Object> affinity = ignite.<Object>affinity("REBALANCE_CACHE");
boolean flag = true;
Map<ClusterNode, Collection<Object>> mapKeysToNodes = new HashMap<>();
while(flag){
mapKeysToNodes = affinity.mapKeysToNodes(l);
if(mapKeysToNodes.size() >= 3){
flag = false;
}
}
Set<Entry<ClusterNode, Collection<Object>>> entrySet = mapKeysToNodes.entrySet();
for (Entry<ClusterNode, Collection<Object>> entry : entrySet) {
ClusterNode node = entry.getKey();
System.out.println("=============start: " + node.hostNames() + "===================");
Collection<Object> values = entry.getValue();
for (Object object : values) {
System.out.println(object);
}
System.out.println("==================end===================");
}
while(true){
}
}
}
PS:顺序启动
//服务器日志1
[16:05:17] Topology snapshot [ver=1, servers=1, clients=0, CPUs=4, heap=0.87GB]
[16:07:59] Topology snapshot [ver=2, servers=2, clients=0, CPUs=4, heap=1.7GB]
[16:09:19] Topology snapshot [ver=3, servers=3, clients=0, CPUs=4, heap=2.6GB]
[16:08:00] Topology snapshot [ver=2, servers=2, clients=0, CPUs=4, heap=1.7GB]
0
节点个数2
=============start: [10.10.18.75, IT-201606271616]===================
0
3
5
6
7
8
==================end===================
=============start: [10.10.18.75, IT-201606271616]===================
1
2
4
9
==================end===================
[16:09:19] Topology snapshot [ver=3, servers=3, clients=0, CPUs=4, heap=2.6GB]
[16:09:20] Topology snapshot [ver=3, servers=3, clients=0, CPUs=4, heap=2.6GB]
=============start: [10.10.18.75, IT-201606271616]===================
0
1
6
7
==================end===================
=============start: [10.10.18.75, IT-201606271616]===================
3
5
8
==================end===================
=============start: [10.10.18.75, IT-201606271616]===================
2
4
9
==================end===================
本来是要以事件的模式,来验证这个数据重新平衡的,但是开启了平衡事件,却一直报错。有大神知道话,望不吝赐教。
讲解下上述代码:
代码是以分区模式存储的缓存~~~~~~
1.第一段代码只是启动一台服务器
2.第二段代码首先创建缓存对象,然后为其设置数据再平衡策略:CacheRebalanceMode.SYNC,然后插入数据。因为有短暂延迟,所以我们做了个while循环,知道取到的映射的数量大于1个了才表示这时数据已经完全准备好了。然后通过打印我们可以看到,两个节点均分了我们的测试数据。
3.第三段代码启动了后,就while循环,等到rebalance结束,结束的标识其实就是我们的键的映射集合的size = 3(一共就三个节点嘛).然后通过日志,验证了rebalance(其实也不用验证,怎么可能不存在嘛)。
需要注意的是:rebalance是需要时间,并不是服务器启动了,数据也就rebalance完了。我们代码中以while循环,一直在等待rebalance结束。
6.1 CacheRebalanceMode
在CacheRebalanceMode enum中定义了再平衡模式。
CacheRebalanceMode | Description |
---|---|
SYNC | 同步平衡模式。在所有必需的数据从其他可用的网格节点加载之前,分布式缓存将不会启动。这意味着任何对公共API的调用都将被阻塞,直到重新平衡为止 |
ASYNC | 异步平衡模式。分布式缓存将立即启动,并将从后台的其他可用网格节点加载所有必需的数据。 |
NONE | 在这种模式下,不需要进行再平衡,这意味着当访问数据时,缓存将被加载到持久存储的需求中,或者将被显式地填充 |
在我们的样例中,使用的是同步的缓存平衡模式,
6.2 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为一个大值可能会显著增加平衡性能,影响您的应用程序吞吐量
6.3 Rebalance Message Throttling
当再平衡器将数据从一个节点转移到另一个节点时,它将整个数据集分解成批,并将每个批发送到一个单独的消息中。如果你的数据集很大,有很多信息要发送,CPU或网络可以被过度消耗。在这种情况下,在重新平衡的消息之间等待是合理的,这样,再平衡过程导致的负面性能影响最小化。这个时间间隔由CacheConfiguration的rebalanceThrottle 配置属性控制。它的默认值为0,这意味着消息之间不会有暂停。注意,单个消息的大小也可以由rebalanceBatchSize配置属性定制(默认大小是512K)。
例如,如果您希望rebalancer以100 ms间隔发送2MB的数据,您应该提供以下配置:
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);
6.4 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(对网格操作的影响最小) |
数据网格到此讲的七七八八,下一个主题是SQL网格