如何基于Jedis/Redis Template将Mongo集合高效存入Redis缓存?
首先,你的场景非常适合Redis缓存——数据总量仅3MB,完全可以放在内存中,而且Redis的内存操作性能远高于Mongo的磁盘/内存混合操作,只要设计好存储结构,效率肯定没问题。下面分几个部分给你拆解方案:
一、纯Jedis的高效实现方案
你担心的"目录+文档需要两次Redis调用"的问题,其实可以通过**Pipeline(管道)**解决,把两次请求打包成一次网络往返,几乎消除额外开销。具体结构设计如下:
1. 主数据存储:哈希结构存单文档
把每个Mongo文档用Redis哈希存储,Key命名为doc:{_id}(比如doc:5fd9e0568564939bc4fb947c),哈希的field就是field1到field10,对应值直接用Mongo里的原始值。这样所有文档只存一次,避免冗余。
用Jedis操作的示例代码:
Jedis jedis = new Jedis("localhost"); // 存储单个文档 Map<String, String> docFields = new HashMap<>(); docFields.put("field1", "value1"); docFields.put("field2", "value2"); // ... 其他field jedis.hset("doc:5fd9e0568564939bc4fb947c", docFields);
2. 组合索引存储:Set结构存关联ID
针对每个需要的查询组合(比如(field1, field2)、(field1, field3)),创建对应的索引Key,格式为idx:{fieldA}-{fieldB}:{valA}:{valB},用Redis Set存储符合该组合条件的文档_id。比如当field1=foo且field2=bar时,把对应的_id加入idx:field1-field2:foo:bar这个Set中。
示例代码:
// 为field1=foo、field2=bar的文档创建索引 jedis.sadd("idx:field1-field2:foo:bar", "5fd9e0568564939bc4fb947c");
3. 查询优化:用Pipeline合并两次调用
查询时,先通过索引Set获取所有匹配的_id,再批量获取对应的文档哈希。用Jedis Pipeline把这两步的命令打包发送,Redis会一次性执行并返回结果,避免两次网络请求的延迟:
try (Jedis jedis = new Jedis("localhost")) { Pipeline pipeline = jedis.pipelined(); // 第一步:获取索引对应的ID列表 Response<Set<String>> idResponse = pipeline.smembers("idx:field1-field2:foo:bar"); // 第二步:批量获取每个ID对应的文档哈希 Map<String, Response<Map<String, String>>> docResponses = new HashMap<>(); for (String id : idResponse.get()) { docResponses.put(id, pipeline.hgetAll("doc:" + id)); } // 执行所有命令 pipeline.sync(); // 处理结果 for (Map.Entry<String, Response<Map<String, String>>> entry : docResponses.entrySet()) { Map<String, String> doc = entry.getValue().get(); // 处理文档逻辑 } }
这种方式的性能几乎和单次Redis调用一致,因为网络开销是Redis操作的主要瓶颈,Pipeline完美解决了这个问题,效率肯定比Mongo查询高。
二、Spring Data Redis(Repositories)的可行性
Spring Data Redis确实可以高效存储你的对象,并且支持类数据库的查询语法,底层也是基于Redis的原生结构实现的,效率和手动用Jedis差不多,还能大幅减少代码量:
1. 实体类配置
用@RedisHash标记你的实体类,指定存储前缀,Spring Data会自动把对象存为Redis哈希,Key格式为{前缀}:{id}:
@RedisHash("doc") public class Document { @Id private String id; // 对应Mongo的_id private String field1; private String field2; // ... field3到field10的getter/setter }
2. 定义Repository接口
继承RedisRepository,然后直接写查询方法,Spring Data会自动生成对应的Redis查询逻辑:
public interface DocumentRepository extends RedisRepository<Document, String> { // 支持(field1, field2)组合查询 List<Document> findByField1AndField2(String field1, String field2); // 支持(field1, field3)组合查询 List<Document> findByField1AndField3(String field1, String field3); }
3. 效率说明
Spring Data Redis会自动为查询中的字段组合创建二级索引(用Redis Set存储),查询时会先求索引的交集,再批量获取文档。底层已经做了Pipeline优化,所以性能和你手动用Jedis实现的方案几乎一致,而且代码更简洁,不用手动维护索引和批量查询逻辑。
唯一需要注意的是,如果你有大量自定义的查询组合,Spring Data会自动创建对应的索引,不过因为你的数据量很小,索引占用的内存可以忽略不计。
三、进阶优化:Lua脚本
如果追求极致性能,可以把查询逻辑用Lua脚本写到Redis端执行,这样完全是Redis内部操作,一次调用直接返回所有匹配的文档,连Pipeline的网络往返都只需要一次:
示例Lua脚本(伪代码):
local idxKey = KEYS[1] local docIds = redis.call('SMEMBERS', idxKey) local docs = {} for _, id in ipairs(docIds) do local docKey = 'doc:' .. id docs[#docs+1] = redis.call('HGETALL', docKey) end return docs
用Jedis执行脚本的代码:
Jedis jedis = new Jedis("localhost"); String script = "上面的Lua脚本内容"; List<Object> result = (List<Object>) jedis.eval(script, Collections.singletonList("idx:field1-field2:foo:bar"), Collections.emptyList()); // 解析result为文档列表
Lua脚本的性能是最高的,因为所有逻辑都在Redis内存中完成,没有客户端和服务器之间的多次数据传输。
总结
- 纯Jedis方案:用哈希存主文档+Set存组合索引,配合Pipeline,既避免数据冗余,又能把查询开销降到最低,完全满足你的需求。
- Spring Data Redis:代码简洁,自动维护索引和查询逻辑,效率和Jedis手动实现相当,适合快速开发。
- Lua脚本:极致性能选择,适合对延迟要求极高的场景。
另外,Redisson作为更高层的Redis客户端,也提供了类似的分布式数据结构和查询支持,但如果你的需求用上述方案就能搞定,没必要额外引入依赖。
内容的提问来源于stack exchange,提问作者Fernando

