博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Redis 数据结构
阅读量:2391 次
发布时间:2019-05-10

本文共 21759 字,大约阅读时间需要 72 分钟。

文章目录

概述

redis不仅仅可以理解为key-value数据库,更好的理解是redis是一个数据结构化的服务器,value可以支持多种数据类型。传统的key-value数据库,key,value都采用String。在redis中,value不再是单一的String类型,还可以是其他类型的数据结构,以下列出redis支持的几种数据结构,后面会分开介绍。

  • String:二进制安全的String
  • List:保存元素是String,按照插入的顺序排序。基于链表实现的列表
  • Sets:元素在整个集合中是唯一的;元素是无序的
  • Sorted sets:排序的sets,与Sets有点类似
  • Sorted sets, similar to Sets but where every string element is associated to a floating number value, called score. The elements are always taken sorted by their score, so unlike Sets it is possible to retrieve a range of elements (for example you may ask: give me the top 10, or the bottom 10).
  • Hashes, which are maps composed of fields associated with values. Both the field and the value are strings. This is very similar to Ruby or Python hashes.
  • Bit arrays (or simply bitmaps): it is possible, using special commands, to handle String values like an array of bits: you can set and clear individual bits, count all the bits set to 1, find the first set or unset bit, and so forth.
  • HyperLogLogs: this is a probabilistic data structure which is used in order to estimate the cardinality of a set. Don’t be scared, it is simpler than it seems… See later in the HyperLogLog section of this tutorial.
    It’s not always trivial to grasp how these data types work and what to use in order to solve a given problem from the command reference, so this document is a crash course to Redis data types and their most common patterns.

所有的例子我们用redis-cli做展示,redis-cli是一个简单实用的命令行工具,用来操作redis服务器

Redis key

Redis key 的key是二进制安全的,可以使用二进制序列作为一个key,可以是字符串"foo",也可以是一个图片格式转换成二进制之后的内容。如果是空字符串,那么这个key就是无效的。

key的规则

  • 不建议使用太长的key。例如:1024字节的key,增加计算机的内存消耗和计算消耗(查找key,计算hash等的)

  • 不建议使用太短的key。例如,u1000flw和user:1000:followers,后者更方便阅读

  • 建议命名方式。 object-type:id,例如:user:1000。使用中划线(-)或者点(.),例如:“comment🔢reply.to” 或者 “comment🔢reply-to”。redis key的最大值是512MB,

Redis String

Strings类型是value最简单的数据类型,也是Memcached中唯一的数据类型。在redis中非常的受欢迎

因为redis的key是String类型的,当我们的value也用String类型的时候,我们就把一个String映射给另外一个String,这种数据类型适用于很多场景,缓存html代码片段和页面。
接下来我们使用redis-cli(本教程中,所有的例子都会通过redis-cli来演示)玩一下String类型

> set mykey somevalueOK> get mykey"somevalue"

可以看出,使用set和get设置数据和检索数据,如果对应的key已经存在,set之后这个key的值会被替换。

value可以是各种类型的字符串,包括二进制类型的,比如,存储一张jpeg格式的图片,但是value的值不能大于512MB。

set命令有几个有意思的选项:

  • nx:如果key存在,则报错,否则返回OK
  • xx:与nx相反,如果key不存在,则报错,否则返回OK,例,
> set mykey newval nx(nil)> set mykey newval xxOK

即使String是redis最基本的数据类型,但它也提供了很多有趣的操作。例如原子自增:

> set counter 100OK> incr counter(integer) 101> incr counter(integer) 102> incrby counter 50(integer) 152

INCR将string类型按照数字进行解析,然后加1,再把这个值重新赋给key。其他类似的命令还有INCRBY、DECR、DECRBY。其实原理都是一样的,表现上有一些差异而已。

INCR的原子操作是什么样的呢?多个客户端发起对同一个key的操作是安全的。比如,上面的例子中,有两个客户端,客户端1,客户端2同时读counter,不会发生客户端1,客户端2取到的值都是100,然后在100的基础上加1,得到101。这个值最终都是102,因为读取+加1+赋值都是单线程的。

GETSET key value 给key设置一个新值value,同时返回上一个值,比如统计网站访问量,初始值的时候设置为0,在更新的时候完成的数据的读取,不会丢失每一次的访问,例:

> set count 1OK> getset count 2"1"> getset count 3"2"

