问题
首先,RediSearch是可以创建多个索引的。
但是,创建多个索引之后,有个问题,就是可以搜到对方的数据。
这是为什么呢?
原因
原因就是因为,写数据的时候,没有指定索引名字。
那为什么没有指定索引名字?因为旧版本的RediSearch是可以指定索引名字的,但是新版本不能指定索引名字。
那新版本,怎么区分不同的数据?根据key的前缀。
为什么新版本要这么设计呢?因为不同索引是可能共享同一部分数据的。
说白了,这个设计思想的背后目的是,粒度更细了。以前是索引粒度,现在是前缀粒度。
优点是:粒度更细了。内存更小了,因为不同索引可以共享同一部分数据。粒度更细了之后,也更灵活了。
缺点是:容易混淆。特别是初学者,搞晕了——而且还容易出bug,导致生产故障。
解决方法
比如,创建两个不同的索引:
1、第一个索引名字
idx:name1
2、第二个索引名字
idx:name2
然后,接下来,开始往索引里面写数据:
1、第一个索引
hset(keyPrefix1+id,map);
2、第二个索引
hset(keyPrefix2+id,map);
注意,写数据的时候,是没有指定索引名字的。那不同索引,怎么区分是不是自己的数据?根据前缀来区分。
写数据的时候,使用了不同的前缀。但是,不同的索引,怎么和不同的前缀关联上呢?创建索引的时候,也要指定前缀。意思是,只搜索匹配前缀的key。具体代码和细节,见下文。
到此为止,答案一目了然,就是旧版本写数据的时候,是根据指定索引名字来区分写到不同的索引。但是新版本是根据key前缀来匹配和搜索数据。
key前缀
作用
告诉索引应该索引哪些键。您可以向索引添加多个前缀。由于参数是可选的,因此默认值为
*
(所有键)。 redis.io/commands/ft…
默认是匹配所有数据,这也是创建多个索引之后,如果没有指定前缀,就会导致搜索结果混乱的原因。其实原因就是,虽然创建了多个索引,但是每个索引都是会搜索所有的数据。怎么过滤自己的数据?基于key前缀。
创建索引的时候,前缀可以指定一个,或多个。但是,写数据的时候,只能指定一个key。
也就是说,一个索引可以匹配一个前缀,多个前缀,所有前缀。而一个前缀key,也可以属于多个索引。索引和key,变成了多对多的关系。
创建索引
直接看真实的代码示例
protected IndexStatus doInitIndex() throws Exception {
logger.warn("will init index!");
return executeSearch(commands -> {
try {
CreateOptions.Builder<String, String> cob = CreateOptions.builder();
cob.defaultLanguage(Language.valueOf(this.defaultLanguage));
cob.prefix(KEY_PREFIX); //只搜索匹配指定前缀的key
String result = commands.create(REDISEARCH_INDEX_NAME, cob.build(),
// fields:
Field.numeric("id").noIndex().build(),
Field.numeric("publishAt").build(),
Field.tag("type").build(),
Field.text("name").weight(10).build(),
Field.text("description").weight(5).build(),
Field.text("content").weight(1).build(),
Field.text("url").noIndex().build(),
Field.text("url2").noIndex().build());
logger.info("FT.CREATE language {}, index {}: {}", REDISEARCH_INDEX_NAME,
this.defaultLanguage, result);
return IndexStatus.CREATED;
} catch (RedisCommandExecutionException e) {
if ("Index already exists".equals(e.getMessage())) {
logger.warn("FT: index already exists.");
return IndexStatus.EXIST;
} else {
logger.error("init index error: {}", e.getMessage());
}
} catch (Exception e) {
logger.error("init index error.", e);
}
return IndexStatus.FAILED;
});
}
key前缀
static final String REDISEARCH_INDEX_NAME = "idx:doc"; //索引名字
public static final String KEY_PREFIX = "doc:"; //key前缀
写数据
前缀和前面保持一致
protected void indexSearchableDocuments(List<SearchableDocument> documents) throws Exception {
logger.info("add searchable documents...");
executeSearch(commands -> {
for (SearchableDocument doc : documents) {
//Map.of方法的key/value不能为null,所以需要转换一下
if (doc.tags == null) {
doc.tags = "";
}
commands.hset(KEY_PREFIX + doc.id,
Map.of("id", String.valueOf(doc.id), "type", doc.type, "name", doc.name, "content",
doc.content, "publishAt", String.valueOf(doc.publishAt), "url", doc.url,
"url2", doc.url2, "tags", doc.tags));
}
logger.info("{} docs indexed.", documents.size());
return documents.size();
});
}
官方文档
从历史上看,RediSearch 使用 FT.ADD 命令,该命令在文档和索引之间建立连接。然后,FT.DROP,也是一个历史命令,默认删除文档。在2.x版本中,RediSearch索引哈希和JSON,索引和文档之间的依赖关系不再存在。 redis.io/commands/ft…
旧版本,写数据的add命令,是可以指定索引名字的。但是新版本,已经不能指定名字了。只能通过key前缀来匹配不同的数据。
问chatgpt
在RediSearch 2.x版本中,确实进行了一些重要的变更。在1.x版本中,FT.ADD用于将文档添加到索引,并且存在文档和索引之间的连接。而在2.x版本中,这种连接不再存在,因为索引现在直接与哈希和JSON相关联。
此外,FT.DROP也经历了变化。在1.x版本中,FT.DROP用于删除整个索引,包括相关的文档。在2.x版本中,FT.DROP仍然存在,但它不再删除与索引相关联的文档,只删除索引本身。
这些变化反映了RediSearch在版本之间的演变和改进,以更好地满足用户需求。
删除数据
同上
public boolean unindexSearchableDocument(long id) throws Exception {
logger.info("remove searchable document {}...", id);
executeSearch(commands -> {
long n = commands.del(KEY_PREFIX + id);
return n == 1;
});
return true;
}
总结
增删查改的时候,包括创建索引的时候,必须全部使用同一个key前缀,搜索结果才准确。
否则,搜到的是所有数据。