Redis 基本使用
Posted 苍青浪
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Redis 基本使用相关的知识,希望对你有一定的参考价值。
基本的数据类型
- 二进制安全的字符串
- Lists: 按插入顺序排序的字符串元素的集合。他们基本上就是链表(linked lists)。
- Sets: 不重复且无序的字符串元素的集合。
- Sorted sets,类似Sets,但是每个字符串元素都关联到一个叫score浮动数值(floating number value)。里面的元素总是通过score进行着排序,所以不同的是,它是可以检索的一系列元素。(例如你可能会问:给我前面10个或者后面10个元素)。
- Hashes,由field和关联的value组成的map。field和value都是字符串的。这和Ruby、Python的hashes很像。
Redis keys
Redis key值是二进制安全的,这意味着可以用任何二进制序列作为key值,从形如”foo”的简单字符串到一个JPEG文件的内容都可以。空字符串也是有效key值。
关于key的几条规则:
- 太长的键值不是个好主意,例如1024字节的键值就不是个好主意,不仅因为消耗内存,而且在数据中查找这类键值的计算成本很高。
- 太短的键值通常也不是好主意,如果你要用”u:1000:pwd”来代替”user:1000:password”,这没有什么问题,但后者更易阅读,并且由此增加的空间消耗相对于key object和value object本身来说很小。当然,没人阻止您一定要用更短的键值节省一丁点儿空间。
- 最好坚持一种模式。例如:”object-type:id:field”就是个不错的注意,像这样”user:1000:password”。我喜欢对多单词的字段名中加上一个点,就像这样:”comment:1234:reply.to”。
Redis key值是二进制安全的,这意味着可以用任何二进制序列作为key值,从形如”foo”的简单字符串到一个JPEG文件的内容都可以。空字符串也是有效key值。
关于key的几条规则:
- 太长的键值不是个好主意,例如1024字节的键值就不是个好主意,不仅因为消耗内存,而且在数据中查找这类键值的计算成本很高。
- 太短的键值通常也不是好主意,如果你要用”u:1000:pwd”来代替”user:1000:password”,这没有什么问题,但后者更易阅读,并且由此增加的空间消耗相对于key object和value object本身来说很小。当然,没人阻止您一定要用更短的键值节省一丁点儿空间。
- 最好坚持一种模式。例如:”object-type:id:field”就是个不错的注意,像这样”user:1000:password”。我喜欢对多单词的字段名中加上一个点,就像这样:”comment:1234:reply.to”。
Redis Strings
这是最简单Redis类型。如果你只用这种类型,Redis就像一个可以持久化的memcached服务器(注:memcache的数据仅保存在内存中,服务器重启后,数据将丢失)。
> set mykey somevalue OK > get mykey "somevalue"
正如你所见到的,通常用SET command 和 GET command来设置和获取字符串值。
值可以是任何种类的字符串(包括二进制数据),例如你可以在一个键下保存一副jpeg图片。值的长度不能超过512 MB。
虽然字符串是Redis的基本值类型,但你仍然能通过它完成一些有趣的操作。例如:原子递增:
> 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和客户端2同时读出“10”,他们俩都对其加到11,然后将新值设置为11』。最终的值一定是12,read-increment-set操作完成时,其他客户端不会在同一时间执行任何命令。
对字符串,另一个的令人感兴趣的操作是GETSET命令,行如其名:他为key设置新值并且返回原值。这有什么用处呢?例如:你的系统每当有新用户访问时就用INCR命令操作一个Redis key。你希望每小时对这个信息收集一次。你就可以GETSET这个key并给其赋值0并读取原值。
为减少等待时间,也可以一次存储或获取多个key对应的值,使用MSET和MGET命令:
> mset a 10 b 20 c 30 OK > mget a b c 1) "10" 2) "20" 3) "30"
MGET 命令返回由值组成的数组。
修改或查询键空间
有些指令不是针对任何具体的类型定义的,而是用于和整个键空间交互的。因此,它们可被用于任何类型的键。
使用EXISTS命令返回1或0标识给定key的值是否存在,使用DEL命令可以删除key对应的值,DEL命令返回1或0标识值是被删除(值存在)或者没被删除(key对应的值不存在)。
> set mykey hello OK > exists mykey (integer) 1 > del mykey (integer) 1 > exists mykey (integer) 0
TYPE命令可以返回key对应的值的存储类型:
> set mykey x OK > type mykey string > del mykey (integer) 1 > type mykey none
Redis超时:数据在限定时间内存活
在介绍复杂类型前我们先介绍一个与值类型无关的Redis特性:超时。你可以对key设置一个超时时间,当这个时间到达后会被删除。精度可以使用毫秒或秒。
> set key some-value OK > expire key 5 (integer) 1 > get key (immediately) "some-value" > get key (after some time) (nil)
上面的例子使用了EXPIRE来设置超时时间(也可以再次调用这个命令来改变超时时间,使用PERSIST命令去除超时时间 )。我们也可以在创建值的时候设置超时时间:
> set key 100 ex 10 OK > ttl key (integer) 9
TTL命令用来查看key对应的值剩余存活时间。
Redis Lists
要说清楚列表数据类型,最好先讲一点儿理论背景,在信息技术界List这个词常常被使用不当。例如”Python Lists”就名不副实(名为Linked Lists),但他们实际上是数组(同样的数据类型在Ruby中叫数组)
一般意义上讲,列表就是有序元素的序列:10,20,1,2,3就是一个列表。但用数组实现的List和用Linked List实现的List,在属性方面大不相同。
Redis lists基于Linked Lists实现。这意味着即使在一个list中有数百万个元素,在头部或尾部添加一个元素的操作,其时间复杂度也是常数级别的。用LPUSH 命令在十个元素的list头部添加新元素,和在千万元素list头部添加新元素的速度相同。
那么,坏消息是什么?在数组实现的list中利用索引访问元素的速度极快,而同样的操作在linked list实现的list上没有那么快。
Redis Lists用linked list实现的原因是:对于数据库系统来说,至关重要的特性是:能非常快的在很大的列表上添加元素。另一个重要因素是,正如你将要看到的:Redis lists能在常数时间取得常数长度。
如果快速访问集合元素很重要,建议使用可排序集合(sorted sets)。可排序集合我们会随后介绍。
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 -1 1) "first" 2) "A" 3) "B"
注意:LRANGE 带有两个索引,一定范围的第一个和最后一个元素。这两个索引都可以为负来告知Redis从尾部开始计数,因此-1表示最后一个元素,-2表示list中的倒数第二个元素,以此类推。
上面的所有命令的参数都可变,方便你一次向list存入多个值。
> 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"
还有一个重要的命令是pop,它从list中删除元素并同时返回删除的值。可以在左边或右边操作。
> rpush mylist a b c (integer) 3 > rpop mylist "c" > rpop mylist "b" > rpop mylist "a"
我们增加了三个元素,并弹出了三个元素,因此,在这最后 列表中的命令序列是空的,没有更多的元素可以被弹出。如果我们尝试弹出另一个元素,这是我们得到的结果:
> rpop mylist
(nil)
当list没有元素时,Redis 返回了一个NULL。
List的常用案例
正如你可以从上面的例子中猜到的,list可被用来实现聊天系统。还可以作为不同进程间传递消息的队列。关键是,你可以每次都以原先添加的顺序访问数据。这不需要任何SQL ORDER BY 操作,将会非常快,也会很容易扩展到百万级别元素的规模。
例如在评级系统中,比如社会化新闻网站 reddit.com,你可以把每个新提交的链接添加到一个list,用LRANGE可简单的对结果分页。
在博客引擎实现中,你可为每篇日志设置一个list,在该list中推入博客评论,等等。
Capped lists
可以使用LTRIM把list从左边截取指定长度。
> rpush mylist 1 2 3 4 5 // 5在最左边 (integer) 5 > ltrim mylist 0 2 OK > lrange mylist 0 -1 1) "1" 2) "2" 3) "3"
List上的阻塞操作
可以使用Redis来实现生产者和消费者模型,如使用LPUSH和RPOP来实现该功能。但会遇到这种情景:list是空,这时候消费者就需要轮询来获取数据,这样就会增加redis的访问压力、增加消费端的cpu时间,而很多访问都是无用的。为此redis提供了阻塞式访问 BRPOP 和 BLPOP 命令。 消费者可以在获取数据时指定如果数据不存在阻塞的时间,如果在时限内获得数据则立即返回,如果超时还没有数据则返回null, 0表示一直阻塞。
同时redis还会为所有阻塞的消费者以先后顺序排队。
如需了解详细信息请查看 RPOPLPUSH 和 BRPOPLPUSH。
key 的自动创建和删除
目前为止,在我们的例子中,我们没有在推入元素之前创建空的 list,或者在 list 没有元素时删除它。在 list 为空时删除 key,并在用户试图添加元素(比如通过 LPUSH
)而键不存在时创建空 list,是 Redis 的职责。
这不光适用于 lists,还适用于所有包括多个元素的 Redis 数据类型 – Sets, Sorted Sets 和 Hashes。
基本上,我们可以用三条规则来概括它的行为:
- 当我们向一个聚合数据类型中添加元素时,如果目标键不存在,就在添加元素前创建空的聚合数据类型。
- 当我们从聚合数据类型中移除元素时,如果值仍然是空的,键自动被销毁。
- 对一个空的 key 调用一个只读的命令,比如
LLEN
(返回 list 的长度),或者一个删除元素的命令,将总是产生同样的结果。该结果和对一个空的聚合类型做同个操作的结果是一样的。
规则 1 示例:
> del mylist (integer) 1 > lpush mylist 1 2 3 (integer) 3
但是,我们不能对存在但类型错误的 key 做操作: > set foo bar OK > lpush foo 1 2 3 (error) WRONGTYPE Operation against a key holding the wrong kind of value > type foo string
规则 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
Redis hash 看起来就像一个 “hash” 的样子,由键值对组成:
> 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 便于表示 objects,实际上,你可以放入一个 hash 的域数量实际上没有限制(除了可用内存以外)。所以,你可以在你的应用中以不同的方式使用 hash。
HMSET
指令设置 hash 中的多个域,而 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
你可以在文档中找到 hash 指令的完整列表。
值得注意的是,小的 hash 被用特殊方式编码,非常节约内存。
Redis Sets
Redis Set 是 String 的无序排列。SADD
指令把新的元素添加到 set 中。对 set 也可做一些其他的操作,比如测试一个给定的元素是否存在,对不同 set 取交集,并集或差,等等。
> sadd myset 1 2 3 (integer) 3 > smembers myset 1. 3 2. 1 3. 2
现在我已经把三个元素加到我的 set 中,并告诉 Redis 返回所有的元素。可以看到,它们没有被排序 —— Redis 在每次调用时可能按照任意顺序返回元素,因为对于元素的顺序并没有规定。
Redis 有检测成员的指令。一个特定的元素是否存在?
> sismember myset 3 (integer) 1 > sismember myset 30 (integer) 0
“3” 是 set 的一个成员,而 “30” 不是。
Sets 适合用于表示对象间的关系。 例如,我们可以轻易使用 set 来表示标记。
一个简单的建模方式是,对每一个希望标记的对象使用 set。这个 set 包含和对象相关联的标签的 ID。
假设我们想要给新闻打上标签。 假设新闻 ID 1000 被打上了 1,2,5 和 77 四个标签,我们可以使用一个 set 把 tag 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
获取一个对象的所有 tag 是很方便的:
> smembers news:1000:tags 1. 5 2. 1 3. 77 4. 2
注意:在这个例子中,我们假设你有另一个数据结构,比如一个 Redis hash,把标签 ID 对应到标签名称。
使用 Redis 命令行,我们可以轻易实现其它一些有用的操作。比如,我们可能需要一个含有 1, 2, 10, 和 27 标签的对象的列表。我们可以用 SINTER
命令来完成这件事。它获取不同 set 的交集。我们可以用:
> sinter tag:1:news tag:2:news tag:10:news tag:27:news ... results here ...
不光可以取交集,还可以取并集,差集,获取随机元素,等等。
以上是关于Redis 基本使用的主要内容,如果未能解决你的问题,请参考以下文章