MSET、MGET 通过一个命令完成数据的设置或者检索有助于减少延迟,例如

> mset a 10 b 20 c 30OK> mget a b c1) "10"2) "20"3) "30"

当使用MGET的时候,Redis返回的是一个数组,hash的get、set速度是O(1)的,查找能力很强,list的get、set速度是O(n)的,因为要遍历。

key的修改和查询

有些常用的命令不是只给特定的key使用的,它可以使用在任何类型的key上,例如EXISTS、DEL。

EXISTS key 存在返回1 ,不存在返回0
DEL key 删除key和相关联的value,无论key是何值,例:

> set mykey helloOK> exists mykey(integer) 1> del mykey(integer) 1> exists mykey(integer) 0

从例子可以看出DEL命令本身也返回了1或者0,这取决于key是否存在或者不存在,如果存在则返回1 ,否则返回0

TYPE 有许多key相关的命令,和上面的命令构成最基本的命令,例如TYPE,返回value对应的数据类型

> set mykey xOK> type mykeystring> del mykey(integer) 1> type mykeynone

Redis expires: key在规定的时间存活,超时之后自动失效,和调用了DEL一样

可以使用秒、毫秒设置过期时间

> set key some-valueOK> expire key 5(integer) 1> get key (immediately)"some-value"> get key (after some time)(nil)

上面的例子中,在第二次执行的get的时候key过期了,因为已经超过了5秒。上面的例子使用命令EXPIRE设置过期(it can also be used in order to set a different expire to a key already having one, like PERSIST can be used in order to remove the expire and make the key persistent forever)我们可以在创建key的时候设置好过期时间,例如

> set key 100 ex 10OK> ttl key(integer) 9

上面的例子,设置一个key的value为100,10秒之后过期。然后用TTL命令来检查还有多久过期。

PEXPIRE,类似EXPIRE,单位是毫秒
PTTL,类似TTL,单位是毫秒

Redis Lists

理解列表数据类型,最好从理论开始,因为计算机术语经常被开发人员不当使用,例如,“Python Lists” 并不像名称一样,而是数组,事实上Ruby里面成为数组

一般的观点,列表只是一个序列有序的元素,10,20,1,2,3 是一个列表,但是基与数组实现的列表和基于链表实现的列表有很大的不同

Redis的列表是通过链表实现的。这意味着,即使在列表中有数以百万计的元素,也可以轻松执行在头部或尾部添加新元素的操作。不管元素有1000万个,还是只有10个,LPUSH命令的速度都是一样的。

有那些缺点呢?基础链表实现的访问速度没有基于数组实现的访问速度快。

对于一个数据库而言,增加元素在一个很长的列表需要很短的时间,这一点很重要。另外一个方面,在某一时刻,你访问最多的是有限的长度(一段时间,一定的长度),所以基于链表实现比基于数组实现更好。

快速访问一个巨大列表中间的数据,数据结构化就可以发挥作用了。那就是排序,排序在后面会讲到。

学习Redis Lists的第一步

LPUSH 在list头部增加一个元素(左侧)
RPUSH 在list尾部增加一个元素(右侧)
LRANGE 从list中取出一定范围的元素

> 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表示倒数第二个,以此类推。0表示从头开始。

...,-5,-4,-3,-2,-1,0,1,2,3,4,5,...

LPUSH、RPUSH还可以实现多个元素一次插入,

> rpush mylist 1 2 3 4 5 "foo bar"(integer) 9> lrange mylist 0 -11) "first"2) "A"3) "B"4) "1"5) "2"6) "3"7) "4"8) "5"9) "foo bar"

删除元素

> rpush mylist a b c(integer) 3> rpop mylist"c"> rpop mylist"b"> rpop mylist"a"> rpop mylist(nil)

最后一个返回NULL,因为列表中没有元素了

列表通用的使用场景

  • 记住用户发布到社交网络中的最新更新。进程间的通信,使用生产-消费模式,生产者将项目推到列表中,而消费者(通常是一个worker)消耗这些项目并执行操作。Redis有特殊列表命令让这个用例更加可靠和有效的。比如流行的Ruby库resque和Sidekiq使用redis实现后台作业。

  • 流行的社交网络平台Twitter,最新发布的推文采用redis的list保存

  • 比如你的微信朋友圈,打开之后显示最新的一条信息,并且以最快的速度访问。

  • 任意时刻一位好友发布了一张新图片,我们把这位用的id RPUSH到list,当用户访问的时候,我们通过 LRANGE 0 9取出10条数据

