联 系 我 们
售前咨询
售后咨询
微信关注:星环科技服务号
更多联系方式 >
3 Keybyte数据类型
更新时间:7/19/2024, 7:21:58 AM

简介

Keybyte支持五种数据类型:

  1. 字符串(strings)

  2. 字符串列表(lists)

  3. 字符串集合(sets)

  4. 有序字符串集合(sorted sets)

  5. 哈希(hashes)

  6. Streams(keybyte5.0新数据类型)

对于Keybyte中的key,有三点要尤其注意:

  1. key不要太长,尽量不要超过1024字节,这不仅消耗内存,而且会降低查找的效率;

  2. key也不要太短,太短的话,key的可读性会降低;

  3. 在一个项目中,key最好使用统一的命名模式,例如user:10000:passwd。

  4. 允许的最大key大小为 512 MB。

string

String是最常用的数据类型

通常可以存储图片,音频 , 视频等

value最大上限512M , 建议不要超过1M

Keybyte 字符串类型是您可以与Keybyte 键关联的最简单的值类型。它是Memcached中唯一的数据类型,所以新手在Keybyte中使用它也是很自然的。

由于Keybyte 键是字符串,当我们也使用字符串类型作为值时,我们将一个字符串映射到另一个字符串。字符串数据类型可用于许多用例,例如缓存HTML 片段或页面。

让我们玩一下字符串类型,使用redis-cli(redis-cli本教程中的所有示例都将通过)。

> set mykey somevalue

OK

> get mykey

"somevalue"
复制

正如您所见,使用SET和GET命令是我们设置和检索字符串值的方式。请注意,SET将替换已存储在键中的任何现有值,如果键已经存在,即使键与非字符串值相关联。所以SET执行一个赋值。

值可以是各种字符串(包括二进制数据),例如您可以在值中存储 jpeg图像。值不能大于 512 MB。

该SET命令有有趣的选项,这是作为附加参数。例如,如果键已经存在,我可能会要求SET失败,或者相反,只有当键已经存在时它才会成功:

> set mykey newval nx

(nil)

> set mykey newval xx

OK
复制

即使字符串是Keybyte的基本值,您也可以使用它们执行一些有趣的操作。例如,一个是原子增量:

> set counter 100

OK

> incr counter

(integer) 101

> incr counter

(integer) 102

> incrby counter 50

(integer) 152
复制

INCR 命令将字符串值解析成整型,将其加一,最后将结果保存为新的字符串值。还有其他类似的命令,如INCRBY、DECR和DECRBY。在内部,它总是相同的命令,以稍微不同的方式行事。

INCR 是原子的意味着什么?即使多个客户端针对同一个key发出 INCR 也永远不会进入竞争条件。例如,永远不会发生客户端 1 读取“10”,客户端2同时读取“10”,两者都增加到11,并将新值设置为 11。最终值将始终为12,读取的值 -在所有其他客户端未同时执行命令时执行增量设置操作。

有许多用于操作字符串的命令。例如,GETSET命令将键设置为新值,返回旧值作为结果。您可以使用此命令,例如,如果您有一个系统, 每次您的网站收到新访问者时都会使用INCR递增Keybyte key。您可能希望每小时收集一次此信息,而不要丢失一个增量。您可以GETSET键,为其分配新值“0”并读回旧值。

在单个命令中设置或检索多个键的值的能力对于减少延迟也很有用。出于这个原因,有MSET和MGET命令:

> mset a 10 b 20 c 30

OK

> mget a b c

1) "10"

2) "20"

3) "30"
复制

MGET 命令返回由值组成的数组。

List

为了解释 List 数据类型,最好从一些理论开始,因为信息技术人员经常以不正确的方式使用术语List。例如,“Python 列表”并不是名称所暗示的(链接列表),而是数组(实际上在 Ruby 中将相同的数据类型称为数组)。

从一个非常普遍的角度来看,List 只是一个有序元素的序列:10,20,1,2,3 是一个列表。但是使用 Array 实现的 List 的属性与使用Linked List实现的 List 的属性非常不同 。

Keybyte 列表是通过链表实现的。这意味着即使列表中有数百万个元素,在列表的头部或尾部添加新元素的操作也是在常数时间内执行的。使用LPUSH命令在10个元素的列表头添加一个新元素的速度与在1000万个元素的列表头添加一个元素的速度是一样的。

