【问题标题】:Ignite cache invoke update very very slow when value is Treemap and data in treemap increase upto 10K当值为 Treemap 并且 treemap 中的数据增加到 10K 时,Ignite 缓存调用更新非常缓慢
【发布时间】:2018-05-20 11:53:00
【问题描述】:

我有更新点燃缓存记录的代码逻辑,

缓存定义为:

IgniteCache<String, TreeMap<Long, InfoResultRecord>> txInfoCache;

键是缓存类型字符串,对于值,我使用 TreeMap 来保持记录有序(我需要对数据进行排序),但是用于更新的时间随着 TreeMap 大小的增加而增加,我发现的是当 TreeMap 大小在 10K 左右时,每次调用一条记录添加到缓存值 treemap 非常慢,大约 2 秒,如果我有 1K 数据需要添加到 Treemap,它将花费 2000 秒,它真的很慢而不是可以接受。

我使用调用来更新缓存以将记录添加到 Treemap:

txInfoCache.invoke(txType, new TxInfoProcessor(), record);

缓存的配置是:

CacheConfiguration<String, TreeMap<Long, InfoResultRecord>> cacheCfg =
        new CacheConfiguration<>("TxInfoCache");
cacheCfg.setCacheMode(CacheMode.REPLICATED);
//cacheCfg.setStoreKeepBinary(true);
cacheCfg.setAtomicityMode(ATOMIC);
cacheCfg.setBackups(0);
txInfoCache = ignite.getOrCreateCache(cacheCfg);

向Treemap添加记录的处理器是:

private static class TxInfoProcessor implements EntryProcessor<
        String,
        TreeMap<Long, InfoResultRecord>,
        TreeMap<Long, InfoResultRecord>> {

    @Override
    public TreeMap<Long, InfoResultRecord> process(
            MutableEntry<String,
                    TreeMap<Long, InfoResultRecord>> entry, Object... args) {

        InfoResultRecord record = (InfoResultRecord) args[0];

        final Long oneDayMsSeconds = 24 * 60 * 60 * 1000L;

        TreeMap<Long, InfoResultRecord>
                InfoResultRecordTreeMap = entry.getValue();

        if (InfoResultRecordTreeMap == null) {
            InfoResultRecordTreeMap = new TreeMap<>();
        }


        InfoResultRecordTreeMap.put(record.getDealTime() + oneDayMsSeconds, record);
        entry.setValue(InfoResultRecordTreeMap);

        return null;
    }
}

有什么问题吗?还是我以错误的方式使用缓存?

我还编写了一个简单的测试代码来验证使用 TreeMap 获取/放置时的速度:

public class Server2 {

    public static void main(String[] args) throws IgniteException {
        try (Ignite ignite = Ignition.start("server-start.xml")) {

            IgniteCache<String, TreeMap<Long, String>> testCache = ignite.getOrCreateCache("testCache");
            testCache.put("my",new TreeMap<>());

            while (true) {
                StopWatch stopWatch = new StopWatch();
                stopWatch.start("1");
                TreeMap<Long, String> map = testCache.get("my");
                stopWatch.stop();
                stopWatch.start("2");
                map.put(System.currentTimeMillis(),String.valueOf(new Random().nextInt(1000000000)));
                testCache.put("my",map);
                stopWatch.stop();

                System.out.println("cacheSize:"+map.size()+","+stopWatch.prettyPrint());
            }
        }
    }
}


cacheSize:1000,StopWatch '': running time (millis) = 195
-----------------------------------------
ms     %     Task name
-----------------------------------------
00080  041%  1
00115  059%  2

cacheSize:1001,StopWatch '': running time (millis) = 38
-----------------------------------------
ms     %     Task name
-----------------------------------------
00028  074%  1
00010  026%  2


cacheSize:3000,StopWatch '': running time (millis) = 139
-----------------------------------------
ms     %     Task name
-----------------------------------------
00055  040%  1
00084  060%  2

cacheSize:3001,StopWatch '': running time (millis) = 68
-----------------------------------------
ms     %     Task name
-----------------------------------------
00042  062%  1
00026  038%  2

它清楚地显示了当 Treemap 大小增加时,ignite cache get/put 消耗的时间增加,我认为这应该是 1~2ms,但这里是 xx ms,随着大小的增加,它会增加到 xxxms 甚至几秒。

【问题讨论】:

    标签: java ignite treemap


    【解决方案1】:

    很明显,在这种情况下,读/写时间会增加,因为在每次操作时,它都需要将数据从堆外复制到堆(反之亦然)并对其进行序列化/反序列化,但是,我已经在本地检查您的代码,时间并没有那么急剧地增长。例如,我得到了

    cacheSize:10082,StopWatch '': running time (millis) = 18
    -----------------------------------------
    ms     %     Task name
    -----------------------------------------
    00009  050%  1
    00009  050%  2
    

    无论如何,我不认为您为您的用例选择了一个好的解决方案 - 我建议单独存储所有这些数据,即引入带有 TreeMap 字符串名称字段的键和一行的某些长键并存储这些数据按行。使用它,您的写入操作将更快。此外,在这种情况下,您需要按树形图名称 collocate data。要请求所有数据,您可以使用 SQL 和 ORDER BY 语句。并且不要忘记使用索引!

    【讨论】:

    • 所以,就我而言,它不适合使用 EntryProcessor,因为它可能只为小规模处理而设计,对吧?在这里,我使用 TreeMap 来保持真实数据的有序性,但这需要每次都与整个数据进行对话。在您的建议中,我应该删除 TreeMap 的使用并改用 SQL 查询,我检查了文档,ScanQuery 有过滤和转换,看来我可以用它来获取真正需要的数据而不是整个数据,请纠正我如果我错了。另外,我看到有 IgniteDataStreamer,我想知道这是否更适合我的情况?谢谢
    • 在官方文档中,对于IgniteDataStreamer:“为将大量连续数据流注入Ignite缓存而构建”,这是否更适合我的情况并且比之前的建议更快?
    • 如果像我一样使用 EntryProcessors 编写集合,您将有不必要的操作。至于DataStreamer,是的,对于大量数据它会更快。你也可以检查 ScanQuery,实际上,我建议检查方法和比较结果。
    • 我在这里使用 EntryProcessors 因为它有一个内部锁,所以我可以更新 clousure 中的记录值,如果我将数据结构更改为许多键值对,在进行更新时,我需要更改从 ATOMIC 到 TRANSACTION 的缓存配置并使用锁来保护要更新的记录,有没有其他方法可以在需要更新大量数据的情况下更快地更新缓存记录?
    • 我不了解您的用例,因此,我建议您比较我之前建议的所有方法,以检查哪种方法更适合您。这里的键值对是什么意思?缓存中的普通键值还是 TreeMap 中的键值?
    猜你喜欢
    • 1970-01-01
    • 2021-11-16
    • 2017-04-22
    • 1970-01-01
    • 2019-06-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多