Redis(二)

什么是Redis:Redis 是一个开源(BSD许可)的,内存中的数据结构存储系统,它可以用作数据库、缓存和消息中间件。 它支持多种类型的数据结构,如 字符串(strings) 散列(hashes) 列表(lists) 集合(sets) 有序集合(sorted sets) 与范围查询, bitmaps hyperloglogs 地理空间(geospatial) 索引半径查询。 Redis 内置了 复制(replication)LUA脚本(Lua scripting)LRU驱动事件(LRU eviction)事务(transactions) 和不同级别的磁盘持久化(persistence), 并通过Redis哨兵(Sentinel)和自动分区(Cluster)提供高可用性(high availability);

你也许已经知道Redis并不是简单的key-value存储,实际上他是一个数据结构服务器,支持不同类型的值。也就是说,你不必仅仅把字符串当作键所指向的值。下列这些数据类型都可作为值类型:

  • 二进制安全的字符串
  • Lists: 按插入顺序排序的字符串元素的集合。他们基本上就是链表(linked lists)
  • Sets: 不重复且无序的字符串元素的集合。
  • Sorted sets,类似Sets,但是每个字符串元素都关联到一个叫score浮动数值(floating number value)。里面的元素总是通过score进行着排序,所以不同的是,它是可以检索的一系列元素。(例如你可能会问:给我前面10个或者后面10个元素)。
  • Hashes,由field和关联的value组成的map。field和value都是字符串的。这和Ruby、Python的hashes很像。
  • Bit arrays (或者说 simply bitmaps): 通过特殊的命令,你可以将 String 值当作一系列 bits 处理:可以设置和清除单独的 bits,数出所有设为 1 的 bits 的数量,找到最前的被设为 1 或 0 的 bit,等等。
  • HyperLogLogs: 这是被用于估计一个 set 中元素数量的概率性的数据结构。别害怕,它比看起来的样子要简单…

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 Strings

这是最简单Redis类型。如果你只用这种类型,Redis就像一个可以持久化的memcached服务器(注:memcache的数据仅保存在内存中,服务器重启后,数据将丢失)。

我们用redis-cli来玩一下字符串类型:

> set mykey somevalue
OK
> get mykey
"somevalue"

正如你所见到的,通常用SET command 和 GET command来设置和获取字符串值。

值可以是任何种类的字符串(包括二进制数据),例如你可以在一个键下保存一副jpeg图片。值的长度不能超过512 MB。

SET 命令有些有趣的操作,例如,当key存在时SET会失败,或相反的,当key不存在时它只会成功。

> set mykey newval nx
(nil)
> set mykey newval xx
OK
虽然字符串是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对应的值,使用MSETMGET命令:

> 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

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
(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。

基本上,我们可以用三条规则来概括它的行为:

  1. 当我们向一个聚合数据类型中添加元素时,如果目标键不存在,就在添加元素前创建空的聚合数据类型。
  2. 当我们从聚合数据类型中移除元素时,如果值仍然是空的,键自动被销毁。
  3. 对一个空的 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 取回单个域。HMGETHGET 类似,但返回一系列值:

> 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 被用特殊方式编码,非常节约内存。

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 ...

不光可以取交集,还可以取并集,差集,获取随机元素,等等。

获取一个元素的命令是 SPOP,它很适合对特定问题建模。比如,要实现一个基于 web 的扑克游戏,你可能需要用 set 来表示一副牌。假设我们用一个字符的前缀来表示不同花色:

>  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 键中的内容,并放入 game:1:deck 键中。

这是通过 SUNIONSTORE 实现的,它通常用于对多个集合取并集,并把结果存入另一个 set 中。但是,因为一个 set 的并集就是它本身,我可以这样复制我的牌:

> sunionstore game:1:deck deck
(integer) 52
现在,我已经准备好给 1 号玩家发五张牌:
> 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"
现在是引入set命令的好时机,它提供了一个集合内的元素的数量, 这在集合理论的上下文中经常被称为集合的基数,所以Redis命令被称为SCARD。

> scard game:1:deck
(integer) 47
当您只需要获取随机元素而不将它们从集合中删除时,就会有适合该任务的SRANDMEMBER命令。它还具有返回重复和非重复元素的能力。

Redis Sorted sets

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

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

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

如果A和B两个元素,他们具有不同的分数(score),如果A的分数大于B的分数,那么A > B。

如果A和B两个元素分数相同,那么如果字符串A比字符串B大,那么A>B,A和B字符串不能相等,因为排序的集合只有唯一的元素。

以一个简单的例子开始,添加一些被选中的黑客名称作为排序的集合元素,他们的出生年份为“score”。

> 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 -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命令的情况下一样)。
如果我想以相反的方式订购它们,最小到最老? 使用ZREVRANGE而不是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命令也可用于获得排名,考虑到以降序排列的元素。
























  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值