那么这样做有什么有什么缺点呢?在使用 Array 实现的列表(恒定时间索引访问)中通过索引访问元素非常快,而在由链表实现的列表中则没有那么快(其中操作需要的工作量与访问元素的索引成正比)。

Keybyte 列表是用链表实现的,因为对于数据库系统来说,能够以非常快的方式将元素添加到很长的列表中是至关重要的。正如您稍后将看到的,另一个强大的优势是可以在恒定时间以恒定长度获取 Keybyte 列表。

当快速访问大量元素的中间很重要时,可以使用不同的数据结构,称为排序集(sorted sets)。本教程稍后将介绍排序集。

Keybyte 列表的第一步

所述LPUSH命令将一个新元素到一个列表,在左侧(在头部),而RPUSH命令将一个新元素到一个列表,在右侧(在尾部)。最后 LRANGE命令从列表中提取元素范围:

> rpush mylist A

(integer) 1

> rpush mylist B

(integer) 2

> lpush mylist first

(integer) 3

> lrange mylist 0 -1

1) "first"

2) "A"

3) "B"
复制

请注意,LRANGE需要两个索引,即要返回的范围的第一个和最后一个元素。两个索引都可以是负数,告诉 Keybyte 从末尾开始计数:所以 -1 是最后一个元素,-2 是列表的倒数第二个元素,依此类推。

如您所见,RPUSH将元素附加在列表右侧,而最终的LPUSH将元素附加在列表左侧。

这两个命令都是可变参数命令,这意味着您可以在一次调用中自由地将多个元素推送到列表中:

> rpush mylist 1 2 3 4 5 "foo bar"

(integer) 9

> lrange mylist 0 -1

1) "first"

2) "A"

3) "B"

4) "1"

5) "2"

6) "3"

7) "4"

8) "5"

9) "foo bar"
复制

Keybyte 列表上定义的一个重要操作是弹出元素的能力。弹出元素是同时从列表中检索元素和从列表中删除元素的操作。您可以从左侧和右侧弹出元素,类似于如何在列表的两侧推送元素:

> rpush mylist a b c

(integer) 3

> rpop mylist

"c"

> rpop mylist

"b"

> rpop mylist

"a"
复制

我们添加了三个元素并弹出了三个元素,所以在这个命令序列的末尾,列表是空的,没有更多的元素要弹出。如果我们尝试弹出另一个元素,这就是我们得到的结果:

> rpop mylist

(nil)

Keybyte 返回一个 NULL 值以表示列表中没有元素。
复制

列表的常见用例

列表对许多任务很有用,以下是两个非常有代表性的用例:

l 记住用户发布到社交网络的最新更新。

l 进程之间的通信,使用消费者-生产者模式,其中生产者将项目推送到列表中,而消费者(通常是工作人员)消费这些项目并执行操作。Keybyte 有特殊的列表命令,使这个用例更加可靠和高效。

例如,流行的 Ruby 库resque和 sidekiq都在后台使用 Keybyte 列表来实现后台作业。

流行的 Twitter 社交网络将 用户发布的最新推文放入 Keybyte 列表。

为了逐步描述一个常见的用例,假设您的主页显示了在照片共享社交网络中发布的最新照片,并且您希望加快访问速度。

每次用户发布新照片时,我们都会将其 ID 添加到带有LPUSH的列表中。

当用户访问首页时,我们使用LRANGE 0 9以获取最新发布的10个项目。

上限列表

在许多用例中,我们只想使用列表来存储最新的项目,无论它们是什么:社交网络更新、日志或其他任何东西。

Keybyte 允许我们使用列表作为上限集合,只记住最新的 N 项并使用LTRIM命令丢弃所有最旧的项。

LTRIM命令类似于LRANGE,而不是显示元件的规定的范围内将其设置在该范围作为新的列表值。删除给定范围之外的所有元素。

一个例子会更清楚:

> rpush mylist 1 2 3 4 5

(integer) 5

> ltrim mylist 0 2

OK

> lrange mylist 0 -1

1) "1"

2) "2"

3) "3"
复制

