6

我想就我正在考虑使用 Redis 排序集实现可搜索索引的两种方法获得一些反馈和建议。

情况和目标

我们目前有一些键值表存储在 Cassandra 中,并且我们希望为其创建索引。例如,一张表将包含人员记录,Cassandra 表将以 id 作为其主键,将序列化对象作为值。该对象将具有诸如 first_name、last_name、last_updated 等字段。

我们想要的是能够进行搜索,例如 "last_name = 'Smith' AND first_name > 'Joel'" 、 "last_name < 'Aaronson'" 、 "last_name = 'Smith' AND first_name = 'Winston'" 等等. 搜索应该会产生匹配的 id,这样我们就可以从 Cassandra 中检索对象。我认为上述搜索可以使用单个索引完成,按字典顺序按 last_name、first_name 和 last_updated 排序。如果我们需要一些使用不同顺序的搜索(例如“first_name = 'Zeus'”),我们可以有一个类似的索引来允许这些搜索(例如 first_name、last_updated)。

我们正在考虑为此使用 Redis,因为我们需要能够每分钟处理大量写入。我已经阅读了一些使用 Redis 排序集的常用方法,并提出了两种可能的实现:

选项 1:每个索引一个排序集

对于我们的按 last_name、first_name、last_updated 的索引,我们将在 Redis 中的键 index:people:last_name:first_name:last_updated 下有一个排序集,其中包含格式为 last_name:first_name:last_updated:id 的字符串。例如:

史密斯:乔尔:1372761839.444:0azbjZRHTQ6U8enBw6BJBw

(对于分隔符,我可能会使用 '::' 而不是 ':' 或其他更好地使用字典顺序的东西,但现在让我们忽略它)

这些项目都将被赋予 0 分,因此已排序的集合将仅按字符串本身按字典顺序排序。然后,如果我想执行“last_name = 'smith' AND first_name < 'bob'”之类的查询,则需要获取列表中“smith:bob”之前的所有项目。

据我所知,这种方法有以下缺点:

  1. 没有 Redis 函数可以根据字符串值选择范围。此功能称为 ZRANGEBYLEX,由 Salvatore Sanfilippo 在https://github.com/antirez/redis/issues/324提出,但尚未实现,因此我必须使用二进制搜索找到端点并自己获取范围(可能使用 Lua,或者在应用程序级别使用 Python,这是我们用来访问 Redis 的语言)。
  2. 如果我们想包含索引条目的生存时间,似乎最简单的方法是定期安排任务,该任务遍历整个索引并删除过期项目。

选项 2:小排序集,按 last_updated 排序

这种方法是相似的,除了我们会有许多更小的排序集,每个集都有一个类似时间的值,例如分数的 last_updated。例如,对于相同的 last_name、first_name、last_updated 索引,我们将为每个 last_name、first_name 组合设置一个排序集。例如,键可能是 index:people:last_name=smith:first_name=joel ,对于我们称为 Joel Smith 的每个人,它都有一个条目。每个条目的名称为 id,其分数为 last_updated 值。例如:

值:0azbjZRHTQ6U8enBw6BJBw;评分:1372761839.444

这样做的主要优点是 (a) 我们知道除 last_updated 之外的所有字段的搜索将非常容易,并且 (b) 使用 ZREMRANGEBYSCORE 实现生存时间将非常容易。

对我来说似乎很大的缺点是:

  1. 以这种方式管理和搜索似乎要复杂得多。例如,我们需要索引来跟踪它的所有键(例如,如果我们想在某个时候清理)​​并以分层方式执行此操作。诸如“last_name < 'smith'”之类的搜索将需要首先查看所有姓氏的列表以找到在 smith 之前的那些,然后查找其中包含的所有名字的每个人,然后查找其中的每一个从其排序集中获取所有项目。换句话说,要构建和担心很多组件。

包起来

所以在我看来,第一种选择会更好,尽管它有缺点。我非常感谢有关这两个或其他可能的解决方案的任何反馈(即使它们是我们应该使用 Redis 以外的东西)。

4

3 回答 3

7
  1. 我强烈反对为此使用 Redis。您将存储大量额外的指针数据,如果您决定要执行更复杂的查询,例如,SELECT WHERE first_name LIKE 'jon%'您将遇到麻烦。您还需要设计额外的、非常大的跨多个列的索引,以防您想同时搜索两个字段。您基本上需要不断破解并重新设计搜索框架。您最好使用Elastic SearchSolr或任何其他已经构建的框架来完成您想要做的事情。Redis 很棒,有很多很好的用途。这不是其中的一个。

  2. 除了警告,回答您的实际问题:我认为最好使用您的第一个解决方案的变体。每个索引使用一个排序集,但只需将字母转换为数字。将您的字母转换为一些十进制值。您可以使用 ASCII 值,或者只是按照字典顺序将每个字母分配给 1-26 的值,假设您使用的是英语。标准化,以便每个字母占用相同的数字长度(因此,如果 26 是您的最大数字,则 1 将写为“01”)。然后只需将这些与前面的小数点一起附加并将其用作每个索引的分数(即“帽子”将是“.080120”)。这将使您在单词和这些数字之间进行正确排序的 1 对 1 映射。搜索的时候,把字母转成数字,就可以使用Redis的全部了ZRANGEBYSCORE无需重写它们。Redis 的函数编写得非常非常优化,因此您最好尽可能使用它们,而不是自己编写。

于 2013-07-02T20:07:14.480 回答
4

您可以为此使用我的项目python-stdnet,它会为您完成所有索引。例如:

class Person(odm.StdModel):
    first_name = odm.SymbolField()
    last_name = odm.SymbolField()
    last_update = odm.DateTimeField()

将模型注册到 redis 后端后,您可以执行以下操作:

qs = models.person.filter(first_name='john', last_name='smith')

qs = models.person.filter(first_name=('john','carl'), last_name=('smith','wood'))

以及更多

过滤速度很快,因为所有 id 都已经在集合中。

于 2013-07-02T20:20:48.880 回答
0

您可以查看redblade,它可以为您自动维护索引,它是由 Node.JS 编写的。

//define schema
redblade.schema('article', {
    "_id"         : "id"
  , "poster"      : "index('user_article')"
  , "keywords"    : "keywords('articlekeys', return +new Date() / 60000 | 0)"
  , "title"       : ""
  , "content"     : ""
})


//insert an article
redblade.insert('article', {
   _id        : '1234567890'
  , poster     : 'airjd'
  , keywords   : '信息技术,JavaScript,NoSQL'
  , title      : '测试用的SLIDE 标题'
  , content    : '测试用的SLIDE 内容'
}, function(err) {

})


//select by index field or keywords
redblade.select('article', { poster:'airjd' }, function(err, articles) {
  console.log(articles[0])
})

redblade.select('article', { keywords: 'NoSQL' }, function(err, articles) {
  console.log(articles[0])
})
于 2015-10-26T09:28:17.583 回答