Redis数据结构和操作

   日期:2024-12-27    作者:b1251063 移动:http://3jjewl.riyuangf.com/mobile/quote/73398.html

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客户端执行)。

Redis数据结构和操作

set mykey somevalue OK get mykey "somevalue" ?1 ?2 ?3 ?4 正如你所看到的,GET和SET命令用于设置或获取一个字符串值。需要注意的是,如果键已经存在,SET会覆盖它的值,即使与这个键相关联的不是字符串类型的值。SET相当于赋值。? 值可以是任意类型的字符串(包含二进制数据),你也可以使用一个jpeg图像。值在大小不能大于512MB。? SET命令配上一些额外的参数,可以实现一些有趣的功能。例如,我可以要求如果键已经存在,SET就会失败,或者相反,键已经存在时SET才会成功。 set mykey newval nx (nil) set mykey newval xx OK ?1 ?2 ?3 ?4 虽然字符串是最基础的数据类型,你仍可以对它执行一些有趣的操作,比如原子性的自增: set counter 100 OK incr counter (integer) 101 incr counter (integer) 102 incrby counter 50 (integer) 152 ?1 ?2 ?3 ?4 ?5 ?6 ?7 ?8 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 30 OK mget a b c

set mykey hello OK exists mykey (integer) 1 del mykey (integer) 1 exists mykey (integer) 0 ?1 ?2 ?3 ?4 ?5 ?6 ?7 ?8 DEL返回1还是0取决于键是(键存在)否(键不存在)被删除掉了。? 有许多键空间相关的命令,但以上这两个命令和TYPE命令是最基本的。TYPE命令的作用是返回这个键的值的类型。 set mykey x OK type mykey string del mykey (integer) 1 type mykey none ?1 ?2 ?3 ?4 ?5 ?6 ?7 ?8 键的生命周期 在介绍更多更复杂的数据结构之间,我们先讨论另一个与值类型无关的特性,那就是redis的期限(redis expires)。最基本的,你可以给键设置一个超时时间,就是这个键的生存周期。当生存周期过去了,键会被自动销毁,就好像被用户执行过DEL一样。? 一些关于redis期限的快速信息: ?生存周期可以设置的时间单位从秒级到毫秒级。 ?生存周期的时间精度都是1毫秒。 ?关于生存周期的数据有多份且存在硬盘上,基于Redis服务器停止了,时间仍在流逝,这意味着redis存储的是key到期的时间。 设置生存周期是件琐碎的事情: set key some-value OK expire key 5 (integer) 1 get key (immediately) "some-value" get key (after some time) (nil) ?1 ?2 ?3 ?4 ?5 ?6 ?7 ?8 键在两次调用之间消失了,这是因为第二次调用的延迟了超过5秒的时间。在上面的例子中,我们使用EXPIRE命令设置生命周期(它也可以用于为一个已经设置过生命周期的键重新设置生命周期,PERSIST命令可以用于移除键的命令周期,使它能够长期存在)。我们还可以使用redis命令在创建键的同时设置生命周期。比如使用带参数的SET命令: set key 100 ex 10 OK ttl key (integer) 9 ?1 ?2 ?3 ?4 上面这个例子中创建了一个键,它的值是字符串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 -1

rpush mylist 1 2 3 4 5 "foo bar" (integer) 9 lrange mylist 0 -1

rpush mylist a b c (integer) 3 rpop mylist "c" rpop mylist "b" rpop mylist "a" ?1 ?2 ?3 ?4 ?5 ?6 ?7 ?8 我们增加和删除的三个元素,因此最后列表是空的,没有元素可以删除。如果我们尝试继续删除元素,会得到这样的结果: rpop mylist (nil) ?1 ?2 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 2 OK lrange mylist 0 -1

brpop tasks 5

del mylist (integer) 1 lpush mylist 1 2 3 (integer) 3 ?1 ?2 ?3 ?4 然而,我们不能对一个已经存在的键执行与它类型不同的操作: set foo bar OK lpush foo 1 2 3 (error) WRONGTYPE Operation against a key holding the wrong kind of value type foo string ?1 ?2 ?3 ?4 ?5 ?6 规则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 ?1 ?2 ?3 ?4 ?5 ?6 ?7 ?8 ?9 ?10 ?11 ?12 当所有元素被取出,这个键就不存在了。? 规则3举例: del mylist (integer) 0 llen mylist (integer) 0 lpop mylist (nil) ?1 ?2 ?3 ?4 ?5 ?6 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

hmget user:1000 username birthyear no-such-field

