使用Redis有序集合进行索引

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中拥有一个排序集,其键为indexes:people:last_name:first_name:last_updated,其中包含格式为last_name:first_name:last_updated:id的字符串。例如:
smith:joel: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组合拥有一个排序集。例如,键可能是indexes:people:last_name=smith:first_name=joel,它将为我们称为Joel Smith的每个人都有一个条目。每个条目的名称为id,其分数为last_updated值。例如:

value: 0azbjZRHTQ6U8enBw6BJBw ; score: 1372761839.444

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

缺点是,对我来说似乎非常大:

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

总结

因此,尽管存在缺点,第一种选择似乎更好。如果您对这两种或其他可能的解决方案(即使是我们应该使用Redis之外的东西)有任何反馈,我会非常感激。

3个回答

7
  1. 我强烈反对使用Redis进行此操作。你将会存储大量的指针数据,如果你决定要执行更复杂的查询,如 SELECT WHERE first_name LIKE 'jon%', 你就会遇到麻烦。你还需要构建跨多列的非常大的索引,以便同时搜索两个字段。你实际上需要不断地修改和重新设计一个搜索框架。你最好使用 Elastic SearchSolr,或任何已经构建好了你要做的事情的其他框架。Redis非常棒,有很多好的用途。但是不适合这个场景。

  2. 提醒一下,回答你实际的问题:我认为你最好使用你第一个解决方案的变体。为每个索引使用单个排序集合,但是将字母转换为数字。将字母转换为某个十进制值。你可以使用ASCII值,或者只是按字典顺序将每个字母分配给1-26的值,假设你使用英语。标准化,使得每个字母占据相同的数字长度(所以,如果26是你最大的数字,那么1将被写成"01")。然后,在每个索引中使用这些数字与小数点一起附加在一起作为你的分数(例如,"hat"将是".080120")。这将让你拥有一个正确排序的单词和这些数字之间的1对1映射。当你搜索时,从字母转换为数字,然后你就能够使用Redis的所有漂亮的排序集合函数,如 ZRANGEBYSCORE,而无需重写它们。Redis的函数编写得非常优化,因此尽可能使用它们,而不是编写自己的。


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 已经在集合中,因此过滤速度很快。


“如何避免成为垃圾邮件发送者”的帮助页面明确指出:“你必须在回答中披露你的从业关系。”我已经相应地编辑了你的回答。 - Louis

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])
})

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接