限制列表

很多使用场景下,我们仅仅想使用列表存储最新的数据,比如:社交网络的更新,日志等。
redis允许我们使用一个限制列表秒,我们记住最新的N条数据,忽略所有的老数据,这个时候我们就可以使用LTRIM命令,LTRIM命令类似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的数据,其他的数据全部被移除。redis还有一个非常简单,但是又很实用的操作:push+trim,增加新元素+移除超出范围的元素:

LPUSH mylist

LTRIM mylist 0 999
上面的组合结果是增加了一个新元素,同时取出1000个最新的元素。使用LRANGE你会拿到需要的数据,而不包含老数据。
Note: while LRANGE is technically an O(N) command, accessing small ranges towards the head or the tail of the list is a constant time operation.

列表的阻塞操作

列表的特殊功能很适合实现队列,在一般情况下,用于进程之间的交互:阻塞操作。

想象一个这样的问题,用一个进程提交了一些数据到list,用另外一个进程取出这些数据,并做一些事情。这就是生产者/消费者配置,下面的通过几个步骤说这这个过程:

推送一条数据到一个list,生产者调用LPUSH。

从list取出这条数据,消费者RPOP。
list有可能是空的,所以RPOP返回NULL。这种情况下,消费者强制等待一会并重新发起RPOP。这个过程称之为轮询,这种情况并不是很好,它有几个缺点:

客户端进程发出的命令都是无效的(当list是空的时候,所有的请求都不能正常工作,返回的都是NULL)

对处理中的进程添加一个延迟,那么在收到一个null之后,它会等待一段时间。使延迟较小,我们可以在调用RPOP的时候减少等待,因为问题1的影响,会有更多无效的redis调用。

BRPOP、BLPOP 如果list是空的,用BRPOP、BLPOP可以实现阻塞,它是RPOP、LPOP的阻塞版本:

只有当一个元素被添加到列表中的时候,或者用户指定超时时,它才会返回

下面是一个BRPOP的例子,我们可以在工作线程中调用

> brpop tasks 51) "tasks"2) "do_something"

意思是:等待列表tasks返回数据,如果5秒钟没有可用数据,就返回

注意,如果timeout设置为0,将会永久等待,也可以同时指定多个列表,而不是一个列表,以便同时在多个列表上等待,并在第一个列表接收元素时得到通知。

使用BRPOP的注意事项:

客户机以有序的方式服务:第一个阻止等待列表的客户机首先在某个元素被其他客户端推送时被服务,等等。
相比于RPOP的返回值是不同的:它是一个两个元素的数组,因为它还包括key的名字,因为BRPOP和BLPOP能够阻止等待来自多个列表元素。

> brpop task 21) "task"2) "a"

上面的例子说明返回了key的值

如果超时,则返回null。
关于列表和阻塞操作,您可以学习更多的知识。我们建议您阅读以下内容:

  • 可以创建安全的队列或队列的使用rpop、lpush。
  • 还有一个rpop、push的升级版本,阻碍命令brpop、lpush。
  • 自动创建和删除键
  • 到目前为止,在我们的示例中,我们没有在插入元素之前创建空列表,或者在不再具有元素的情况下删除空列表。这是redis的责任删除键当列表是空的,或者创建一个空列表如果键是不存在的,例如,我们想添加元素直接调用LPUSH。

上述这几点不仅仅是list,它还适用于所有的Redis数据类型由多个元素集合,有序集合和散列。

基本上我们可以用三条规则来概括:

  • 当我们将元素添加到聚合数据类型时,如果目标键不存在,则在添加元素之前创建空聚合数据类型。

  • 当我们从聚合数据类型中移除元素时,如果该值仍然为空,则该键将自动销毁。

  • 调用一个只读命令如LLEN(返回列表的长度),不管聚合数据类型是否为空,当调用删除命令时,总是产生相同的结果

第1个例子:

> del mylist(integer) 1> lpush mylist 1 2 3(integer) 3

如果key存在,返回错误"(error) WRONGTYPE Operation against a key holding the wrong kind":

> 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

元素被移空之后,key也不存在了

第3个例子:

> del mylist(integer) 0> llen mylist(integer) 0> lpop mylist(nil)

Redis Hashes(哈希)

