二级索引
在 Redis 中构建二级索引
Redis 并不完全是一个键值存储,因为值可以是复杂的数据结构。但是,它有一个外部键值 shell:在 API 级别,数据由键名称寻址。公平地说,Redis 原生只提供主键访问。但是,由于 Redis 是一个数据结构服务器,因此其功能可用于索引,以便创建不同类型的二级索引,包括复合(多列)索引。
本文档介绍了如何使用以下数据结构在 Redis 中创建索引:
- 哈希和 JSON 文档,使用各种字段类型;与 Redis 查询引擎结合使用。
- 排序集,用于按 ID 或其他数字字段创建二级索引。
- 具有字典范围的排序集,用于创建更高级的二级索引、复合索引和图形遍历索引。
- 用于创建随机索引的集。
- 用于创建简单可迭代索引和最后 N 项索引的列表。
- 带标签的时间序列。
使用 Redis 实现和维护索引是一个高级主题,因此大多数 需要对数据执行复杂查询的用户应该了解他们 关系存储可以更好地提供服务。然而,尤其是在缓存中 场景中,明确需要将索引数据存储到 Redis 中,以加快需要某种形式的索引才能执行的常见查询。
哈希和 JSON 索引
Redis 查询引擎提供了使用各种字段类型为哈希键和 JSON 键编制索引和查询的功能:
TEXT
TAG
NUMERIC
GEO
VECTOR
GEOSHAPE
使用FT.CREATE
命令中,所有使用索引中定义的前缀的键都可以使用FT.SEARCH
和FT.AGGREGATE
命令。
有关创建哈希索引和 JSON 索引的更多信息,请参阅以下页面。
具有排序集的简单数值索引
您可以使用 Redis 创建的最简单的二级索引是使用 sorted set 数据类型,它是表示一组 元素按浮点数排序,浮点数是 每个元素。元素按从最小到最高分的顺序排序。
由于分数是双精度浮点数,因此您可以使用 原版排序集仅限于索引字段为数字的内容 在给定范围内。
构建此类索引的两个命令是ZADD
和ZRANGE
使用BYSCORE
参数分别添加项目和检索
指定范围。
例如,可以按人员姓名的 age 来将 Element 添加到有序集合中。该元素将是 person 的 Man 和分数将是 Age。
ZADD myindex 25 Manuel
ZADD myindex 18 Anna
ZADD myindex 35 Jon
ZADD myindex 67 Helen
为了找回所有年龄在 20 到 40 岁之间的人员,以下 命令的
ZRANGE myindex 20 40 BYSCORE
1) "Manuel"
2) "Jon"
通过使用 WITHSCORES 选项ZRANGE
这也是可能的
以获取与返回的元素关联的分数。
这ZCOUNT
command 可用于检索元素数量
在给定范围内,而不实际获取元素,这也是
很有用,特别是考虑到该作以对数执行
时间,而不管范围的大小如何。
范围可以是包含或排除的,请参考ZRANGE
命令文档了解更多信息。
注意:使用ZRANGE
使用BYSCORE
和REV
参数中,可以在
reversed order,这在给定的
方向(升序或降序),但我们想要检索信息
反过来。
使用对象 ID 作为关联值
在上面的示例中,我们将 names 与 ages 相关联。但是,一般来说,我们 可能想要索引存储在其他位置的对象的某个字段。 而不是直接使用排序集值来存储关联的数据 使用 indexed 字段,可以只存储对象的 ID。
例如,我可能有代表用户的 Redis 哈希。每个用户都是 由单个 key 表示,可通过 ID 直接访问:
HMSET user:1 id 1 username antirez ctime 1444809424 age 38
HMSET user:2 id 2 username maria ctime 1444808132 age 42
HMSET user:3 id 3 username jballard ctime 1443246218 age 33
如果我想创建一个索引以便按年龄查询用户,我会 可以做到:
ZADD user.age.index 38 1
ZADD user.age.index 42 2
ZADD user.age.index 33 3
这次,与排序集中的 score 关联的值是
对象的 ID。因此,一旦我使用ZRANGE
使用BYSCORE
参数,我将
还必须检索我需要的信息HGETALL
或类似
命令。明显的优点是物体可以在不接触的情况下发生变化
索引,只要我们不更改 indexed 字段。
在接下来的示例中,我们几乎总是使用 ID 作为与 索引,因为这通常是听起来更响亮的设计,有一些 异常。
更新简单排序集索引
我们通常会为随时间变化的事物编制索引。在上述 例如,用户的年龄每年都会变化。在这种情况下,它会 使用出生日期作为索引而不是年龄本身是有意义的, 但是在其他情况下,我们只是希望将一些字段从 time to time 以及反映此更改的索引。
这ZADD
命令使更新简单索引成为一项非常简单的作
由于重新添加回具有不同 score 和相同值的元素
将简单地更新分数并将元素移动到正确的位置,
因此,如果用户antirez
39 岁了,为了更新
data 在代表用户的 hash 中,在 index 中,我们需要
执行以下两个命令:
HSET user:1 age 39
ZADD user.age.index 39 1
该作可以包装在MULTI
/EXEC
transaction 以便
确保 both fields are updated 或 none。
将多维数据转换为线性数据
使用排序集创建的索引只能为单个数值编制索引 价值。因此,您可能会认为不可能为某些内容编制索引 它有多个维度使用这种索引,但实际上这个 并不总是正确的。如果你能有效地表现某事 多维线性方式,它们通常可以使用简单的 sorted 集进行索引。
例如,Redis 地理索引 API 使用已排序的 设置为使用称为 Geo Hash 的技术按纬度和经度为地点编制索引。排序集 score 表示经度和纬度的交替位,以便我们将 对地球表面的许多小方块的排序集的线性分数。 通过进行 8+1 风格的中心加社区搜索,可以 按 radius 检索元素。
分数的限制
排序的集合元素分数是双精度浮点数。这意味着
它们可以表示不同的十进制或整数值,具有不同的
错误,因为它们在内部使用指数表示。
然而,出于索引目的,有趣的是 score 是
始终能够表示 -9007199254740992 之间没有任何错误号
和 9007199254740992,即-/+ 2^53
.
当表示更大的数字时,您需要不同形式的索引 能够以任何精度为数字编制索引,称为字典 指数。
时间序列索引
当您使用TS.CREATE
命令中,您可以关联一个或多个LABELS
与它。每个标签都是一个名称-值对,其中 name 和 value 都是文本。标签用作二级索引,允许您使用各种时间序列命令对时间序列键组执行查询。
有关创建带有标签的时间序列的示例,请参阅时间序列快速入门指南。
这TS.MGET
,TS.MRANGE
和TS.MREVRANGE
命令根据指定的标签或使用与标签相关的筛选条件表达式对多个时间序列进行作。这TS.QUERYINDEX
command 返回与给定标签相关的筛选条件表达式匹配的所有时间序列键。
词典索引
Redis 排序集有一个有趣的属性。添加元素时
使用相同的分数,它们将按字典顺序排序,比较
strings 作为二进制数据,并使用memcmp()
功能。
对于不懂 C 语言也不懂memcmp
函数,什么
这意味着具有相同分数的元素将比较
它们字节的原始值,一个字节接一个字节。如果第一个字节相同,则
第二个是 checked 的,依此类推。如果两个字符串的公共前缀是
相同,则较长的字符串被视为两者中的较大者,
所以 “foobar” 大于 “foo”。
有ZRANGE
和ZLEXCOUNT
那
能够按字典顺序查询和计算范围,假设
它们与所有元素都具有相同分数的排序集一起使用。
这个 Redis 功能基本上相当于b-tree
data 结构
通常用于使用传统数据库实现索引。
正如您可以猜到的那样,因此可以使用此 Redis 数据
结构来实现非常花哨的索引。
在我们深入研究使用字典索引之前,让我们看看如何 排序集在这种特殊作模式下运行。由于我们需要 添加具有相同分数的元素,我们将始终使用 Special Score 零。
ZADD myindex 0 baaa
ZADD myindex 0 abbb
ZADD myindex 0 aaaa
ZADD myindex 0 bbbb
从排序集中获取所有元素会立即显示它们 按字典顺序排序。
ZRANGE myindex 0 -1
1) "aaaa"
2) "abbb"
3) "baaa"
4) "bbbb"
现在我们可以使用ZRANGE
使用BYLEX
参数来执行范围查询。
ZRANGE myindex [a (b BYLEX
1) "aaaa"
2) "abbb"
请注意,在范围查询中,我们为min
和max
元素
使用特殊字符 和 标识区域。
此前缀是必需的,它们指定元素
的范围是非独占或独占。所以范围[
(
[a (b
意思是给我
按字典顺序排列的所有元素a
inclusive 和b
独家
这些元素都是以a
.
还有两个特殊字符表示无限负数
string 和无限正的 string,它们是 和 。-
+
ZRANGE myindex [b + BYLEX
1) "baaa"
2) "bbbb"
基本上就是这样。让我们看看如何使用这些功能来构建索引。
第一个例子:完成
索引的一个有趣应用是 completion。完成就是什么 当您开始在搜索引擎中键入查询时发生:用户 interface 将预测您可能输入的内容,提供通用 以相同字符开头的查询。
一种天真的完成方法是只添加我们
get 从用户发送到索引中。例如,如果用户搜索banana
我们只会这样做:
ZADD myindex 0 banana
以此类推,对于遇到的每个搜索查询。然后,当我们想要
完成用户输入,我们使用ZRANGE
使用BYLEX
论点。
想象一下用户在搜索表单中键入 “bit”,我们希望
提供以 “bit” 开头的可能搜索关键字。我们向 Redis 发送一个命令
诸如此类:
ZRANGE myindex "[bit" "[bit\xff" BYLEX
基本上,我们使用用户现在正在键入的字符串创建一个范围
作为 start,并且相同的字符串加上一个尾随字节设置为 255,即\xff
在示例中,作为范围的末尾。这样,我们就可以获得用户正在键入的字符串的所有字符串。
请注意,我们不希望返回太多项目,因此我们可以使用 LIMIT 选项来减少结果数量。
在组合中添加频率
上述方法有点幼稚,因为所有用户的搜索都是相同的 以这种方式。在实际系统中,我们希望根据它们的 频率:将以更高的概率提出非常热门的搜索 与很少键入的搜索字符串相比。
为了实现依赖于频率的东西,并且在 Same Time 通过清除 不再流行,我们可以使用非常简单的流式处理算法。
首先,我们修改索引,以便不仅存储搜索词
但也包括与该术语相关的频率。因此,而不是仅仅添加banana
我们添加banana:1
,其中 1 是频率。
ZADD myindex 0 banana:1
我们还需要逻辑,以便在搜索词 已经存在于索引中,所以我们实际上要做的是这样的 那:
ZRANGE myindex "[banana:" + BYLEX LIMIT 0 1
1) "banana:1"
这将返回banana
如果存在。然后我们
可以递增关联的频率并发送以下两个
命令:
ZREM myindex 0 banana:1
ZADD myindex 0 banana:2
请注意,由于可能存在并发更新,因此 以上三个命令应该通过 Lua 脚本发送,这样 Lua 脚本就会自动获取旧的 count 和 重新添加 Score 递增的项目。
所以结果将是,每次用户搜索banana
我们将
更新我们的条目。
还有更多:我们的目标是让项目搜索非常频繁。 所以我们需要某种形式的清除。当我们实际查询索引 为了完成用户输入,我们可能会看到如下内容:
ZRANGE myindex "[banana:" + BYLEX LIMIT 0 10
1) "banana:123"
2) "banaooo:1"
3) "banned user:49"
4) "banning:89"
例如,显然没有人搜索 “banaooo”,但查询是 执行一次,因此我们结束将其呈现给用户。
这就是我们能做的。从返回的物品中,我们随机选择一个, 将其分数减 1,然后使用新分数重新添加它。 但是,如果分数达到 0,我们只需从列表中删除该项目。 您可以使用更高级的系统,但其思路是 长跑将包含 Top Searches,如果 Top Search 将发生变化 它会自动适应的时间。
此算法的改进是根据 他们的权重:分数越高,被选中的可能性就越小 以减少它的分数,或驱逐他们。
规范化大小写和重音符号的字符串
在补全示例中,我们始终使用小写字符串。然而 现实要复杂得多:语言有大写的名字, 口音,等等。
处理此问题的一种简单方法是实际将 string 进行用户搜索。无论用户搜索 “Banana” 什么, “BANANA” 或 “Ba'nana” 我们总是可以把它变成 “banana”。
但是,有时我们可能希望向用户提供原始
item 类型,即使我们对字符串进行规范化以进行索引。为了
这样做,我们所做的是更改索引的格式,以便改为
只是存储term:frequency
我们存储normalized:frequency:original
如以下示例所示:
ZADD myindex 0 banana:273:Banana
基本上,我们添加了另一个字段,我们将提取并仅用于 可视化。范围将始终使用规范化字符串进行计算 相反。这是一个具有多个应用程序的常见技巧。
在索引中添加辅助信息
当直接使用有序集合时,我们有两个不同的 attribute 对于每个对象:分数(我们用作索引)和关联的 价值。当改用字典索引时,分数始终为 设置为 0,基本上根本不使用。我们只剩下一个字符串 这是元素本身。
就像我们在前面的完成示例中所做的那样,我们仍然能够 使用分隔符存储关联数据。例如,我们在 order 添加频率和原始单词以完成。
通常,我们可以向索引键添加任何类型的关联值。
为了使用字典索引实现简单的键值存储
我们只将条目存储为key:value
:
ZADD myindex 0 mykey:myvalue
并使用以下命令搜索 key:
ZRANGE myindex [mykey: + BYLEX LIMIT 0 1
1) "mykey:myvalue"
然后我们提取冒号后面的部分以检索值。 但是,在这种情况下,需要解决的问题是碰撞。冒号字符 可能是 key 本身的一部分,因此必须选择它才能从 与我们添加的键碰撞。
由于 Redis 中的字典范围是二进制安全的,因此您可以使用任何 byte 或任何字节序列。但是,如果您收到不受信任的用户 input,最好使用某种形式的转义来保证 分隔符永远不会恰好是 Key 的一部分。
例如,如果使用两个 null 字节作为分隔符"\0\0"
,您可以
希望始终将 null 字节转义为字符串中的两个字节序列。
数字填充
只有当手头的问题时,词典索引才可能看起来不错 用于索引字符串。其实使用这种索引非常简单 来执行任意精度数字的索引。
在 ASCII 字符集中,数字按 0 到 9 的顺序显示,因此 如果我们用前导零左填充数字,结果是比较 它们作为字符串将按其数值对它们进行排序。
ZADD myindex 0 00324823481:foo
ZADD myindex 0 12838349234:bar
ZADD myindex 0 00000000111:zap
ZRANGE myindex 0 -1
1) "00000000111:zap"
2) "00324823481:foo"
3) "12838349234:bar"
我们使用一个数值字段有效地创建了一个索引,该字段可以是 大如我们所愿。这也适用于任何精度的浮点数 通过确保我们用前导零左填充数字部分,并将 decimal 部分,尾随零,如以下数字列表所示:
01000000000000.11000000000000
01000000000000.02200000000000
00000002121241.34893482930000
00999999999999.00000000000000
使用二进制形式的数字
以十进制存储数字可能会占用太多内存。另一种方法
只是为了将数字(例如 128 位整数)直接存储在它们的
binary 形式。但是,要使其正常工作,您需要以 big endian 格式存储数字,以便最重要的字节存储在
最低有效字节。这样,当 Redis 将字符串与memcmp()
,它将按数字的值有效地对数字进行排序。
请记住,以二进制格式存储的数据不太容易被观察到 debugging,更难解析和导出。所以这绝对是一种权衡。
复合索引
到目前为止,我们探索了为单个字段编制索引的方法。然而,我们都知道 SQL 存储能够使用多个字段创建索引。例如 我可以按房间号和价格为非常大的商店中的产品编制索引。
我需要运行查询才能检索给定 房间有给定的价格范围。我能做的是为每个产品编制索引 采用以下方式:
ZADD myindex 0 0056:0028.44:90
ZADD myindex 0 0034:0011.00:832
以下是字段room:price:product_id
.我只使用了四位数的填充
为了简单起见,在示例中。辅助数据(产品 ID)不会
需要任何填充物。
有了这样的索引,要得到 56 号房间的所有产品都有一个价格 10 到 30 美元之间非常简单。我们可以运行以下命令 命令:
ZRANGE myindex [0056:0010.00 [0056:0030.00 BYLEX
以上称为组合索引。其有效性取决于 我要运行的字段和查询的顺序。例如上面的 index 无法有效地使用,以获取所有具有 特定的价格范围,与房间号无关。但是,我可以使用 主键,以便运行查询而不管价格如何,例如 Give Me the All the Products in Room 44.
复合索引非常强大,用于传统商店 以优化复杂查询。在 Redis 中,它们可能两者都有用 实现存储在 传统的数据存储,或者为了直接为 Redis 数据编制索引。
更新字典索引
索引在字典索引中的值可以变得非常花哨 并且很难或缓慢地从我们存储的有关对象的信息中重建。所以一个 方法来简化索引的处理,但代价是使用更多 memory 的 Memory,也要与表示索引的排序集并列 将对象 ID 映射到当前索引值的哈希值。
因此,例如,当我们索引时,我们还会添加到哈希值中:
MULTI
ZADD myindex 0 0056:0028.44:90
HSET index.content 90 0056:0028.44:90
EXEC
这并不总是需要的,但简化了更新作
索引。为了删除旧信息,我们为对象编制了索引
ID 90,则无论对象的当前 fields 值如何,我们只需
必须通过对象 ID 检索哈希值,并且ZREM
it 在 sorted 中
Set View 设置视图。
使用 hexastore 表示和查询图形
复合索引的一个很酷的一点是它们按顺序很方便 来表示图形,使用称为 Hexastore 的数据结构。
hexastore 为对象之间的关系提供表示。 由主语、谓语和宾语组成。 对象之间的简单关系可以是:
antirez is-friend-of matteocollina
为了表示这种关系,我可以存储以下元素 在我的词典索引中:
ZADD myindex 0 spo:antirez:is-friend-of:matteocollina
请注意,我在我的项目前面加上了字符串 spo。这意味着 该项目表示 subject、predicate、object 关系。
in 可以为同一关系再添加 5 个条目,但顺序不同:
ZADD myindex 0 sop:antirez:matteocollina:is-friend-of
ZADD myindex 0 ops:matteocollina:is-friend-of:antirez
ZADD myindex 0 osp:matteocollina:antirez:is-friend-of
ZADD myindex 0 pso:is-friend-of:antirez:matteocollina
ZADD myindex 0 pos:is-friend-of:matteocollina:antirez
现在事情开始变得有趣起来,我可以在许多
不同的方式。例如,who are all the peopleantirez
是朋友吗?
ZRANGE myindex "[spo:antirez:is-friend-of:" "[spo:antirez:is-friend-of:\xff" BYLEX
1) "spo:antirez:is-friend-of:matteocollina"
2) "spo:antirez:is-friend-of:wonderwoman"
3) "spo:antirez:is-friend-of:spiderman"
或者,所有的关系是什么antirez
和matteocollina
有 where
第一个是主语,第二个是宾语?
ZRANGE myindex "[sop:antirez:matteocollina:" "[sop:antirez:matteocollina:\xff" BYLEX
1) "sop:antirez:matteocollina:is-friend-of"
2) "sop:antirez:matteocollina:was-at-conference-with"
3) "sop:antirez:matteocollina:talked-with"
通过组合不同的查询,我可以提出花哨的问题。例如:谁是我的朋友,就像啤酒一样,住在巴塞罗那,而 matteocollina 也认为是朋友?为了获得此信息,我从spo
查询以查找所有人员
我是朋友。然后,对于我得到的每个结果,我都会执行一个spo
查询
检查他们是否喜欢啤酒,删除我找不到的那些
这种关系。我再次执行此作以按城市进行筛选。最后,我执行ops
查询以查找我获得的列表,谁被认为是朋友
马特奥科利纳。
请务必查看 Matteo Collina 关于 Levelgraph 的幻灯片,以便更好地理解这些想法。
多维索引
更复杂的索引类型是允许您执行查询的索引 其中,两个或多个变量同时查询特定的 范围。例如,我可能有一个数据集,表示 people 年龄和 薪水,我想找回所有 50 到 55 岁之间的人 薪水在 70000 到 85000 之间。
可以使用多列索引执行此查询,但这需要 us 选择第一个变量,然后扫描第二个变量,这意味着我们 可能会做比需要更多的工作。可以执行这些类型的 涉及使用不同数据结构的多个变量的查询。 例如,多维树(如 k-d 树或 r 树)是 有时使用。在这里,我们将介绍一种将数据索引的不同方法 多个维度,使用表示技巧,允许我们执行 使用 Redis 字典范围以非常有效的方式进行查询。
假设我们在空间中有点,这些点代表我们的数据样本,其中x
和y
是我们的坐标。这两个变量的最大值均为 400。
在下图中,蓝色框表示我们的查询。我们希望所有x
介于 50 和 100 之间,其中y
介于 100 和 300 之间。
为了表示使这些类型的查询快速执行的数据, 我们首先用 0 填充我们的数字。因此,例如,假设我们想 将点 10,25 (x,y) 添加到我们的索引中。假设 例如是 400,我们可以只填充到三位数,所以我们得到:
x = 010
y = 025
现在我们要做的是交错数字,取最左边的数字 在 x 中,最左边的数字在 y 中,依此类推,以便创建一个 数:
001205
这是我们的索引,但是为了更容易地重建原始 表示形式,如果我们愿意(以空间为代价),我们还可以添加 原始值作为附加列:
001205:10:25
现在,让我们来了解一下这种表示形式以及为什么它在
范围查询的上下文。例如,让我们以蓝色
box 的x=75
和y=200
.我们可以像以前一样对这个数字进行编码
之前通过交错数字,获得:
027050
如果我们分别用 00 和 99 替换最后两位数字会发生什么? 我们得到一个按字典顺序连续的范围:
027000 to 027099
这映射到一个表示所有值的正方形,其中x
variable 介于 70 和 79 之间,并且y
variable 介于 200 和 209 之间。
要识别这个特定区域,我们可以在该区间内写入随机点。
因此,上面的字典查询允许我们轻松地查询 图片中的特定方格。但是,正方形对于 box 中,因此需要太多的查询。 所以我们可以做同样的事情,但不是将最后两位数字替换为 00 和 99,我们可以对最后四位数字执行此作,得到以下内容 范围:
020000 029999
这次,范围表示x
介于 0 和 99 之间
和y
介于 200 和 299 之间。在此区间内绘制随机点
向我们展示这个更大的区域。
所以现在我们的区域对于我们的查询来说太大了,我们的搜索框仍然是 不完全包含。我们需要更多的粒度,但我们可以很容易地获得 它通过以二进制形式表示我们的数字。这一次,当我们替换 数字,而不是得到大十倍的方格,我们得到方格 它们只是大两倍。
我们的二进制形式的数字,假设每个变量只需要 9 位 (为了表示值不超过 400 的数字)将为:
x = 75 -> 001001011
y = 200 -> 011001000
因此,通过交错数字,我们在索引中的表示将是:
000111000011001010:75:200
让我们看看当我们替换最后的 2、4、6、8 时,我们的范围是什么...... 交错表示形式中具有 0 和 1 的位:
2 bits: x between 74 and 75, y between 200 and 201 (range=2)
4 bits: x between 72 and 75, y between 200 and 203 (range=4)
6 bits: x between 72 and 79, y between 200 and 207 (range=8)
8 bits: x between 64 and 79, y between 192 and 207 (range=16)
等等。现在我们肯定有更好的粒度!
如您所见,从索引中替换 N 位会得到
side 的搜索框2^(N/2)
.
所以我们要做的是检查搜索框较小的维度, 并检查最接近此数字的 2 的幂。我们的搜索框 是 50,100 到 100,300,因此它的宽度为 50,高度为 200。 我们取两者中较小的一个 50,并检查最接近的 2 的幂 即 64。64 是 2^6,因此我们将使用替换 交错表示中的最新 12 位(以便我们结束 每个变量仅替换 6 位)。
然而,单个方块可能无法覆盖我们的所有搜索,因此我们可能需要更多。 我们所做的是从搜索框的左下角开始, 即 50,100,然后通过替换最后 6 位来找到第一个范围 在每个数字中为 0。然后我们对右上角执行相同的作。
使用两个简单的嵌套 for 循环,我们只增加有效 bits,我们可以找到这两者之间的所有方块。对于每个方格,我们 将两个数字转换为我们的交错表示形式,并创建 使用转换后的表示作为 start 的范围,并且相同的 表示形式,但将最新的 12 位作为结束范围打开。
对于找到的每个方块,我们执行查询并获取其中的元素 删除搜索框之外的元素。
将其转换为代码很简单。下面是一个 Ruby 示例:
def spacequery(x0,y0,x1,y1,exp)
bits=exp*2
x_start = x0/(2**exp)
x_end = x1/(2**exp)
y_start = y0/(2**exp)
y_end = y1/(2**exp)
(x_start..x_end).each{|x|
(y_start..y_end).each{|y|
x_range_start = x*(2**exp)
x_range_end = x_range_start | ((2**exp)-1)
y_range_start = y*(2**exp)
y_range_end = y_range_start | ((2**exp)-1)
puts "#{x},#{y} x from #{x_range_start} to #{x_range_end}, y from #{y_range_start} to #{y_range_end}"
# Turn it into interleaved form for ZRANGE query.
# We assume we need 9 bits for each integer, so the final
# interleaved representation will be 18 bits.
xbin = x_range_start.to_s(2).rjust(9,'0')
ybin = y_range_start.to_s(2).rjust(9,'0')
s = xbin.split("").zip(ybin.split("")).flatten.compact.join("")
# Now that we have the start of the range, calculate the end
# by replacing the specified number of bits from 0 to 1.
e = s[0..-(bits+1)]+("1"*bits)
puts "ZRANGE myindex [#{s} [#{e} BYLEX"
}
}
end
spacequery(50,100,100,300,6)
虽然这并非立即微不足道,但这是一个非常有用的索引策略, 将来可能会以本机方式在 Redis 中实现。 目前,好处是复杂性可能很容易封装 在可用于执行索引和查询的库中。 此类库的一个例子是 Redimension,这是一个概念验证 Ruby 库,它使用此处描述的技术为 Redis 中的 N 维数据编制索引。
具有负数或浮点数的多维索引
表示负值的最简单方法是使用 unsigned 整数并使用偏移量表示它们,这样当您索引时,在 在索引表示中平移数字时,将 absolute 值相加 的较小负整数。
对于浮点数,最简单的方法可能是将它们转换为 通过将整数乘以 10 的幂与 要保留的点之后的位数。
非范围索引
到目前为止,我们检查了对按 range 或 single 查询有用的索引 项目。但是,可以使用其他 Redis 数据结构,例如 Sets 或 Lists 来构建其他类型的索引。它们非常常用,但 也许我们并不总是意识到它们实际上是一种索引形式。
例如,我可以将对象 ID 索引为 Set 数据类型,以便使用
获取随机元素作通过SRANDMEMBER
为了检索
一组随机对象。集合也可用于检查是否存在
我所需要的只是测试给定的项目是否存在或是否具有单个布尔值
属性与否。
同样,可以使用 lists 将项目索引到固定顺序中。
我可以将所有项目添加到 Redis 列表中,并使用RPOPLPUSH
使用与 source 和 destination 相同的键名称。这很有用
当我想在
相同的顺序。考虑需要刷新本地副本的 RSS 源系统
周期性地。
Redis 经常使用的另一个常用索引是上限列表,其中 items
添加LPUSH
并修剪LTRIM
,以便创建视图
仅遇到最新的 N 项,其顺序与
明显。
索引不一致
在几个月内保持指数更新可能具有挑战性 或者由于软件的原因,可能会添加不一致 错误、网络分区或其他事件。
可以使用不同的策略。如果索引数据在 Redis 读取修复之外,则 read fix 可能是一种解决方案,其中数据以懒惰的方式修复,当
这是请求的。当我们索引存储在 Redis 本身中的数据时
这SCAN
命令系列可用于验证、更新或
从头开始逐步重建索引。