上面的LTRIM命令告诉 Keybyte 只获取从索引 0 到 2 的列表元素,其他所有元素都将被丢弃。这允许一个非常简单但有用的模式:一起做一个列表推送操作 + 一个列表修剪(trim)操作,以便添加一个新元素并丢弃超过限制的元素:

LPUSH mylist <some element>

LTRIM mylist 0 999
复制

上述组合添加了一个新元素,并且只将 1000 个最新元素放入列表中。使用LRANGE,您可以访问最重要的项目,而无需记住非常旧的数据。

注意:虽然LRANGE在技术上是一个O(N)命令,但访问列表头部或尾部的小范围是一个恒定时间操作。

LIST上的阻塞操作

List有一个特殊的特性,使它们适合实现队列,并且通常作为进程间通信系统的构建块:阻塞操作。

想象一下,您想通过一个流程将项目推送到列表中,并使用不同的流程来实际处理这些项目。这是通常的生产者/消费者设置,可以通过以下简单方式实现:

  1. 要将项目推送到列表中,生产者调用LPUSH。

  2. 为了从列表中提取/处理项目,消费者调用RPOP。

然而,有时列表是空的并且没有任何要处理的东西是可能的,所以RPOP只返回 NULL。在这种情况下,消费者被迫等待一段时间并使用RPOP重试。这称为polling,在这种情况下不是一个好主意,因为它有几个缺点:

  1. 强制 Keybyte 和客户端处理无用的命令(列表为空时的所有请求将不会完成任何实际工作,它们只会返回 NULL)。

  2. 为项目的处理添加延迟,因为在工作人员收到 NULL 后,它会等待一段时间。为了使延迟更小,我们可以在调用RPOP之间减少等待,从而放大问题 1,即更多无用的 Keybyte 调用。

这是我们可以在 worker 中使用的BRPOP调用的示例:

> brpop tasks 5

1) "tasks"

2) "do_something"
复制

这意味着:“等待列表中的元素tasks,但如果 5 秒后没有元素可用则返回”。

请注意,您可以使用 0 作为超时来永久等待元素,也可以指定多个列表而不是一个,以便同时等待多个列表,并在第一个列表接收到元素时得到通知。

关于BRPOP 的一些注意事项:

  1. 客户端以有序的方式提供服务:第一个阻止等待列表的客户端,当某个元素被其他客户端推送时首先服务,依此类推。

  2. 返回值与RPOP不同:它是一个二元素数组,因为它还包含键的名称,因为BRPOP和BLPOP能够阻止等待来自多个列表的元素。

  3. 如果达到超时,则返回 NULL。

关于列表和阻塞操作,您还应该了解更多信息。我们建议您阅读以下内容:

  • 可以使用LMOVE构建更安全的队列或轮换队列。

  • 该命令还有一个阻塞变体,称为BLMOVE。

 

set

Keybyte集合是无序的字符串集合。该SADD命令添加新的一组元素。还可以对集合执行许多其他操作,例如测试给定元素是否已经存在,执行多个集合之间的交集、并集或差集,等等。

> sadd myset 1 2 3

(integer) 3

> smembers myset

1. 3

2. 1

3. 2
复制

在这里,我向我的集合中添加了三个元素,并告诉 Keybyte 返回所有元素。正如您所看到的,它们没有排序——Keybyte 可以在每次调用时以任何顺序自由返回元素,因为与用户没有关于元素排序的合同。

Keybyte 有测试成员资格的命令。例如,检查元素是否存在:

> sismember myset 3

(integer) 1

> sismember myset 30

(integer) 0
复制

“3”是集合的成员,而“30”不是。

集合有利于表达对象之间的关系。例如,我们可以轻松地使用集合来实现标签。

对这个问题建模的一个简单方法是为我们想要标记的每个对象设置一个集合。该集合包含与对象关联的标签的 ID。

一个例子是标记新闻文章。如果文章 ID 1000 被标记为标签 1、2、5 和 77,则集合可以将这些标签 ID 与新闻项目相关联:

> sadd news:1000:tags 1 2 5 77

(integer) 4
复制

我们也可能希望有相反的关系:用给定标签标记的所有新闻的列表:

> sadd tag:1:news 1000

(integer) 1

> sadd tag:2:news 1000

(integer) 1

> sadd tag:5:news 1000

