我正在使用Ignite 1.7.0并正在测试Apache Ignite的后写功能。提出这个问题的动机是为了更好地理解在Apache Ignite中启用后写功能时幕后发生的事情。
我有一个Ignite客户端程序,它会在测试缓存中插入20个条目(称之为" test_cache")。
Ignite服务器在同一台计算机上运行,但在不同的JVM上运行。
Ignite Cache具有以下配置设置:
所有其他属性都设置为默认值。
此外,还有一个为缓存配置的缓存存储,代码如下:
package com.ignite.genericpoc;
import java.util.Collection;
import java.util.Map;
import javax.cache.Cache.Entry;
import javax.cache.integration.CacheLoaderException;
import javax.cache.integration.CacheWriterException;
import org.apache.ignite.Ignite;
import org.apache.ignite.IgniteCache;
import org.apache.ignite.cache.store.CacheStore;
import org.apache.ignite.lang.IgniteBiInClosure;
import org.apache.ignite.resources.CacheNameResource;
import org.apache.ignite.resources.IgniteInstanceResource;
public class IgniteStoreTest implements CacheStore<String, String> {
@IgniteInstanceResource
Ignite gridReference;
@CacheNameResource
String cacheName;
@Override
public String load(String key) throws CacheLoaderException {
System.out.println("load method called for the key [ " + key + " ] and cache [ " + cacheName + " ] ");
return null;
}
@Override
public Map<String, String> loadAll(Iterable<? extends String> keys) throws CacheLoaderException {
IgniteCache<String, String> ic = gridReference.cache(cacheName);
int currentKeyNo = 0;
for (String key : keys) {
ic.put(key, "Value:" + currentKeyNo);
currentKeyNo++;
}
System.out.println("Got " + currentKeyNo + " entries");
return null;
}
@Override
public void write(Entry<? extends String, ? extends String> entry) throws CacheWriterException {
System.out.println("Write method called");
}
@Override
public void writeAll(Collection<Entry<? extends String, ? extends String>> entries) throws CacheWriterException {
System.out.println("Write all method called for [ " + entries.size() + " ] entries in the thread "
+ Thread.currentThread().getName());
System.out.println("Entries recieved by " + Thread.currentThread().getName() + " : " + entries.toString());
try {
Thread.sleep(60000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
@Override
public void delete(Object key) throws CacheWriterException {
System.out.println("Delete method called");
}
@Override
public void deleteAll(Collection<?> keys) throws CacheWriterException {
System.out.println("Delete All method called");
}
@Override
public void loadCache(IgniteBiInClosure<String, String> clo, Object... args) throws CacheLoaderException {
System.out.println("Load cache method called with " + args[0].toString());
}
@Override
public void sessionEnd(boolean commit) throws CacheWriterException {
System.out.println("Session End called");
}
}
我有意在writeAll()方法中按顺序调用Thread.sleep()方法,以模拟慢速数据库写入。
将数据加载到缓存中的Ignite Client的代码如下:
package com.ignite.genericpoc;
import java.util.ArrayList;
import java.util.List;
import javax.cache.configuration.FactoryBuilder;
import org.apache.ignite.Ignite;
import org.apache.ignite.IgniteCache;
import org.apache.ignite.Ignition;
import org.apache.ignite.configuration.CacheConfiguration;
public class IgnitePersistentStoreClientTest {
public static void main(String[] args) throws InterruptedException {
List<String> addressess = new ArrayList<>();
addressess.add("*.*.*.*:47500"); // Hiding the IP
Ignition.setClientMode(true);
Ignite i = IgniteConfigurationUtil.startIgniteServer(
IgniteConfigurationUtil.getIgniteConfiguration(false, IgniteTestConstants.GRID_NAME, addressess));
System.out.println("Client Started");
CacheConfiguration<String, String> ccfg = new CacheConfiguration<>();
ccfg.setName("Persistent_Store_Test_Cache");
ccfg.setCacheStoreFactory(FactoryBuilder.factoryOf(IgniteStoreTest.class));
ccfg.setReadThrough(true);
ccfg.setWriteThrough(true);
ccfg.setWriteBehindEnabled(true);
ccfg.setWriteBehindFlushSize(13);
ccfg.setWriteBehindFlushThreadCount(1);
System.out.println(ccfg.getWriteBehindBatchSize());
IgniteCache<String, String> ic = i.getOrCreateCache(ccfg);
System.out.println("Cache Created");
for (int t = 1; t <= 20; t++) {
System.out.println("Loading key "+t);
ic.put("Key:" + t,"Value: "+t);
System.out.println("Key "+ t + " loaded ");
}
System.out.println("Cache Loaded");
i.close();
}
}
执行如下:
首先启动Ignite服务器。
加载数据的Ignite Client在服务器之后启动。
由于writeAll()方法定义了60秒的睡眠,因此Ignite Client在写入第20个条目时会卡住。
另外,我可以在服务器日志中看到为两个线程调用writeAll()方法,其中Flush线程收到15个条目写入存储,并且一个系统线程已收到1个条目写信给商店。 Ignite Server日志如下:
在线程flusher-0中编写[15]条目调用的所有方法#66%test_grid%
在线程sys中编写[1]条目调用的所有方法 - #22%test_grid%
我可以理解Ignite Client put因为Write Behind缓存已满并且所有Flush线程也忙于写入数据而陷入写入该条目的位置。
以下是我需要明确理解的要点:
为什么客户端在插入第20个条目时被阻止,它应该在插入第14个条目时被阻止(基于13个条目的最大缓存大小)
为什么Flush线程只调用15个条目,而不是所有19个条目,因为我没有设置批量大小,默认为512.
使用writeAll()方法调用的系统线程是否与处理来自Ignite Client的请求放入第20个条目的线程相同。
考虑到我的Cache已启用后写并且写入顺序模式为PRIMARY_SYNC(默认)且缓存中没有备份,应阻止对缓存的任何put调用,直到主节点能够提交写入为止。这是否也意味着能够将条目放入Write Behind缓存中。
如果在服务器中存储条目,Ignite Server会将条目的两个副本分别用于存储,一个用于后写高速缓存。或者使用相同的条目参考。
感谢您耐心阅读这个问题。如果问题太冗长,我会道歉,但内容对于向有关受众阐述情况至关重要。
答案 0 :(得分:2)
后写式商店在引擎盖下有背压控制。这意味着如果系统无法处理所有异步操作,则异步操作可以即时转换为同步
如果底层后写高速缓存的大小超过临界大小(flushSize * 1.5),则将使用正在执行写入操作的线程而不是flusherThread。
这就是您在日志中看到这些线程的原因:
考虑到我的Cache已启用后写并且写入顺序模式为 PRIMARY_SYNC(默认)并且缓存中没有任何备份 应该阻止对缓存的调用,直到主节点能够 提交写。这是否也意味着能够将条目放入 写在缓存后面。
是的,确实如此。
如果在服务器中存储条目,Ignite Server会生成 两个条目的副本一个用于存储,一个用于写入 缓存。或者使用相同的条目参考。
应使用相同条目的参考。
让我们一步一步地考虑这个场景:
客户端主题已上传了14个条目。 GridCacheWriteBehindStore
检测到底层缓存中的多个条目超过了刷新大小并发送信号以唤醒刷新线程。
请参阅GridCacheWriteBehindStore#updateCache()
刷新线程唤醒并尝试通过ConcurrentLinkedHashMap
从后写缓存(write-behind-cache.entrySet().iterator()
)获取数据。
该迭代器提供弱一致的遍历,即不保证它反映了构造之后的任何修改。
重要的是客户端线程并行放置新条目。
客户端线程放置最后一个值[key=Key:20, val=Value: 20]
。同时,Thread.sleep()
方法中的writeAll()
阻止了刷新线程。
GridCacheWriteBehindStore
检测到后写缓存的当前大小超过临界大小(刷新大小* 1.5),因此应使用反压机制。
GridCacheWriteBehindStore
调用flushSingleValue()
方法以从后写缓存中刷新最旧的值(当然,此值不应由flusher线程获取)。
在客户端线程的上下文中调用flushSingleValue()
方法。
之后,刷新线程唤醒并处理剩余的条目。
我希望了解后写商店实施是有帮助的。
谢谢!