hincrby user:1000 birthyear 10 (integer) 1987 hincrby user:1000 birthyear 10 (integer) 1997 ?1 ?2 ?3 ?4 你可以查看这篇文档《hash命令全列》? 把小的哈希(少量的元素,较小的值)用特殊的编码方式存放在内存中并不是什么难事,因此它们的空间效率非常高。 redis的集(sets) Redis的集是字符串无序的集合。SADD向集中增加一些元素。对于集合还有很多其它的操作,例如测试某个给定的元素是否存在,多个集合之间求交集、合集或者差集,等。 sadd myset 1 2 3 (integer) 3 smembers myset

sismember myset 3 (integer) 1 sismember myset 30 (integer) 0 ?1 ?2 ?3 ?4 “3”是这个集中的一员,而“30”不是。? 集善于表现对象之间的关系。例如我们可以很容易使用集实现标签(tags)。处理这个问题的一个简单的模型就是把所有要打标签的对象设置一个集。集包含相关对象的标签的ID。? 假设我们想要为新闻加标签。ID为1000的新闻被打上1,2,5和77这几个标签,我们可以用一个集将这些标签ID与新闻关联起来: sadd news:1000:tags 1 2 5 77 (integer) 4 ?1 ?2 然而有时我会想要相反的关系:列表中的所有新闻都被打上一个给定的标签: 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 ?1 ?2 ?3 ?4 ?5 ?6 ?7 ?8 要获取一个对象的所有标签是很麻烦的。 smembers news:1000:tags

sinter tag:1:news tag:2:news tag:10:news tag:27:news ... results here ... ?1 ?2 求交集运算不是唯一可以执行的操作,你还可以执行求并集运算、求差集运算、提取任意一个元素等。? 提取一个元素的命令是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 ?1 ?2 ?3 ?4 ?5 现在我们给每个玩家提供5张牌。SPOP命令移除一个随机的元素,并把它返回给客户端,因此在这个用例中是最好的操作。? 然后如果我们直接对deck调用它,下一轮游戏我们需要把再次填写所有的牌,这还不够理想。因此在开始之前,先把集中存储的deck键做一个备份到game中。使用SUNIONSTORE来实现,把结果存到另一个集中。这个命令通常是对多个集做求并集运行的。对一个集求并集运算就是它自己,因此可以用于复制: sunionstore game:1:deck deck (integer) 52 ?1 ?2 现在我已经准备好为第一个玩家发五张牌了。 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" ?1 ?2 ?3 ?4 ?5 ?6 ?7 ?8 ?9 ?10 一对J,不太好。。。? 现在是时候介绍集中元素个数的命令了。在集理论中,元素个数常被为集的势,因此这个命令是SCARD。 scard game:1:deck (integer) 47 ?1 ?2 计算公式: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 ?2 ?3 ?4 ?5 ?6 ?7 ?8 ?9 ?10 ?11 ?12 ?13 ?14 ?15 ?16 ?17 ?18 正如你所见,ZADD和SADD相似,但是需要一个额外的参数(位置在要加的元素之前),这就是分数。ZADD也是参数可变的,你可以随意地定义多个“分数-值”对,虽然上面的例子没有这么写。? 要求有序集中的黑客名单按他们的出生年份排序是没有意义的,因为它们已经是这样的了。? 实现细节:有序集是基于一个双端口数据结构实现的,包含一个跳跃表和一个哈希表。因此增加一个元素的执行时间是O(log(N))。这很好,当我们请求有序的元素时不需要其它的工作,它们已经是排序的了: zrange hackers 0 -1

zrevrange hackers 0 -1

zrange hackers 0 -1 withscores

zrangebyscore hackers -inf 1950

zremrangebyscore hackers 1940 1960 (integer) 4 ?1 ?2 ZREMRANGEBYSCORE命令的名字也许不是很好,但是真的很有用,它返回被移除的元素的个数。? 另一个为有序集元素定义的非常有用的操作是获取排名(get-rank)操作。可以询问一个元素在它的有序集中的位置。 zrank hackers "Anita Borg" (integer) 4 ?1 ?2 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" ?1 ?2 ?3 基于有序集的排序规则,它们是字典序排序的: zrange hackers 0 -1

zrangebylex hackers [B [P


特别提示:本信息由相关用户自行提供,真实性未证实,仅供参考。请谨慎采用,风险自负。


举报收藏 0评论 0
0相关评论
相关最新动态
推荐最新动态
点击排行
{
网站首页  |  关于我们  |  联系方式  |  使用协议  |  隐私政策  |  版权隐私  |  网站地图  |  排名推广  |  广告服务  |  积分换礼  |  网站留言  |  RSS订阅  |  违规举报  |  鄂ICP备2020018471号