(integer) 1

> sadd tag:77:news 1000

(integer) 1
复制

获取给定对象的所有标签很简单:

> smembers news:1000:tags

1. 5

2. 1

3. 77

4. 2
复制

注意:在示例中,我们假设您有另一个数据结构,例如 Keybyte 哈希,它将标签 ID 映射到标签名称。

还有其他重要的操作仍然可以使用正确的 Keybyte 命令轻松实现。例如,我们可能想要一个包含标签 1、2、10 和 27 的所有对象的列表。我们可以使用SINTER命令执行此操作,该命令执行不同集合之间的交集。我们可以用:

> sinter tag:1:news tag:2:news tag:10:news tag:27:news

... results here ...
复制

除了交集,您还可以执行联合、差异、提取随机元素等。

提取元素的命令称为SPOP,可以方便地对某些问题进行建模。例如,为了实现基于 Web 的扑克游戏,您可能需要用一组来代表您的套牌。想象一下,我们对©lubs、(D)iamonds、(H)earts、(S)pades 使用一个字符的前缀:

>  sadd deck C1 C2 C3 C4 C5 C6 C7 C8 C9 C10 CJ CQ CK

   D1 D2 D3 D4 D5 D6 D7 D8 D9 D10 DJ DQ DK H1 H2 H3

   H4 H5 H6 H7 H8 H9 H10 HJ HQ HK S1 S2 S3 S4 S5 S6

   S7 S8 S9 S10 SJ SQ SK

   (integer) 52
复制

现在我们要为每个玩家提供 5 张牌。该SPOP命令删除一个随机元素,将其返回到客户端,所以在这种情况下完美运行。

然而,如果我们直接针对我们的牌组调用它,在下一次游戏中我们需要再次填充一副牌,这可能并不理想。因此,首先,我们可以将存储在deck key中的集合复制到game:1:deck key中。

这是使用SUNIONSTORE完成的,它通常执行多个集合之间的并集,并将结果存储到另一个集合中。然而,由于单个集合的并集本身,我可以复制我的套牌:

> sunionstore game:1:deck deck

(integer) 52

现在我准备为第一个玩家提供五张牌:

> spop game:1:deck

"C6"

> spop game:1:deck

"CQ"

> spop game:1:deck

"D1"

> spop game:1:deck

"CJ"

> spop game:1:deck

"SJ"
复制

现在是介绍 set 命令的好时机,该命令提供集合中元素的数量。 在集合论的上下文中,这通常称为集合的基数,因此Keybyte 命令称为SCARD。

> scard game:1:deck

(integer) 47
复制

数学计算:52 - 5 = 47。

当您只需要获取随机元素而不将它们从集合中删除时,有适合该任务的SRANDMEMBER命令。它还具有返回重复和非重复元素的能力。

 

sorted set

sorted set 是 string 类型的有序集合

不允许有重复的成员,不同成员可以有相同的score

集合中每一个成员都会关联一个double类型的score用于排序

可以做集合和集合之间的操作

集合的最大长度是2^32 - 1

案例:

各种系统中排名的存储

金钱排名

经验排名

等级排名

有序集合是一种类似于集合和哈希混合的数据类型。与集合一样,有序集合由唯一的、不重复的字符串元素组成,因此在某种意义上,有序集合也是一个集合。

然而,虽然集合内的元素没有排序,排序集合中的每个元素都与一个浮点值相关联,称为分数(这就是为什么类型也类似于散列,因为每个元素都映射到一个值)。

此外,有序集合中的元素是按顺序获取的(因此它们不是按请求排序的,顺序是用于表示有序集合的数据结构的特性)。它们根据以下规则排序:

  • 如果 A 和 B 是具有不同分数的两个元素。如果 A.score 是 > B.score,则 A > B 。

  • 如果 A 和 B 的分数完全相同。如果 A 字符串按字典顺序大于 B 字符串,则 A > B 。A 和 B 字符串不能相等,因为排序集只有唯一元素。

让我们从一个简单的例子开始,添加一些选定的黑客名称作为排序集合元素,他们的出生年份作为“分数”。

> zadd hackers 1940 "Alan Kay"

(integer) 1

> zadd hackers 1957 "Sophie Wilson"

(integer) 1

> zadd hackers 1953 "Richard Stallman"

