翻译: http://redis.io/topics/data-types-intro
作者:
Salvatore
15分钟了解redis中的数据类型
在开始之前,你可能已经了解到了:redis并不只是提供一个单一的键-值的存储格式的数据,实际上他是一个结构数据存储服务,可以支持各种各样的值。你可以设置存储不仅仅是字符串格式的键值对的数据。具体的数据值的支持格式如下:
* 字符串
* 列表
* 集合 集合存储一系列的没有排序的唯一的字符串。你可以把他想想成一个所有的健都被设置成true的Ruby hash表
* 排序集合 和集合类似,每一个元素都被关联一个浮动的数字。然后根据浮动的数字排序。你可以想象这个好比多个Ruby hash表。健是元素,值是浮动数字。只是这种元素通常使用都不需要排序操作。
掌握这些数据类型的工作原理,还有采用命令解决问题的时候该使用哪种数据类型并不是简单的事情。所以这篇文章就是对于redis数据类型以及使用情况的一个速成文档。
说明下,下面说有的例子我们都使用的是redis-cli命令行工具,对于提交命令和redis服务器交互来说,他是一个简单的容易上手的命令行工具。
Redis keys
redis的健都是安全的二进制数。这意味着你能使用任何的二进制序列作为一个健。从一个字符串像foo,到一个JPEG文件。同样空字符串也可以作用一个可用的健。关于健的一些其他规范:
*
不建议使用太长的健。举个例子,设置一个1024字节的健通常不是一个好注意,不仅仅是内存使用不明智,而且也因为在数据集合中的查找健会话费大量的健与健之间比较的开销。
*
不建议使用太短的健。如果使用“user:1000:password”代替“u:1000:pwd”,这些字母可读性更高,并且增加了的空间是key对象本身还有值的空间。然而不可否认的是,短的键花费的内存回更少。
*
使用
同
一个模式。例如,“object-type:id:field” 就是一个很好的主意,像“user:1000:password”。对于多字段,我喜欢用点隔开,如“comment:1234:reply。to”
字符串类型
它是redis中最简单的数据类型。如果你只使用这一种数据结构,那么redis在功能上就和memcached就相同了。
下面举个例子:
$ redis-cli set mykey "my binary safe value"
OK
$ redis-cli get mykey
my binary safe value
如你看到的。你可以使用SET命令喝GET命令方便的给一个字符串设置值和返回这个值。
值可以是各种各样的字符串。例如,你可以在使用一个key来存储一个jpeg图片。一个值的大小不能超过512M。
尽管字符串是redis中值的基本类型,对于他们还是有很多有用的操作。例如,一个自增长的例子:
$ redis-cli set counter 100
OK
$ redis-cli incr counter
(integer) 101
$ redis-cli incr counter
(integer) 102
$ redis-cli incrby counter 10
(integer) 112
INCR命令把字符串的值看成一个整数,从1开始自增长,最后设置新的值给该字符串。DECR和DECRBY命令2个命令和INCR命令很像,内部来看,他们是相同的命令,用途上有些不同。
INCR是增量的是什么意思呢?即使有多个客户端INCR命令对同样个键来进行操作,他们将不回引入竞争。例如,客户端1读“10”,客户端2在同一时间也读“10”,他们都自增到了11,并设置新的值为11.最后的值将是12.所有的其他客户端都在不同一时间执行命令的时候,读--自增--赋值的操作才会被执行。
另一个对字符串操作的命令是GETSET。它做的操作是:给一个键设置一个新值并返回旧的值。它有什么用?举例:你有一个系统,当任何时候你的web站点接受了一个新的访问请求,立即使用INCR命令自增长一个Redis中的健值。而你想每一小时一次的采集数据信息,而不想删除那个键。你可以使用GETSET命令,设置它的值为0并且返回原来的旧的值。
列表类型
为了解释列表的数据类型,有一点理论要说明。术语list经常被一些人的误用。举个例子,python lists 并不建议这个名称(链表),它不是
实际
意义上的list,它实际上是一个数组,同样的在ruby中
叫
作数组。
从一个普遍的观点上说,list是一个有序的元素:10,20.30,1.2.3是一个list。但是一个数组来实现一个list的性能和使用链表来实现一个list的性能是非常不同的。
redis中list是由链表来实现的。这就意味着即使你有成千上万中元素在list中,添加一个新元素到list开头或者结尾的操作都是在一个的时间内进行的。使用命令LPUSH添加一个元素到一个10元素的lsit中的速度和添加一个元素到一个1亿元素的list速度是相同的。
redis的list的缺点是什么?通过索引访问一个数组中list是非常快的。但是在链表实现的list中就不那么快了。
redis Lists 采用链表来实现的。因为对于一个数据库系统而言,能快速的添加元素到一个非常长的list中是起决定性的作用的。另一个优势是,如你即将看到的,redis lists能保持恒定的长度在一个恒定的时间内。
redis lists 第一步学习
LPUSH命令用来添加一个新的元素到列表,位于列表的左边(列表的开头)。然而RPUSH用来添加一个新的元素到列表中,位于列表的右边(列表的结尾)。最后LRANGE返回列表中的元素个数,既列表的长度。
$ redis-cli rpush messages "Hello how are you?"
OK
$ redis-cli rpush messages "Fine thanks. I'm having fun with Redis"
OK
$ redis-cli rpush messages "I should look into this NOSQL thing ASAP"
OK
$ redis-cli lrange messages 0 2
1. Hello how are you?
2. Fine thanks. I'm having fun with Redis
3. I should look into this NOSQL thing ASAP
注意,lrange需要2个索引,分别是需要返回的第一个元素和最后一个元素的位置。所有的索引可以告诉redis,从反向开始计数,所以-1是最后一个元素,-2是倒数第二个元素,等等。
从上面的例子可以猜到,列表可以用来实现一个聊天系统。另一中使用是作为队列在2个进程间传递消息。但是关键是,你可以使用redis以相同的次序列出每一次你需要访问的他们添加的数据。这将不需要任何SQL ORDER BY操作,将非常快,并可以拓展到上百万个元素即使使用一个小型的linux系统。
举例,一个排名系统像reddit.com社会新闻网站,你可以添加没一个提交的连接到一个list中并且使用LRANGE将是分页结果变得简单。
一个博客引擎实现中,你可以对于blog评论的每一个psot采用list。等等。
Pushing IDs instead of the actual data in Redis lists
在下面的例子中,我设置我们的对象(在例子中是一个简单的消息)到redis的list中。但是通常是行不同的,因为对象可能涉及到多个属性
可能包含了
:一个列表是按时间的排列的一个序列,一个集合用来存储一个特定类别,另一个列表只有在对象满足某些特定需求的时候猜使用,等等。
让我返回到reddit.com的例子。最好的提交信息的例子如下
$ redis-cli incr next.news.id
(integer) 1
$ redis-cli set news:1:title "Redis is simple"
OK
$ redis-cli set news:1:url "http://code.google.com/p/redis"
OK
$ redis-cli lpush submitted.news 1
OK
我们给我们的新闻对象设置了一个自增长的ID,然后使用这个ID,来创建了包含每一个属性的对象字符串键。最后这个对象的ID被存储到submitted。news列表中。
这只是一个开始,阅读http://redis.io/commands页面获取相关的命令使用方法。有remove elements,rotate lists,get 或者set elements,或者获取list长度的操作。
redis中的集合
redis 的sets 是一个没有序列的字符串的集合体。使用SADD命令添加一个元素到集合中。它也是一种很重要的数据类型。对集合可以做大量的操作包括测试一个元素是不是已经存在,求交集,并集或者多个集合之间的不同等等操作。说这么多,下面的例子值千言万语。
$ redis-cli sadd myset 1
(integer) 1
$ redis-cli sadd myset 2
(integer) 1
$ redis-cli sadd myset 3
(integer) 1
$ redis-cli smembers myset
1. 3
2. 1
3. 2
上面的操作中,我已经添加了3个元素到我的集合中并且告诉redis返回我所有集合中的元素。正如你看到的,他们没有被排序。现在让我来检测一个元素是否存在:
$ redis-cli sismember myset 3
(integer) 1
$ redis-cli sismember myset 30
(integer) 0
“3”是集合中的一个元素,而“30”不是。集合非常擅长描述对象之间的关系。我们可以使用redis的集合来实现标签。
一个简单的模型,给每一个包含了一个标签ID的对象设置一个集合,和每一个包含了对象ID的标签集合。
例如,如果你的news ID 为1000被设置标签1,2,4,77.我们能定义5个集合,一个集合包含了对象的标签,4个标签集合。
$ redis-cli sadd news:1000:tags 1
(integer) 1
$ redis-cli sadd news:1000:tags 2
(integer) 1
$ redis-cli sadd news:1000:tags 5
(integer) 1
$ redis-cli sadd news:1000:tags 77
(integer) 1
$ redis-cli sadd tag:1:objects 1000
(integer) 1
$ redis-cli sadd tag:2:objects 1000
(integer) 1
$ redis-cli sadd tag:5:objects 1000
(integer) 1
$ redis-cli sadd tag:77:objects 1000
(integer) 1
获取对象的所有标签就很简单了:
$ redis-cli smembers news:1000:tags
1. 5
2. 1
3. 77
4. 2
但是同样存在其他看起来不那么简单的但是通过命令可以简单实现的操作。
例如,我们想显示所有对象的1,2,10,27的标签。我们能使用SINTER命令,求2个集合的交集。为了达到我们的目的,我们可以使用:
$ redis-cli sinter tag:1:objects tag:2:objects tag:10:objects tag:27:objects ... no result in our dataset composed of just one object ;) ...
查看http://redis.io/commands获取更多的集合相关的命令,其中肯定有你感兴趣的一个。另外,可以查看SORT命令,它可以对集合和列表排序。
排序集合
集合是非常的素据类型。但是对于数字来说,它有不能排序的问题。这是为什么redis1,2介绍排序集合的原因。排序集合和上面的集合很相似,都是包含了字符串。但是这一次,排序集合给元素关联了一个标识。并且这个操作和列表中的LRANGE操作并返回很相似。但是不同的是,排序集合使用的ZRANGE命令。
基本上,排序集合于sql中的索引。例如,在我们的reddit.com例子中,我们没有描述怎么采集实际的用户投票的主页还有时间。我们将看到排序集合是如何解决这个问题的。我们从简单的例子开始,说明这种拓展数据类型的基本的工作原理。先让我们添加黑客到集合中,并使用他们的年份作为标识。
$ redis-cli zadd hackers 1940 "Alan Kay"
(integer) 1
$ redis-cli zadd hackers 1953 "Richard Stallman"
(integer) 1
$ redis-cli zadd hackers 1965 "Yukihiro Matsumoto"
(integer) 1
$ redis-cli zadd hackers 1916 "Claude Shannon"
(integer) 1
$ redis-cli zadd hackers 1969 "Linus Torvalds"
(integer) 1
$ redis-cli zadd hackers 1912 "Alan Turing"
(integer) 1
对于排序集合来说,返回他们出生年份是没有必要的,因为他们已经被排序了。排序集合采用了一个双入口的数据接口包含了一个跳跃链表和一个hash表。所以每一次我们添加一个元素的复杂度是O(log(N))。我们不用要求redis做任何操作。它就已经排序完成了。
$ redis-cli zrange hackers 0 -1
1. Alan Turing
2. Claude Shannon
3. Alan Kay
4. Richard Stallman
5. Yukihiro Matsumoto
6. Linus Torvalds
如果想得到一个反向排序集合,使用ZREVRANGE代替ZRANGE。
一个非常重要的建议。ZSets已经默认排序了。但是你想调用SORT命令来获取集合的不同排序(这将花费服务器的CPU)。对于多个排序请求,一个可选方案是,在同一时间把每一个元素添加到多个排序集合中。
operating on ranges
排序集合的作用远远不止。它可以用来操作一个范围的数据。让我们所有1950后出生的黑客信息。我们使用ZRANGEBYSCORE来完成它。
$ redis-cli zrangebyscore hackers -inf 1950
1. Alan Turing
2. Claude Shannon
3. Alan Kay
同样可以删除范围内的数据。让我们删除1940到1960间出生的黑客。
$ redis-cli zremrangebyscore hackers 1940 1960
(integer) 2
ZRANGEBYSCORE也许不是一个好的命令名称,但他们非常有用,返回删除的元素个个数。
回到上面Reddit例子
最后,返回到reddit例子。现在我们已经有一个的计划:使用一个排序集合来获取主页。一个排序集合包含了所有的最近的news(我们回删除那么过时了的集合中的元素)。使用一个后台的任务来从排序集合中获取元素,获取投票信息和new时间并且计算后填充的reddit.home.page排序集合,该集合使用newID值作为排序标签。显示我们主页,我们只需要一个快速的命令ZRAGE。
我们会不时的删除一些过时的news元素从reddit.home.page排序集合中来确保我们的数据都是最新的。
更新排序集合的标签
教程结束前的最后一个注意项。排序集合中的标签可以在任何时候被更新。使用ZADD命令就可以了。它的时间复杂度为O(log(N)),所以,即使有大量的更新,排序集合都不用担心。
这个教程是不完整的。获取更多的信息请阅读http://redis.io/commands。
by salvatore
写在后面
目前的redis已经支持hash表的数据类型,更多的关于hash表的介绍,请查看这里
http://redis.io/commands#hash