使用Redis排序集进行索引

时间:2013-07-02 17:10:43

标签: indexing nosql redis set sorted

我想获得一些关于我正在考虑使用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:每个索引的单个有序集

有关的姓氏,FIRST_NAME,我们的索引LAST_UPDATED,我们将不得不在Redis的下键索引的有序集合:人:姓氏:FIRST_NAME:LAST_UPDATED,其中将包含与格式字符串姓氏:FIRST_NAME:LAST_UPDATED:ID。例如:

史密斯:乔尔:1372761839.444:0azbjZRHTQ6U8enBw6BJBw

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

这些项目都将被赋予得分0,以便排序的集合将按字典顺序按字符串本身排序。如果我想做一个像“last_name ='smith'和'first_name<'bob'”这样的查询,我需要获取列表中'smith:bob'之前的所有项目。

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

  1. 没有Redis功能可根据字符串值选择范围。此功能称为ZRANGEBYLEX,已在https://github.com/antirez/redis/issues/324,但没有实现,所以我不得不使用二进制搜索找到端点和使用Lua让自己(也许范围内,或者在该申请已经提出了萨尔瓦托雷圣菲利波使用Python的级别,这是我们用来访问Redis的语言。)
  2. 如果我们想要为索引条目包含生存时间,那么最简单的方法就是有一个定期计划的任务,该任务遍历整个索引并删除过期的项目。
  3. 选项2:小型排序集,按last_updated排序

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

    值:0azbjZRHTQ6U8enBw6BJBw;得分:1372761839.444

    主要优势,这是(a)的搜索,我们知道除了LAST_UPDATED所有字段会很轻松,和(b)实施时间的生存会很简单,用ZREMRANGEBYSCORE。

    这个缺点对我来说似乎很大:

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

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

3 个答案:

答案 0 :(得分:7)

  1. 我强烈反对使用Redis。您将存储大量额外的指针数据,如果您决定要执行更复杂的查询,例如SELECT WHERE first_name LIKE 'jon%',您将遇到麻烦。如果要同时搜索两个字段,还需要设计跨越多列的额外的非常大的索引。你基本上需要不断攻击并重新设计搜索框架。使用Elastic SearchSolr,或者已经构建的任何其他框架来做你正在尝试做的事情,你会好得多。 Redis太棒了,有很多好的用途。这不是其中之一。

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

答案 1 :(得分:4)

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

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

一旦模型为registered with a redis backend,您就可以执行此操作:

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

以及

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

以及更多

过滤速度很快,因为所有ID都已经存在。

答案 2 :(得分: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])
})