哈希可以理解为:键值对本身又是一个键值对

HGETALL runoobkey 获取所有

> hmset user:1000 username antirez birthyear 1977 verified 1OK> hget user:1000 username"antirez"> hget user:1000 birthyear"1977"> hgetall user:10001) "username"2) "antirez"3) "birthyear"4) "1977"5) "verified"6) "1"

用hash来代表对象是很合适的,hash可以存放的数量没有限制(可用内存之内),所以你可以在你的应用程序用不同的方式使用哈希表。

HMSET 可以设置多个不同的key:value

HGET 查找一个field
HMGET 类似HGET,但是返回的是一个数组

> hmget user:1000 username birthyear no-such-field1) "antirez"2) "1977"3) (nil)

能够在单个的field执行操作命令,像HINCRBY:

> hincrby user:1000 birthyear 10(integer) 1987> hincrby user:1000 birthyear 10(integer) 1997

值得注意的是,小的值(即,几个元素与小值)是以特殊的方式载入内存,所以处理的能力很强

Redis Sets(集合)

无序且不重复的String集合。SADD命令添加新的元素到集合。也可以针对诸如测试的集合执行许多其他操作,如果给定元素已经存在,执行交集,多个集合之间的联合或区别等。

> sadd myset 1 2 3(integer) 3> smembers myset1. 32. 13. 2

上面的例子我添加了三个元素,并通过smembers返回所有元素。 正如你所看到的,它们没有被排序–Redis可以在每次调用时以任意顺序自由地返回元素,因为与用户没有关于元素排序的约定。

Redis通过命令sismember测试元素是否存在。 例如,检查一个元素是否存在:

> sismember myset 3(integer) 1> sismember myset 30(integer) 0

“3”是该组的成员,而“30”不是。

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

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

例如:标记新闻文章。 如果文章ID 1000标记有标签1,2,5和77,set可以将这些标签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

除了交集之外,你还可以执行联合,区别,提取一个随机元素,等等。

提取元素的命令称为SPOP,对于模拟某些问题很方便。 例如,为了实现一个基于网络的扑克游戏,你可能想要用一套代表你的套牌。 想象一下,我们对(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命令删除一个随机元素,将其返回给客户端,因此在这种情况下它是合适的操作。

然而,如果我们直接对它进行反击,在下一场比赛中,我们需要再次填充一副牌,这可能并不理想。 所以开始时,我们可以将存储在卡组密钥中的一组副本复制到游戏中:1:卡组密钥。

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

> sunionstore game:1:deck deck(integer) 52Now I'm ready to provide the first player with five cards:> 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"

再来看一个命令,redis提供了一个查看集合中元素数量的命令。 这通常被称为集合论集合的基数,因此Redis命令被称为SCARD。

> scard game:1:deck(integer) 47

计算结果: 52 - 5 = 47.

当你需要获取随机元素而不从集合中移除它们时,就有SRANDMEMBER命令适用于该任务。 它还具有返回重复和非重复元素的功能。

Redis Sorted sets(有序集合)

排序集合是一种类似于Set和Hash之间的混合的数据类型。 与集合类似,有序集合由唯一的非重复字符串元素组成,因此在某种意义上,有序集合也是集合。

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

而且,排序集中的元素按顺序排列(因此它们不按请求排序,排序是用于表示排序集的数据结构的特性)。 他们根据以下规则进行排序:

如果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也是可变的,所以你可以自由地指定多个分值对,这在上面的例子中没有使用。

对于有序集合,返回按出生年份排序的黑客列表是很容易的,因为实际上他们已经被排序。

实现注意事项:排序集是通过包含列表和散列表的双数据结构实现的,因此每次添加元素Redis都会执行O(log(N))操作。 当我们要求排序元素时,Redis根本不需要做任何工作,它已经全部排序:

> zrange hackers 0 -11) "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 -11) "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 withscores1) "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"

Operating on ranges

Sorted sets are more powerful than this. They can operate on ranges. Let’s get all the individuals that were born up to 1950 inclusive. We use the ZRANGEBYSCORE command to do it:
操作范围
排序集比可以在范围内操作。 让我们得到所有1950年以前出生的人。 我们使用ZRANGEBYSCORE命令来完成它:

> zrangebyscore hackers -inf 19501) "Alan Turing"2) "Hedy Lamarr"3) "Claude Shannon"4) "Alan Kay"5) "Anita Borg"