(integer) 1

> zadd hackers 1949 "Anita Borg"

(integer) 1

> zadd hackers 1965 "Yukihiro Matsumoto"

(integer) 1

> zadd hackers 1914 "Hedy Lamarr"

(integer) 1

> zadd hackers 1916 "Claude Shannon"

(integer) 1

> zadd hackers 1969 "Linus Torvalds"

(integer) 1

> zadd hackers 1912 "Alan Turing"

(integer) 1
复制

正如您所看到的,ZADD类似于SADD,但需要一个额外的参数(放置在要添加的元素之前),即分数。 ZADD也是可变参数,因此您可以自由指定多个分值对,即使在上面的示例中没有使用。

使用排序集返回一个按出生年份排序的黑客列表是微不足道的,因为实际他们已经排序了。

实现说明:Sorted set 是通过双端口数据结构实现的,包含一个skip list 和一个hash table,所以每次我们添加一个元素Keybyte 都会执行一个O(log(N)) 的操作。这很好,但是当我们要求排序元素时,Keybyte 根本不需要做任何工作,它已经全部排序了:

> zrange hackers 0 -1

1) "Alan Turing"

2) "Hedy Lamarr"

3) "Claude Shannon"

4) "Alan Kay"

5) "Anita Borg"

6) "Richard Stallman"

7) "Sophie Wilson"

8) "Yukihiro Matsumoto"

9) "Linus Torvalds"
复制

注意:0 和 -1 表示从元素索引 0 到最后一个元素(-1 在这里的作用就像在LRANGE命令的情况下一样)。

如果我想以相反的方式订购它们,从小到大怎么办?使用ZREVRANGE而不是ZRANGE:

> zrevrange hackers 0 -1

1) "Linus Torvalds"

2) "Yukihiro Matsumoto"

3) "Sophie Wilson"

4) "Richard Stallman"

5) "Anita Borg"

6) "Alan Kay"

7) "Claude Shannon"

8) "Hedy Lamarr"

9) "Alan Turing"
复制

也可以使用WITHSCORES参数返回分数:

> zrange hackers 0 -1 withscores

1) "Alan Turing"

2) "1912"

3) "Hedy Lamarr"

4) "1914"

5) "Claude Shannon"

6) "1916"

7) "Alan Kay"

8) "1940"

9) "Anita Borg"

10) "1949"

11) "Richard Stallman"

12) "1953"

13) "Sophie Wilson"

14) "1957"

15) "Yukihiro Matsumoto"

16) "1965"

17) "Linus Torvalds"

18) "1969"
复制

在范围内操作

排序集比这强大得多。它们可以在范围内操作。让我们把所有出生到 1950 年的人都包括在内。我们使用ZRANGEBYSCORE命令来做到这一点:

> zrangebyscore hackers -inf 1950

1) "Alan Turing"

2) "Hedy Lamarr"

3) "Claude Shannon"

4) "Alan Kay"

5) "Anita Borg"
复制

我们要求 Keybyte 返回分数在负无穷大和 1950 之间的所有元素(包括两个极端)。

也可以删除元素范围。让我们从排序集中删除所有在 1940 年到 1960 年之间出生的黑客:

> zremrangebyscore hackers 1940 1960

(integer) 4
复制

ZREMRANGEBYSCORE可能不是最好的命令名称,但它常有用,并返回已删除元素的数量。

为有序集合元素定义的另一个非常有用的操作是 get-rank 操作。可以问一个元素在有序元素集中的位置是什么。

> zrank hackers "Anita Borg"

(integer) 4
复制

该ZREVRANK命令也可以为了获得军衔,考虑的要素排序的下降方式。

词典分数

在 Keybyte引入了一个新功能,允许按字典顺序获取范围,假设已排序集合中的元素都以相同的分数插入(元素与 Cmemcmp函数进行比较 ,因此保证没有排序规则,并且每个 Keybyte 实例都将使用相同的输出进行回复)。

操作字典范围的主要命令是ZRANGEBYLEX、 ZREVRANGEBYLEX、ZREMRANGEBYLEX和ZLEXCOUNT。

例如,让我们再次添加我们的著名黑客列表,但这次对所有元素使用零分:

