redis数据结构有哪些内容
本篇内容主要讲解“redis数据结构有哪些内容”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“redis数据结构有哪些内容”吧!
redis不只是一个简单的键(key)-值(value)数据库,实际上它是一个数据结构服务器,支持各种类型的值。也就是说,在传统的键-值数据库中,你把字符串键与字符串值联系起来,而在redis,值不仅限于一个简单的字符串,还可以是更复杂的数据结构。下面列出了所有redis支持的数据结构,下文会分别对这些结构进行介绍:
二进制安全字符串
队列(lists):基于插入顺序有序存储的字符串元素集合。主要是链式的list。
集(sets):元素唯一的、无序的字符串元素集合。
有序集(sorted sets):与sets相似,但是每个字符串元素都与一个被称为分数(score)的浮点数相关联。和sets不同的是,元素能够基于分数排序,因此可以检索某个范围内的元素(比如你可以查询前10个或后10个)。
哈希(hashes):由域(fields)和值之间关系组成的映射。域和值都是字符串。这和Ruby或Python的哈希非常相似。
位数组(位图bitmaps):可以通过特殊命令,像处理位图一样地处理字符串:设置和清除某一位,统计被置1的位数,找到第一个被设置或没有被设置的位等。
HyperLogLogs:这是一种概率数据结构,用于估算集的势。不要被吓到了,没那么难。本文将在下文中HyperLogLog章节介绍。
遇到问题的时候,理解数据结构是怎么工作的以及怎么被使用的并不是那么微不足道的事情。因此,这篇文档是一个关于Redis数据类型和它们常用模式的速成教材。
这里所有的例子,我们都使用redis客户端(redis-cli)。相对于redis服务器来说,这是一个简单方便的命令行控制台。
redis的键
redis的键是二进制安全【1】的,也说是说,你可以使用任意的二进制序列作为键,比如字符串”foo”或一个JPEG文件的内容。
空串也是一个有效的键。
一些关于键的其它规则:
太长的键不推荐。例如长度为1024字节的键并不好,不管是从内存角度,还是从查询键的角度。因为从数据集中查询键需要多次的键匹配步骤。即使手边的任务就是要判断一个很大的值是否存在,采用某种手段对它做hash是个好主意,尤其是从内存和带宽的角度去考虑。
太短的键通常也不推荐。如果你把键“user:1000:followers”写成“u1000flw”可能会有点问题。因为前者可读性更好,而只需要多花费一点点的空间。短的键显然占的花费的空间会小一点,因此你需要找到平衡点。
尽量坚持模式。例如”object-type:id”是推荐的,就像”user:1000”。点和短线常用于多个单词的场景,比如”comment:1234:reply.to”或”comment:1234:reply-to”。
键的大小不能超过512MB。
Redis中的字符串
Redis中的字符串类型是可以与键关联的最简单的类型。它中Memcached中唯一的数据类型,也是Redis新手最常用的类型。
由于Redis的键都是字符串,那么把使用字符串为值,也就是字符串到字符串的映射。字符串数据类型可以用于许多场景,比如缓存HTML片段或页面。
让我们用redis客户端尝试一些字符串类型的使用吧(本文所有的例子都在redis客户端执行)。
> set mykey somevalueOK> get mykey"somevalue"
正如你所看到的,GET和SET命令用于设置或获取一个字符串值。需要注意的是,如果键已经存在,SET会覆盖它的值,即使与这个键相关联的不是字符串类型的值。SET相当于赋值。
值可以是任意类型的字符串(包含二进制数据),你也可以使用一个jpeg图像。值在大小不能大于512MB。
SET命令配上一些额外的参数,可以实现一些有趣的功能。例如,我可以要求如果键已经存在,SET就会失败,或者相反,键已经存在时SET才会成功。
> set mykey newval nx(nil)> set mykey newval xxOK
虽然字符串是最基础的数据类型,你仍可以对它执行一些有趣的操作,比如原子性的自增:
> set counter 100OK> incr counter(integer) 101> incr counter(integer) 102> incrby counter 50(integer) 152
INCR命令把字符串解析成一个整数,然后+1,把得到的结果作为一个新值存进去。还有其它相似的命令:INCRBY, DECR, DECRBY。从命令的实现原理上讲,这几个命令是相同的,只是有一点细微的差别。
为什么说INCR是原子性的呢?因为即使是多个客户端对同一个键使用INCR命令,也不会形成竞争条件。举个例子,像这样的情况是不会发生的:客户端1读取到键是10,客户端2也读到键值是10,它们同时对它执行自增命令,最终得到的值是11。实际上,最终的得到的值是12,因为当一个客户端对键值做读-自增-写的过程中,其它的客户是不能同时执行这个过程的。
有许多用于操作字符串的命令,例如GETSET命令,它给键设置一个新值,并返回旧值。比如你有一个系统,每当有一个新的访问者登陆你的网站时,使用INCR对一个键值自增。你可能想要统计每个小时的信息,却又不希望丢失每次自增操作。你可以使用GETSET命令,设置一个新值“0”,同时读取旧值。
redis支持通过一条命令同时设置或读取多个键,这对于减少延时很有用。这就是MSET命令和MGET命令:
> mset a 10 b 20 c 30OK> mget a b c1) "10" 2) "20" 3) "30"
使用MGET时,redis返回包含多个值的数组。
更改或查询键空间
【2】
有些命令并没有指定特定的类型,但在与键空间的交互有非常有用,因此可以用于任意类型的键。
举个例子,EXISTS命令返回1或者0,用于表示某个给定的键在数据库中是否存在。DEL命令删除键以及它对应的值而不管是什么值。
> set mykey helloOK> exists mykey(integer) 1> del mykey(integer) 1> exists mykey(integer) 0
DEL返回1还是0取决于键是(键存在)否(键不存在)被删除掉了。
有许多键空间相关的命令,但以上这两个命令和TYPE命令是最基本的。TYPE命令的作用是返回这个键的值的类型。
> set mykey xOK> type mykeystring> del mykey(integer) 1> type mykeynone
键的生命周期
在介绍更多更复杂的数据结构之间,我们先讨论另一个与值类型无关的特性,那就是redis的期限(redis expires)。最基本的,你可以给键设置一个超时时间,就是这个键的生存周期。当生存周期过去了,键会被自动销毁,就好像被用户执行过DEL一样。
一些关于redis期限的快速信息:
生存周期可以设置的时间单位从秒级到毫秒级。
生存周期的时间精度都是1毫秒。
关于生存周期的数据有多份且存在硬盘上,基于Redis服务器停止了,时间仍在流逝,这意味着redis存储的是key到期的时间。
设置生存周期是件琐碎的事情:
> set key some-valueOK> expire key 5(integer) 1> get key (immediately)"some-value"> get key (after some time)(nil)
键在两次调用之间消失了,这是因为第二次调用的延迟了超过5秒的时间。在上面的例子中,我们使用EXPIRE命令设置生命周期(它也可以用于为一个已经设置过生命周期的键重新设置生命周期,PERSIST命令可以用于移除键的命令周期,使它能够长期存在)。我们还可以使用redis命令在创建键的同时设置生命周期。比如使用带参数的SET命令:
> set key 100 ex 10OK> ttl key(integer) 9
上面这个例子中创建了一个键,它的值是字符串100,生命周期是10秒。后面的TTL命令用于查看键的剩余时间。
如果要以毫秒为单位设置或查询键的生命周期,请查询PEXPIRE命令和PTTL命令,以及SET命令的参数列表。
redis中的列表(lists)
要解释列表数据类型,最好先从一点理论开始。因为列表这个术语常被信息技术人员错误地使用。例如“python 列表”,并不像它的命令所提示的(链表),而是数组(实际上与Ruby中的数组是同一个数据类型)。
从广义上讲,列表只是元素的有序序列:10,20,1,2,3是一个列表。但是用数组实现的列表和用链表实现的列表,它们的属性有很大的不同。
redis的列表都是用链表的方式实现的。也就是说,即使列表中有数百万个元素,增加一个新元素到列表头部或尾部操作的执行时间是常数时间。使用LPUSH命令把一个新元素增加到一个拥有10个元素的列表的头部,或是增加到一个拥有一千万个元素的列表的头部,其速度是一样的。
缺点是什么呢?通过索引访问一个元素的操作,在数组实现的列表中非常快(常数时间),但在链表实现的列表中不是那么快(与找到元素对应下标的速度成比例)。
redis选择用链表实现列表,因为对于一个数据库来说,快速地向一个很大的列表新增元素是非常重要的。另一个使用链表的强大优势,你稍后将会看到,能够在常数时间内得到一个固定长度的redis列表。
快速地读取很大一堆元素的中间元素也是重要的,这时可以使用另一种数据结构,称为有序集(sorted sets)。本文后面会讲到有序集。
regis列表第一步
LPUSH命令把一个新的元素加到列表的左边(头部),而RPUSH命令把一个新的元素加到列表的右边(尾部)。LRANGE命令从列表中提取某个范围内的元素
> rpush mylist A(integer) 1> rpush mylist B(integer) 2> lpush mylist first(integer) 3> lrange mylist 0 -11) "first" 2) "A" 3) "B"
注意,LRANGE命令需要输入两个下标,即范围的第一个元素下标和最后一个元素下标。两个下标都可以是负的,意思是从尾部开始数:因此-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"
Redis中定义了一个重要的操作就是删除元素。删除命令可以同时从列表中检索和删除元素。你可以从左边或者右边删除元素,和从两边增加元素的方法类似:
> rpush mylist a b c(integer) 3> rpop mylist"c"> rpop mylist"b"> rpop mylist"a"
我们增加和删除的三个元素,因此最后列表是空的,没有元素可以删除。如果我们尝试继续删除元素,会得到这样的结果:
> rpop mylist(nil)
redis返回空值说明列表中没有元素了。
列表的常见用例
列表可以用于完成多种任务,以下是两个非常有代表性的用例:
记住用户发布到社交网络的最新更新。
使用消费者-生产者模型进行进程间通信,生产生把表项(items)放进列表中,消费者(通常是工作者)消费这些items并执行一些行为。redis针对这种用例有一些特殊的列表命令,既可靠又高效。
例如非常有名的Ruby库resque和sidekip,在底层都使用了Redis列表来实现后台作业。
著名的社交网络Twitter使用Redis列表来获取用户发布的最新的消息。
为了一步一步地描述一个常见用例,假设要在你的主页上展示社交网络上最新分享的照片并且加速访问。
每当一个用户发布了一张新的照片,我们使用LPUSH命令把它的ID加入到列表中。
当用户访问这个主页,我们使用LRANGE 0 9获取最新加入的10个表项。
限制列表
很多情况下我们只想要使用列表来存储最新的几条表项,例如社交网络更新、日志或者其它。
Redis允许我们使用列表作为一个固定集合,使用LTRIM命令,只记录最新的N条记录,而丢弃所有更早的记录。
LTRIM命令和LRANGE命令相似,但不像LRANGE一样显示特定范围的元素,而是用这个范围内的值重新设置列表。所有范围外的元素都被删除了。
用个例子来说明这一点:
> rpush mylist 1 2 3 4 5(integer) 5> ltrim mylist 0 2OK> lrange mylist 0 -11) "1" 2) "2" 3) "3"
上面的LTRIM命令告诉Redis只取得列表中下标为0到2的元素,其它的都要丢弃。这就是一种简单有用的模式成为了可能:列表增加(push)元素操作+列表提取(trim)元素操作=增加一个元素同时删除一个元素使得列表元素总数有限:
LPUSH mylist <some element>LTRIM mylist 0 999
上面的操作结合增加一个元素但只是存在1000个最新的元素在列表中。通过LRANGE你可以获取最新的表项而不需要记住旧的数据。
注意:由于理论上LRANGE是O(N)命令,读取从头开始或从尾开始的小范围数据是常数时间的操作。
列表中是阻塞型操作
列表的一些特性使它适合现实队列(queues),也通常作为进程间通信系统的一个基础组件:阻塞式操作。
假设你通过一个进程把元素增加到列表中,使用另一个进程对这些元素做些实际的操作。这是通常的生产者/消费者基础,你可以用下面这种简单的方法实现:
生产者调用LPUSH,把元素加入列表
消费者调用RPOP,把元素从列表中取出或处理
有没有可能出现这种情况,列表是空的,没有什么东西可以处理,因此RPOP返回NULL。这种情况下,消费者不得不等一会再尝试RPOP。这就叫轮询。这并不是一个好方法,因为它有以下缺点:
要求redis和客户端执行没有意义的命令(当列表为空是所有的请求都不会执行实际工作,只是返回NULL)
工作者在收到NULL之后加入一个延时,让它等待一些时间。如果让延时小一点,在两次调用RPOP之间的等待时间会比较短,这成为第一个问题的放大-调用Redis更加没有意义
因此Redis实现了命令BRPOP和BLPOP,它是RPOP和LPOP的带阻塞功能的版本:当列表为空时,它们会等到一个新的元素加入到列表时,或者用户定义的等待时间到了时,才会返回。
这是BRPOP调用的一个例子,我们可以在工作者进程使用它:
> brpop tasks 5 1) "tasks"2) "do_something"
它的意思是:等待列表中的元素,如果5秒还没有可用的元素。
注意,如果使用0作为超时时间,将会永远等待,你也可以定义多个列表而不只是一个,这样就会同时等待多个列表,当任意一个列表收到一个元素时就会收到通知。
一些关于BRPOP需要注意的事情:
客户端是按顺序被服务的:第一个等待某个列表的客户端,当列表被另一个客户端增加一个元素时,它会第一个处理。
返回值与RPOP的不同:只得到两个元素的包含键名的数组,因为BRPOP和BLPOP因为等待多个列表而阻塞。
如果时间超时了,就会返回NULL
还有更多你应该知道的关于列表和阻塞操作的东西。我们建议你阅读以下材料:
可以使用RPOPLPUSH创建更安全的队列或旋转队列。
这个命令有一个阻塞参数,即BRPOPLPUSH
自动创建和移除键
到目前为止我们的例子还没有涉及到这些情景,在增加一个元素之间创建一个空的列表,或者当一个列表没有元素时把它移除。redis有责任删除变为空的列表,或当我们试图增加元素时创建空列表。例如LPUSH
这不仅适用于列表,它可以应用于所有包含多个元素的Redis数据结构-集、有序集和哈希。
基本上讲,我们把它的行为总结为三个规则:
当我们把一个元素增加到一个集合类数据类型时,如果这个键不存在,在增加前会创建一个空的集合类数据类型。
我们从一个集合类数据类型中移除一个元素时,如果值保持为空,键就会被自动删除
调用一个只读命令例如LLEN(返回列表的长度),或者一个移除元素的写命令但键为空,结果不会改变。[3]
规则1举例:
> del mylist(integer) 1> lpush mylist 1 2 3(integer) 3
然而,我们不能对一个已经存在的键执行与它类型不同的操作:
> set foo barOK> lpush foo 1 2 3(error) WRONGTYPE Operation against a key holding the wrong kind of value> type foostring
规则2举例:
> lpush mylist 1 2 3(integer) 3> exists mylist(integer) 1> lpop mylist"3"> lpop mylist"2"> lpop mylist"1"> exists mylist(integer) 0
当所有元素被取出,这个键就不存在了。
规则3举例:
> del mylist(integer) 0> llen mylist(integer) 0> lpop mylist(nil)
redis中的哈希(hashed)
redis的哈希和我们所认识的“哈希”非常相似,是域-值对。
> 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"
hash表示对象(object)非常方便。实际上,可以放入一个hash的域的数量没有限制(不考虑可用内存),因此你可以在你的应用中用许多不同的方式使用哈希。
HMSET命令为hash设置多个域,而HGET获取某一个域。HMGET和HGET相似,但它返回由值组成的数组。
> hmget user:1000 username birthyear no-such-field1) "antirez"2) "1977"3) (nil)
还有一些命令可以对单个的域执行操作,例如HINCRBY:
> hincrby user:1000 birthyear 10(integer) 1987> hincrby user:1000 birthyear 10(integer) 1997
你可以查看这篇文档《hash命令全列》
把小的哈希(少量的元素,较小的值)用特殊的编码方式存放在内存中并不是什么难事,因此它们的空间效率非常高。
redis的集(sets)
Redis的集是字符串无序的集合。SADD向集中增加一些元素。对于集合还有很多其它的操作,例如测试某个给定的元素是否存在,多个集合之间求交集、合集或者差集,等。
> sadd myset 1 2 3(integer) 3> smembers myset1. 32. 13. 2
在这个例子中,我向myset中增加了三个元素,并让redis返回所有的元素。正如你所看到的,它们是无序的。每次调用,redis都可能以任何顺序返回元素,因此在这里,用户不能对元素的顺序有要求。
redis提供测试成员的命令。这个给定的元素是否存在?
> sismember myset 3(integer) 1> sismember myset 30(integer) 0
“3”是这个集中的一员,而“30”不是。
集善于表现对象之间的关系。例如我们可以很容易使用集实现标签(tags)。处理这个问题的一个简单的模型就是把所有要打标签的对象设置一个集。集包含相关对象的标签的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:tags1. 52. 13. 774. 2
注意:在这个例子中我们假设你还有另一个数据结构,例如redis的哈希,用于标签ID到标签名的映射。
如果使用正确的redis命令,可以通过并不繁琐却简单的方式去实现。例如我们可能想到同时拥有1,2,10和27标签的所有对象。我们可以使用SINTER命令执行不同集之间的求交运算。我们可以这么用:
> sinter tag:1:news tag:2:news tag:10:news tag:27:news... results here ...
求交集运算不是唯一可以执行的操作,你还可以执行求并集运算、求差集运算、提取任意一个元素等。
提取一个元素的命令是SOP,它对于模拟某些问题很方便。例如要实现一个基于网页的扑克牌游戏,你可能会把你的牌(deck)做成一个集。假设我们使用一个字符前缀来表示C(梅花)、D(方块)、H(红心)、S(黑桃):
> 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调用它,下一轮游戏我们需要把再次填写所有的牌,这还不够理想。因此在开始之前,先把集中存储的deck键做一个备份到game中。使用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"
一对J,不太好。。。
现在是时候介绍集中元素个数的命令了。在集理论中,元素个数常被为集的势,因此这个命令是SCARD。
> scard game:1:deck(integer) 47
计算公式:52-5=47
如果你只是想得到一个随机的元素但不把它从集中删除,SRANDMEMBER命令适合这个任务。它还可以提供返回重复元素或非重要元素的功能。
Redis的有序集
有序集像一种将集和哈希混合的数据类型。像集一样,有序集由唯一的不重复的字符串元素组成。因此某种意义上说,有序集也是一个集。
集中的元素是无序的,而有序集中的元素都基于一个相关联的浮点值排序。这个浮点值称为分数(score)(每个元素都映射到一个值,因此和哈希相似)。
此外,有序集中的元素是按顺序取的(它们不是按照要求排序的,排序是这个数据结构用于表现有序集的一个特性【4】)。它们按照下面的规则排序:
假设A和B是分值不同的两个元素,如果A的分数>B的分数,则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
1
正如你所见,ZADD和SADD相似,但是需要一个额外的参数(位置在要加的元素之前),这就是分数。ZADD也是参数可变的,你可以随意地定义多个“分数-值”对,虽然上面的例子没有这么写。
要求有序集中的黑客名单按他们的出生年份排序是没有意义的,因为它们已经是这样的了。
实现细节:有序集是基于一个双端口数据结构实现的,包含一个跳跃表和一个哈希表。因此增加一个元素的执行时间是O(log(N))。这很好,当我们请求有序的元素时不需要其它的工作,它们已经是排序的了:
> 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命令中的-1一样)。
如果想要反向排序,从最年轻到最老呢?使用ZREVERANGE代替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"
我们请求Redis返回所有分数在无限到1950(两边都是闭区间)之间的元素。
也可以移除某个范围内的元素。我们要从有序集中移除所有出生年份在1940和1960之间的黑客:
> zremrangebyscore hackers 1940 1960(integer) 4
ZREMRANGEBYSCORE命令的名字也许不是很好,但是真的很有用,它返回被移除的元素的个数。
另一个为有序集元素定义的非常有用的操作是获取排名(get-rank)操作。可以询问一个元素在它的有序集中的位置。
> zrank hackers "Anita Borg"(integer) 4
ZREVRANK命令也可以获取排名,不过元素是逆序排序的。
字典序的分数
最近的Redis 2.8版本引入了一个新特性,假设有序集中所有元素的分数相同(使用C语言的memcmp函数来比较元素,这样保证每个redis实例都会返回相同的结果)的情况下,允许按照字典序获得范围。【5】
针对字典序范围操作的主要命令是ZRANGEBYLEX、ZREVRANGEBYLEX、ZREMRANGEBYLEX和ZLEXCOUNT。
举个例子,我们再次把所有著名黑客加入到列表中,但这一次所有元素的分数都是0:
> 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"
基于有序集的排序规则,它们是字典序排序的:
> 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)以及一个由128位数值组成的8字节前缀。由于数值是大端的,字典序的顺序(原始字节序)实际上是数字序的,你可以在128位空间请求范围,以前缀降序返回元素的值。
如果你想查看这个特性更严谨的演示,请查看 Redis autocomplete demo.
更新分数:排行榜
这是在切换到下一个话题之前最后一个关于有序集的点。有序集的分数可以随时被更新。只需要对一个在有序集中已经存在的元素执行ZADD就可以在O(log(N))更新它的分数(和位置)。同样的,当会经常更新时使用有序集非常合适。
这个特性的一个通常用例是排行榜。典型的应用是Facebook的一个游戏,你可以使用户基于它们的高分排序,增加获取排名的操作,在排行榜上显示前N个用户及用户排名(例:你是第4932好的分数)
位图
位图其实不是一个真正的数据类型,只是在这种字符串类型上有一系列基于位的操作。由于字符串是二进制安全的,最大长度是512MB,因此可以设置多达2^32种不同的位串。
位操作分为两类,一种是针对某一个位的常数时间操作,如果设置某个位为1或0,或获取某个位的值。另一种是对所有位的操作,例如计算一个给定范围内被设置为1的位的个数(如人数统计)。
位图一个最大的优势就是存储信息时非常少空间。例如一个系统里面每个用户用一个不同的递增的用户ID表示,可以记录40亿用户的某个信息(这个用户是否想要接收邮件)只需要512M的内存。
通过SETBIT命令和GETBIT命令设置或获取位:
> setbit key 10 1(integer) 1> getbit key 10(integer) 1> getbit key 11(integer) 0
SETBIT命令把第一个参数作为位的序号,第二个参数作为这个位要设置的值,只能是1或0。如果地址位大于当前字符串的长度,这个命令会自动扩充字符串。
GETBIT只返回位于某个位置的值。超过长度的位(地址位大于字符串的长度)将必然得到0。
这三个命令是对所有位的操作:
BITOP:执行不同字符串之间的位操作。包括AND、OR、XOR和NOT。
BITCOUNT:执行统计操作,返回位集中1的个数
BITPOS:找到第一个被设置为0或1的位置
BITPOS和BITCOUNT都可以接受位的地址范围作为参数,这样就不对对整个字符串做操作。下面是一个关于BITCOUNT的简单例子:
> setbit key 0 1(integer) 0> setbit key 100 1(integer) 0> bitcount key(integer) 2
位图的通常用法:
各种实时分析
存储要求空间时间高效的与对象ID关联的二进制信息。
假如你想知道你的网页用户中每天访问最长的时间【6】,你从开始记录天数,你把你的网页公开化的第一天开始,计数为0,然后每当有用户用户网页,使用SETBIT设置一位。位的下标可以简单的认为是当前系统时间,减去第一天得到偏移,然后除以3600*24。
使用这种方式,对于每一个用户,你有一个包含每天访问信息的字符串来表示。使用BITCOUNT命令可以得到某个用户访问网页的天数。而使用多个BITOPs调用,可能简单地获取和分析位图,就很容易地计算出longest streak。
为了共享数据或避免对一个很大的键操作,bitmap可以分裂成多个键。要把一个位图分裂成多个键而不是全部设置到一个键里面去,一个比较麻烦的方法就是让每个键存储M位,键名为键的数量/M,第N个位在这个键里的地址是数的位置%M。
HyperLogLogs
HyperLogLogs是一个概率性的数据结构,用于计算特别的东西(技术上常用于估算一个集的势)。通过计算一个特别的表项需要使用相对于要计算的表项本身来说很大的内存,因为需要记住你已经见过的元素,以免重复计算。然后有一系列算法可以按精度使用内存:在redis的实现中,你最终得到一个标准错误的估计测量结果的可能性少于1%【7】。这个算法的神奇之处在于你再大需要相对于要计算的表项本身来说很大的内存空间,可能是只使用一个常数数量的空间。最坏情况下12K字节,如果元素少的话,这个空间会更小。
Redis中的HLL,从技术上讲,它是一个完全不同的数据结构,但像字符串一样编码,因此你可以使用GET来串行化一个HLL,使用SET并行到服务器。
从概念上讲,HLL的接口使用集来完成相同的工作。你会使用SADD把每一个观测值写入集中,使用SCARD来查询集中的元素个数。因为SADD不会重复添加一个已存在的元素,因此集中的元素是唯一的。
但是你不能把一个表项真正地添加入一个HLL,因为数据结构只包含并不真正存在的元素的状态,API是一样的:
每当你看见一个新的元素,使用PFADD计数增加
每当你想要恢复当前的近似值,使用PFCOUNT。【8】
> pfadd hll a b c d(integer) 1> pfcount hll(integer) 4
使用这种数据结构的一个例子统计在一个调整中用户每天执行的请求数。
Redis还可以对HLL执行求并集操作,请查询完整文件获取更多信息。
其它显著的特性
关于redis的接口,还有其它一些重要的信息不能放在这个文档中,但是非常值得引起你的注意:
可以递增地迭代键空间
可以在服务器端运行LUA脚本获取潜在因素和带宽
redis还是一个发布-订阅型服务器
REDIS所有的命令
<<ABOUT LIST>>
LPOP key : 删除并取得LIST头部一个元素
RPOP key : 删除并取得LIST尾部一个元素
BLPOP key [key ...] timeout : 删除并取得LIST头部一个元素,如果没有就BLOCK
BRPOP key [key ...] timeout : 删除并取得LIST尾部一个元素,如果没有就BLOCK
LPUSH key value : 在LIST头部扩展一个元素
RPUSH key value : 在LIST尾部扩展一个元素
LPUSHX key value : 如果LIST存在,在LIST头部扩展一个元素
RPUSHX key value : 如果LIST存在,在LIST尾部扩展一个元素
LINDEX key index : 通过INDEX取得LIST的一个元素
LLEN key : 取得LIST的长度
LRANGE key start stop : 取得LIST在指定范围内的元素
LREM key count value : 删除LIST的元素们
LSET key index value : 设置LIST索引为INDEX的元素的值
LTRIM key start stop : 裁剪LIST,保留一定范围的元素
RPOPLPUSH source destination :删除当前LIST的尾部一个元素,并将其扩展到另一个LIST的尾部
BRPOPLPUSH source destination timeout :
弹出LIST一个元素,并将其插入到另一个LIST里,然后返回,如果前个LIST空就BLOCK
SORT key [BY pattern] [LIMIT offset count] [GET pattern [GET pattern ...]] [ASC|DESC] [ALPHA] [STORE destination] :
排序LIST,SET, SORTED SET
<<ABOUT SET>>
SADD key member : 向SET中添加一个成员
SREM key member : 从SET中删除一个成员
SDIFF key [key ...] : 集合求差集
SINTER key [key ...] : 集合求交集
SUNION key [key ...] : 集合求并集
SDIFFSTORE destination key [key ...] : 集合求差集,并保存结果集到另一集合
SINTERSTORE destination key [key ...] : 集合求交集,并保存结果集到另一集合
SUNIONSTORE destination key [key ...] : 集合求并集,并保存结果集到另一集合
SCARD key : 取得SET成员总数
SISMEMBER key member : 判断给定值是否为SET成员
SPOP key : 删除并返回SET任一成员
SRANDMEMBER key : 返回SET任一成员
SMEMBERS key : 取得SET所有成员
SMOVE source destination member : 将一个SET中一个成员移动到另一个SET中
<<ABOUT SORTED SET>>
ZADD key score member : 在SSET中添加一个成员,或者说更新已有成员的SCORE
ZCARD key : 取得SSET的成员总数
ZCOUNT key min max : 计算SSET中SCORE在一个给定范围内的成员总数
ZINCRBY key : 为SSET中的成员自增SCORE
ZINTERSTORE destination numkeys key [key ...] [WEIGHTS weight [weight ...]] [AGGREGATE SUM|MIN|MAX] :
求SSET交集,并将结果集保存到一个新KEY
ZRANGE key start stop [WITHSCORES] : 返回SSET中一定INDEX范围内的成员
ZRANGEBYSCORE key min max [WITHSCORES] [LIMIT offset count] : 返回SSET中一定SCORE范围内的成员
ZREM key member : 删除SSET中一个成员
ZREMRANGEBYRANK key start stop : 删除SSET一定INDEX范围内的成员
ZREMRANGEBYSCORE key min max : 删除SSET一定SCORE范围内的成员
ZREVRANGE key start stop [WITHSCORES] : 返回SSET中一定INDEX范围内的成员,其顺序是SCORE从高到低
ZREVRANGEBYSCORE key max min [WITHSCORES] [LIMIT offset count] : 返回SSET中一定SCORE范围内的成员,其顺序是SCORE从高到低
ZSCORE key member : 获得SSET中与给定MEMBER关联的SCORE
ZUNIONSTORE destination numkeys key [key ...] [WEIGHTS weight [weight ...]] [AGGREGATE SUM|MIN|MAX] :
SSET求并集,并将结果集存到一个新的KEY中
<<ABOUT HASH>>
HDEL key field : 删除一个HASHFIELD
HEXISTS key field : 判断一个HASHFIELD是否存在
HGET key field : 获得一个HASHFIELD的值
HGETALL key : 获得一个HASH所有的FIELDs和VALUEs
HINCRBY key field increment : 对HASHFIELD的整数值增加increment的整数值
HKEYS key : 获得HASH所有的FIELD
HLEN key : 获得HASH内FIELD总数
HMGET key field [field ...] : 获得HASH指定FIELD的所有值
HMSET key field value [field value ...] : 设置HASH的一些FILED和VALUE
HSET key field value : 设置HASH的某FIELD为某值
HSETNX key field value : 当HASH的某FIELD不存在时候,设置其为某值
HVALS key : 获得HASH的所有值
<<ABOUT KEY>>
DEL key [key ...] : 删除一个KEY
GET key : 获得一个KEY的值
SETEX key seconds value : 设置KEY的VALUE和EXP时间
SETNX key value : 设置KEY的VALUE,前提是KEY之前不存在
SET key value : 设置KEY,VALUE
APPEND key value : 向一个KEY扩展一个值
DEBUG OBJECT key : 获得一个KEY的DEBUG信息
DECR key : 给一个KEY-1
DECRBY key integer : 给一个KEY-integer
EXISTS key : 判断一个KEY是否存在
EXPIRE key seconds : 设定一个KEY的TTL(second)
EXPIREAT key timestamp : 设定一个KEY的TTL(unix timestamp)
TTL key : 获得KEY的TTL
PERSIST key : 删除一个KEY的过期标志
KEYS pattern : 查找所有符合PATTERN的KEY
MOVE key db : 将一个KEY移动到另一个DATABASE中
RENAME key newkey : 重命名一个KEY
RENAMENX key newkey : 给一个KEY重命名,前提是新KEYNAME不存在
RANDOMKEY : 从KEYSPACE返回一个任一的KEY
<<ABOUT VALUE>>
GETRANGE key start end : 获得KEY对应的字符串里指定范围的子串
GETSET key value : 设置KEY对应的VALUE,并返回老的VALUE
INCR key : 为KEY对应的整数值自增1
INCRBY key increment : 为KEY对应的整数值自增increment
MGET key [key ...] : 获得所有指定KEY的值
MSET key value [key value ...] : 为指定的KEYS设置指定的VALUES
MSETNX key value [key value ...] : 当指定的KEY存在时候,为指定的KEYS设置指定的VALUES
STRLEN key : 获得KEY的VALUE长度
<<ABOUT SERVER>>
INFO : 获得服务器的状态信息和统计信息
MONITOR : 实时监听SERVER获得的所有请求
PING : Ping服务器
QUIT : 关闭链接
PUBLISH channel message : 发布一个消息到一个CHANNEL
AUTH password : 认证服务器
LASTSAVE : 获得最后一次成功SAVETODISK的时间戳
OBJECT subcommand [arguments [arguments ...]] : 侦测REDIS对象的内部
PSUBSCRIBE pattern [pattern ...] : 监听发布到CHANNEL的所有符合PATTERN的消息
PUNSUBSCRIBE [pattern [pattern ...]] : 停止监听发布到CHANNEL的所有符合PATTERN的消息
CONFIG RESETSTAT : 重设INFO命令返回的状态信息
SUBSCRIBE channel [channel ...] : 监听指定CHANNEL的消息
UNSUBSCRIBE [channel [channel ...]] : 停止监听指定CHANNEL的消息
UNWATCH : Forget about all watched keys 停止监视所有被监视的KEY
WATCH key [key ...] : 监视所有给定的KEY,来判断MULTI和EXEC块的执行
<<ABOUT DATABASE>>
SAVE Synchronously : 保存DATASET到硬盘
SELECT index : 切换当前数据库
BGSAVE : 异步保存DATASET到硬盘
DBSIZE : 返回一个DATABASE的KEY总数
FLUSHALL : 删除所有DATABASE上所有的KEY
FLUSHDB : 删除当前DATABASE上所有的KEY
SHUTDOWN Synchronously : 保存DATASET到硬盘后,关闭服务器
<<CONFIGURE>>
CONFIG GET parameter : 获得一个配置参数值
CONFIG SET parameter value : 设置一个配置参数为给定值
<<OTHER>>
GETBIT key offset : 返回KEY对应的VALUE在OFFSET的比特值
MULTI : 标识一个业务块的开始
SETRANGE key offset value : 从指定的OFFSET开始覆盖写KEY对应的VALUE串
BGREWRITEAOF : 异步重写append-only file
DEBUG SEGFAULT : 使服务器crash
DISCARD : 忽略所有的以MULTI开头的命令
ECHO message : ECHO message
EXEC : 执行所有以MULTI开头的命令
SLAVEOF host port : 使本服务器成为另一REDIS HOST的SLAVE,或者使本服务器成为主服务器
SYNC : 内部备份命令
LINSERT key BEFORE|AFTER refvalue value : 向列表key的refvalue之前或者之后插入value
到此,相信大家对“redis数据结构有哪些内容”有了更深的了解,不妨来实际操作一番吧!这里是蜗牛博客网站,更多相关内容可以进入相关频道进行查询,关注我们,继续学习!
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:niceseo99@gmail.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。
评论