We asked Redis to return all the elements with a score between negative infinity and 1950 (both extremes are included).

It’s also possible to remove ranges of elements. Let’s remove all the hackers born between 1940 and 1960 from the sorted set:

> zremrangebyscore hackers 1940 1960(integer) 4

ZREMRANGEBYSCORE is perhaps not the best command name, but it can be very useful, and returns the number of removed elements.

Another extremely useful operation defined for sorted set elements is the get-rank operation. It is possible to ask what is the position of an element in the set of the ordered elements.

> zrank hackers "Anita Borg"(integer) 4

The ZREVRANK command is also available in order to get the rank, considering the elements sorted a descending way.

Lexicographical scores

With recent versions of Redis 2.8, a new feature was introduced that allows getting ranges lexicographically, assuming elements in a sorted set are all inserted with the same identical score (elements are compared with the C memcmp function, so it is guaranteed that there is no collation, and every Redis instance will reply with the same output).

The main commands to operate with lexicographical ranges are ZRANGEBYLEX, ZREVRANGEBYLEX, ZREMRANGEBYLEX and ZLEXCOUNT.

For example, let’s add again our list of famous hackers, but this time use a score of zero for all the elements:

> 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”
Because of the sorted sets ordering rules, they are already sorted lexicographically:

> zrange hackers 0 -11) "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"

Using ZRANGEBYLEX we can ask for lexicographical ranges:

> zrangebylex hackers [B [P1) "Claude Shannon"2) "Hedy Lamarr"3) "Linus Torvalds"

Ranges can be inclusive or exclusive (depending on the first character), also string infinite and minus infinite are specified respectively with the + and - strings. See the documentation for more information.

This feature is important because it allows us to use sorted sets as a generic index. For example, if you want to index elements by a 128-bit unsigned integer argument, all you need to do is to add elements into a sorted set with the same score (for example 0) but with an 16 byte prefix consisting of the 128 bit number in big endian. Since numbers in big endian, when ordered lexicographically (in raw bytes order) are actually ordered numerically as well, you can ask for ranges in the 128 bit space, and get the element’s value discarding the prefix.

If you want to see the feature in the context of a more serious demo, check the Redis autocomplete demo.

Updating the score: leader boards

Just a final note about sorted sets before switching to the next topic. Sorted sets’ scores can be updated at any time. Just calling ZADD against an element already included in the sorted set will update its score (and position) with O(log(N)) time complexity. As such, sorted sets are suitable when there are tons of updates.