> zadd hackers 0 "Alan Kay" 0 "Sophie Wilson" 0 "Richard
Stallman" 0

  "Anita Borg" 0 "Yukihiro Matsumoto" 0 "Hedy Lamarr" 0 "Claude
Shannon"

  0 "Linus Torvalds" 0 "Alan Turing"
复制

由于 sorted set 的排序规则,它们已经按字典顺序排序了:

> zrange hackers 0 -1

1) "Alan Kay"

2) "Alan Turing"

3) "Anita Borg"

4) "Claude Shannon"

5) "Hedy Lamarr"

6) "Linus Torvalds"

7) "Richard Stallman"

8) "Sophie Wilson"

9) "Yukihiro Matsumoto"

使用ZRANGEBYLEX我们可以要求字典范围:

> zrangebylex hackers [B [P

1) "Claude Shannon"

2) "Hedy Lamarr"

3) "Linus Torvalds"
复制

范围可以是包含的或不包含的(取决于第一个字符),也可以分别使用+和-字符串指定字符串无穷大和负无穷大。

这个特性很重要,因为它允许我们使用排序集作为通用索引。例如,如果您想通过 128 位无符号整数参数索引元素,您需要做的就是将元素添加到具有相同分数(例如 0)但具有 16 字节前缀的有序集合中,其中包含128 big endian 中的位数。由于大端的数字,当按字典顺序(按原始字节顺序)实际上也是按数字排序时,您可以在 128 位空间中请求范围,并获得元素的值,丢弃前缀。

更新分数:排行榜

在切换到下一个主题之前,只是关于排序集的最后说明。排序集的分数可以随时更新。仅针对已包含在排序集中的元素调用ZADD将更新其分数(和位置),时间复杂度为O(log(N))。因此,当有大量更新时,排序集是合适的。

由于这个特性,一个常见的用例是排行榜。典型的应用程序是 Facebook 游戏,您可以将按高分排序的用户与获取排名操作结合起来,以显示前 N 个用户,以及用户在排行榜中的排名(例如,“你是这里的#4932 最高分”)。

 

hash

Hash 类型是一个 string 类型的 field 和 value 的映射表

底层通过 ziplist 或是 hash table 实现

具体采用何种数据结构实现是由 field 的数量和 value的长度决定

Field 和 value 对的数量不超过 2^32-1

Keybyte 散列看起来与人们可能期望的“散列”看起来完全一样,具有字段值对:

> hmset user:1000 username antirez birthyear 1977 verified
1

OK

> hget user:1000 username

"antirez"

> hget user:1000 birthyear

"1977"

> hgetall user:1000

1) "username"

2) "antirez"

3) "birthyear"

4) "1977"

5) "verified"

6) "1"
复制

虽然散列可以方便地表示对象,但实际上可以放入散列中的字段数量没有实际限制(可用内存除外),因此您可以在应用程序中以多种不同方式使用散列。

命令HMSET设置散列的多个字段,而HGET检索单个字段。HMGET类似于HGET,但返回一组值:

> hmget user:1000 username birthyear no-such-field

1) "antirez"

2) "1977"

3) (nil)
复制

有些命令也可以对单个字段执行操作,例如HINCRBY:

> hincrby user:1000 birthyear 10

(integer) 1987

> hincrby user:1000 birthyear 10

(integer) 1997
复制

Streams

Stream 是引入的一种新数据类型,它以更抽象的方式对日志数据结构进行建模。然而,日志的本质仍然完好无损:就像日志文件一样,通常以仅附加模式打开的文件实现,Keybyte Streams 主要是仅附加数据结构。至少在概念上,由于作为内存中表示的抽象数据类型,Keybyte Streams 实现了强大的操作来克服日志文件的局限性。

尽管数据结构本身非常简单,但使 Keybyte 流成为最复杂的 Keybyte 类型的原因是它实现了额外的非强制性功能:一组阻塞操作,允许消费者等待生产者添加到流中的新数据,此外还有一个叫做Consumer Groups的概念。

消费者组最初是由流行的消息传递系统 Kafka ™ 引入的。Keybyte 用完全不同的术语重新实现了一个类似的想法,但目标是相同的:允许一组客户端合作使用同一消息流的不同部分。

了解更多关于Streams的用法,请参阅命令中Streams的部分。