如何高效从Apache Ignite缓存仅读取键且不加载全量数据集
Apache Ignite 仅读取缓存键的最优实现(大数据集场景)
问题根因
你遇到的性能问题、运行失败问题核心原因有两个:
- 默认
ScanQuery会同时把缓存条目的键和值全部序列化后通过网络传输到厚客户端,哪怕你只需要读取键,值对象的序列化、网络传输、反序列化会产生90%以上的无意义开销,数据集越大这部分开销越明显 - 调用
.ToList()会一次性把所有查询结果全量加载到客户端内存,数据集超过客户端堆内存阈值时直接OOM失败
你后续尝试的游标遍历实现虽然解决了一次性全量加载的问题,但没有关闭值的返回,所以依然存在大量无效传输,耗时很长。
最优实现方案
方案1:配置ScanQuery仅返回键(90%场景下的首选,改造成本最低)
ScanQuery提供了IncludeValue配置项,默认值为true,将其设为false后,服务端节点不会序列化、传输值对象,仅返回键,性能可以提升数倍到数十倍。
配合游标流式读取,避免一次性加载全量键到内存,代码示例:
// 注意泛型参数要和缓存实际键值类型匹配,你之前代码里键是int就不要用string var cache = ignite.GetOrCreateCache<int, IBinaryObject>("my-cache").WithKeepBinary<int, IBinaryObject>(); var keys = new List<int>(); // 核心配置:IncludeValue = false,不返回值对象 var query = new ScanQuery<int, IBinaryObject> { IncludeValue = false }; // 游标流式拉取,不会一次性加载全量结果到内存 using (var cursor = cache.Query(query)) { foreach (var entry in cursor) { // entry.Value此时为默认值,服务端根本没有传输值,没有额外开销 keys.Add(entry.Key); // 如果键总量超千万级,建议在这里直接处理键,不要全部攒到List中,避免客户端OOM } }
该方案的优势:
- 改造成本极低,仅需加一行配置
- 网络传输量仅为原来的几十分之一(取决于值对象的大小)
- 客户端不需要做值的反序列化,CPU开销大幅降低
- 流式拉取对客户端内存要求低,只要处理逻辑跟得上,亿级键量也可以稳定运行
方案2:服务端Compute执行(超大规模数据集首选,性能最高)
如果你的数据集规模特别大(单缓存键量过亿),不需要把所有键拉到客户端处理,可以直接通过Ignite Compute将任务派发到所有服务端节点,每个节点仅遍历本地存储的键,处理完成后只返回汇总结果,完全不需要跨节点传输全量键:
// 把任务广播到所有服务端节点,每个节点处理本地分片的键 var nodeLocalKeyResults = ignite.GetCompute().Broadcast(new GetLocalCacheKeysTask(), "my-cache"); // 汇总所有节点返回的结果即可 var allKeys = nodeLocalKeyResults.SelectMany(r => r).ToList();
服务端执行的任务定义:
private class GetLocalCacheKeysTask : IComputeFunc<string, List<int>> { public List<int> Invoke(string cacheName) { var ignite = Ignition.GetIgnite(); var localCache = ignite.GetCache<int, IBinaryObject>(cacheName) .WithKeepBinary<int, IBinaryObject>(); var localKeys = new List<int>(); // Local=true表示仅遍历当前节点本地存储的缓存数据,无网络开销 var query = new ScanQuery<int, IBinaryObject> { IncludeValue = false, Local = true }; using var cursor = localCache.Query(query); foreach (var entry in cursor) { localKeys.Add(entry.Key); // 可以直接在服务端做键的过滤、统计,不需要传回客户端 } return localKeys; } }
原有代码问题说明
- 第一版实现调用
.ToList():会把所有包含完整值的缓存条目一次性加载到客户端内存,大数据集下必然OOM,且等待时间极长 - 第二版游标实现:虽然避免了一次性全量加载,但没有设置
IncludeValue = false,值对象依然会被全量传输、反序列化,所以耗时没有本质下降 - 代码中存在键类型不匹配问题:缓存定义的键是
int类型,ScanQuery却用string作为键泛型参数,会导致类型转换错误,额外增加开销
额外优化建议
- 如果键是基础值类型(int/long/string/Guid),不需要额外配置
WithKeepBinary也能正常读取键,不会产生额外开销 - 键量级特别大时不要把所有键都存入客户端集合,边遍历边处理,处理完及时释放内存
- 遍历游标时不要执行长时间阻塞操作,避免长时间占用集群连接资源
内容的提问来源于stack exchange,提问作者Justin Mathew
相关产品推荐
相关产品推荐