Because of this characteristic a common use case is leader boards. The typical application is a Facebook game where you combine the ability to take users sorted by their high score, plus the get-rank operation, in order to show the top-N users, and the user rank in the leader board (e.g., “you are the #4932 best score here”).

Bitmaps

Bitmaps are not an actual data type, but a set of bit-oriented operations defined on the String type. Since strings are binary safe blobs and their maximum length is 512 MB, they are suitable to set up to 232 different bits.

Bit operations are divided into two groups: constant-time single bit operations, like setting a bit to 1 or 0, or getting its value, and operations on groups of bits, for example counting the number of set bits in a given range of bits (e.g., population counting).

One of the biggest advantages of bitmaps is that they often provide extreme space savings when storing information. For example in a system where different users are represented by incremental user IDs, it is possible to remember a single bit information (for example, knowing whether a user wants to receive a newsletter) of 4 billion of users using just 512 MB of memory.

Bits are set and retrieved using the SETBIT and GETBIT commands:

> setbit key 10 1(integer) 1> getbit key 10(integer) 1> getbit key 11(integer) 0

The SETBIT command takes as its first argument the bit number, and as its second argument the value to set the bit to, which is 1 or 0. The command automatically enlarges the string if the addressed bit is outside the current string length.

GETBIT just returns the value of the bit at the specified index. Out of range bits (addressing a bit that is outside the length of the string stored into the target key) are always considered to be zero.

There are three commands operating on group of bits:

BITOP performs bit-wise operations between different strings. The provided operations are AND, OR, XOR and NOT.

BITCOUNT performs population counting, reporting the number of bits set to 1.
BITPOS finds the first bit having the specified value of 0 or 1.
Both BITPOS and BITCOUNT are able to operate with byte ranges of the string, instead of running for the whole length of the string. The following is a trivial example of BITCOUNT call:

> setbit key 0 1(integer) 0> setbit key 100 1(integer) 0> bitcount key(integer) 2

Common use cases for bitmaps are:

Real time analytics of all kinds.

Storing space efficient but high performance boolean information associated with object IDs.
For example imagine you want to know the longest streak of daily visits of your web site users. You start counting days starting from zero, that is the day you made your web site public, and set a bit with SETBIT every time the user visits the web site. As a bit index you simply take the current unix time, subtract the initial offset, and divide by 3600*24.

This way for each user you have a small string containing the visit information for each day. With BITCOUNT it is possible to easily get the number of days a given user visited the web site, while with a few BITPOS calls, or simply fetching and analyzing the bitmap client-side, it is possible to easily compute the longest streak.

Bitmaps are trivial to split into multiple keys, for example for the sake of sharding the data set and because in general it is better to avoid working with huge keys. To split a bitmap across different keys instead of setting all the bits into a key, a trivial strategy is just to store M bits per key and obtain the key name with bit-number/M and the Nth bit to address inside the key with bit-number MOD M.

HyperLogLogs

A HyperLogLog is a probabilistic data structure used in order to count unique things (technically this is referred to estimating the cardinality of a set). Usually counting unique items requires using an amount of memory proportional to the number of items you want to count, because you need to remember the elements you have already seen in the past in order to avoid counting them multiple times. However there is a set of algorithms that trade memory for precision: you end with an estimated measure with a standard error, which in the case of the Redis implementation is less than 1%. The magic of this algorithm is that you no longer need to use an amount of memory proportional to the number of items counted, and instead can use a constant amount of memory! 12k bytes in the worst case, or a lot less if your HyperLogLog (We’ll just call them HLL from now) has seen very few elements.

HLLs in Redis, while technically a different data structure, are encoded as a Redis string, so you can call GET to serialize a HLL, and SET to deserialize it back to the server.

Conceptually the HLL API is like using Sets to do the same task. You would SADD every observed element into a set, and would use SCARD to check the number of elements inside the set, which are unique since SADD will not re-add an existing element.

While you don’t really add items into an HLL, because the data structure only contains a state that does not include actual elements, the API is the same:

Every time you see a new element, you add it to the count with PFADD.

Every time you want to retrieve the current approximation of the unique elements added with PFADD so far, you use the PFCOUNT.

> pfadd hll a b c d(integer) 1> pfcount hll(integer) 4

An example of use case for this data structure is counting unique queries performed by users in a search form every day.

Redis is also able to perform the union of HLLs, please check the full documentation for more information.

其他重要的功能

There are other important things in the Redis API that can’t be explored in the context of this document, but are worth your attention:

It is possible to iterate the key space of a large collection incrementally.

It is possible to run Lua scripts server side to improve latency and bandwidth.
Redis is also a Pub-Sub server.
Learn more
This tutorial is in no way complete and has covered just the basics of the API. Read the command reference to discover a lot more.

Thanks for reading, and have fun hacking with Redis!

Book&Resources

转载地址:http://uhqab.baihongyu.com/

你可能感兴趣的文章
[转]stl 通用排序算法解析
查看>>
分布式存储系统GlusterFS初体验
查看>>
GlusterFS常用命令小结
查看>>
GlusterFS分布式文件系统使用简介
查看>>
Use Docker Engine plugins
查看>>
Using Gluster for a Distributed Docker Storage Volume
查看>>
有容云老司机带路, 使用Docker实现丝般顺滑的持续集成
查看>>
如何让Ubuntu系统支持WebP图片格式
查看>>
变态的静态资源缓存与更新(超详细好文)
查看>>
关于lvs均衡负载socket服务的配置实现
查看>>
Qt学习旅程(1)
查看>>
[转]CentOS 5.4挂载可读写NTFS
查看>>
SmartChineseAnalyzer的对中文开源社区是一大贡献
查看>>
[转]Apache Mahout 简介
查看>>
[转]分布式key-value存储方案介绍:Cassandra,LightCloud,TokyoCabinet
查看>>
[转]HDFS+MapReduce+Hive+HBase十分钟快速入门
查看>>
stdlib中的xmalloc,xfree,xinit_mempool
查看>>
关于Java Advanced Imaging(JAI)的一点积累
查看>>
Spirit越狱iPhone/iPod touch/iPad 3.1.3/3.2固件(Windows)
查看>>
[转]Adobe发布Puppet Recipes for Hadoop
查看>>