Redis 3.0官方文档翻译计划(3) ——从入门到精通(中)

1、Redis列表是怎么使用的?
2、Redis的哈希/散列怎么查找列表?
3、Redis集合怎么使用的?

Redis列表(Lists) 
    为了解释列表类型,最好先开始来点理论,因为列表这个术语在信息技术领域常常使用不当。例如,”Python Lists”,并不是字面意思(链表),实际是表示数组 (和Ruby中的Array是同一种类型)。 
    通常列表表示有序元素的序列:10,20,1,2,3是一个列表。但是数组实现的列表和链表实现的列表,他们的属性非常不同。 
    Redis的列表是使用链表实现的。这意味着,及时你的列表中有上百万个元素,增加一个元素到列表的头部或者尾部的操作都是在常量时间完成。使用LPUSH命令增加一个新元素到拥有10个元素的列表的头部的速度,与增加到拥有1000万个元素的列表的头部是一样的。 
缺点又是什么呢?使用索引下标来访问一个数组实现的列表非常快(常量时间),但是访问链表实现列表就没那么快了(与元素索引下标成正比的大量工作)。 
    Redis采用链表来实现列表是因为,对于数据库系统来说,快速插入一个元素到一个很长的列表非常重要。另外一个即将描述的优势是,Redis列表能在常数时间内获得常数长度。 
    如果需要快速访问一个拥有大量元素的集合的中间数据,可以用另一个称为有序集合的数据结构。稍后将会介绍有序集合。 

   Redis列表起步 
    LPUSH命令从左边(头部)添加一个元素到列表,RPUSH命令从右边(尾部)添加一个元素的列表。LRANGE命令从列表中提取一个范围内的元素。 

  • > 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命令使用两个索引下标,分别是返回的范围的开始和结束元素。两个索引坐标可以是负数,表示从后往前数,所以-1表示最后一个元素,-2表示倒数第二个元素,等等。 
    如你所见,RPUSH添加元素到列表的右边,LPUSH添加元素到列表的左边。 
    两个命令都是可变参数命令,也就是说,你可以在一个命令调用中自由的添加多个元素到列表中: 

  • > 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"

复制代码
定义在Redis列表上的一个重要操作是弹出元素。弹出元素指的是从列表中检索元素,并同时将其从列表中清楚的操作。你可以从左边或者右边弹出元素,类似于你可以从列表的两端添加元素。 
  • > rpush mylist a b c
  • (integer) 3
  • > rpop mylist
  • "c"
  • > rpop mylist
  • "b"
  • > rpop mylist
  • "a"

复制代码
我们添加了三个元素并且又弹出了三个元素,所以这一串命令执行完以后列表是空的,没有元素可以弹出了。如果我们试图再弹出一个元素,就会得到如下结果: 
  • > rpop mylist
  • (nil)

复制代码
Redis返回一个NULL值来表明列表中没有元素了。 

    列表的通用场景(Common use cases) 
    列表可以完成很多任务,两个有代表性的场景如下: 
记住社交网络中用户最近提交的更新。
使用生产者消费者模式来进程间通信,生产者添加项(item)到列表,消费者(通常是worker)消费项并执行任务。Redis有专门的列表命令更加可靠和高效的解决这种问题。

    例如,两种流行的Ruby库resque和sidekiq,都是使用Redis列表作为钩子,来实现后台作业(background jobs)。 
    流行的Twitter社交网络,使用Redis列表来存储用户最新的微博(tweets)。 
为了一步一步的描述通用场景,假设你想加速展现照片共享社交网络主页的最近发布的图片列表。 
    每次用户提交一张新的照片,我们使用LPUSH将其ID添加到列表。 
    当用户访问主页时,我们使用LRANGE 0 9获取最新的10张照片。 

    上限列表 (Capped) 
    很多时候我们只是想用列表存储最近的项,随便这些项是什么:社交网络更新,日志或者任何其他东西。 
    Redis允许使用列表作为一个上限集合,使用LTRIM命令仅仅只记住最新的N项,丢弃掉所有老的项。 
    LTRIM命令类似于LRANGE,但是不同于展示指定范围的元素,而是将其作为列表新值存储。所有范围外的元素都将被删除。 
    举个例子你就更清楚了: 

  • > rpush mylist 1 2 3 4 5
  • (integer) 5
  • > ltrim mylist 0 2
  • OK
  • > lrange mylist 0 -1
  • 1) "1"
  • 2) "2"
  • 3) "3"

复制代码
上面LTRIM命令告诉Redis仅仅保存第0到2个元素,其他的都被抛弃。这可以让你实现一个简单而又有用的模式,一个添加操作和一个修剪操作一起,实现新增一个元素抛弃超出元素。
  • LPUSH mylist <some element>
  • LTRIM mylist 0 999

复制代码
上面的组合增加一个元素到列表中,同时只持有最新的1000个元素。使用LRANGE命令你可以访问前几个元素而不用记录非常老的数据。 
    注意:尽管LRANGE是一个O(N)时间复杂度的命令,访问列表头尾附近的小范围是常量时间的操作。 

   列表的阻塞操作(blocking) 
    列表有一个特别的特性使得其适合实现队列,通常作为进程间通信系统的积木:阻塞操作。 
    假设你想往一个进程的列表中添加项,用另一个进程来处理这些项。这就是通常的生产者消费者模式,可以使用以下简单方式实现: 
生产者调用LPUSH添加项到列表中。
消费者调用RPOP从列表提取/处理项。

    然而有时候列表是空的,没有需要处理的,RPOP就返回NULL。所以消费者被强制等待一段时间并重试RPOP命令。这称为轮询 (polling),由于其具有一些缺点,所以不合适在这种情况下: 
    1.强制Redis和客户端处理无用的命令(当列表为空时的所有请求都没有执行实际的工作,只会返回NULL)。 
    2.由于工作者受到一个NULL后会等待一段时间,这会延迟对项的处理。 
    于是Redis实现了BRPOP和BLPOP两个命令,它们是当列表为空时RPOP和LPOP的会阻塞版本:仅当一个新元素被添加到列表时,或者到达了用户的指定超时时间,才返回给调用者。 
这个是我们在工作者中调用BRPOP的例子: 

  • > brpop tasks 5
  • 1) "tasks"
  • 2) "do_something"

复制代码
上面的意思是”等待tasks列表中的元素,如果5秒后还没有可用元素就返回”。 
    注意,你可以使用0作为超时让其一直等待元素,你也可以指定多个列表而不仅仅只是一个,同时等待多个列表,当第一个列表收到元素后就能得到通知。 
    关于BRPOP的一些注意事项。 
    1.客户端按顺序服务:第一个被阻塞等待列表的客户端,将第一个收到其他客户端添加的元素,等等。 
    2.与RPOP的返回值不同:返回的是一个数组,其中包括键的名字,因为BRPOP和BLPOP可以阻塞等待多个列表的元素。 
    3.如果超时时间到达,返回NULL。 
    还有更多你需要知道的关于列表和阻塞选项,建议你阅读下面的页面: 
使用RPOLPUSH构建更安全的队列和旋转队列。
BRPOPLPUSH命令是其阻塞变种命令。


    自动创建和删除键 
    到目前为止的例子中,我们还没有在添加元素前创建一个空的列表,也没有删除一个没有元素的空列表。要注意,当列表为空时Redis将删除该键,当向一个不存在的列表键(如使用LPUSH)添加一个元素时,将创建一个空的列表。 
    这并不只是针对列表,适用于所有Redis多元素组成的数据类型,因此适用于集合,有序集合和哈希。 
    基本上我们可以概括为三条规则: 
    1.当我们向聚合(aggregate)数据类型添加一个元素,如果目标键不存在,添加元素前将创建一个空的聚合数据类型。 
    2.当我们从聚合数据类型删除一个元素,如果值为空,则键也会被销毁。 
    3.调用一个像LLEN的只读命令(返回列表的长度),或者一个写命令从空键删除元素,总是产生和操作一个持有空聚合类型值的键一样的结果。 
    规则1的例子: 

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

复制代码
然而,我们不能执行一个错误键类型的操作:
  • > 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

复制代码
当所有元素弹出后,键就不存在了。 
    规则3的例子:

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

复制代码
Redis哈希/散列 (Hashes) 
    Redis哈希看起来正如你期待的那样: 

  • > 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"

复制代码
哈希就是字段值对(fields-values pairs)的集合。由于哈希容易表示对象,事实上哈希中的字段的数量并没有限制,所以你可以在你的应用程序以不同的方式来使用哈希。 
    HMSET命令为哈希设置多个字段,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

复制代码
你可以从命令页找到全部哈希命令列表。 
    值得注意的是,小的哈希(少量元素,不太大的值)在内存中以一种特殊的方式编码以高效利用内存。 

   Redis集合(Sets) 
    Redis集合是无序的字符串集合(collections)。SADD命令添加元素到集合。还可以对集合执行很多其他的操作,例如,测试元素是否存在,对多个集合执行交集、并集和差集,等等。

  • > sadd myset 1 2 3
  • (integer) 3
  • > smembers myset
  • 1. 3
  • 2. 1
  • 3. 2

复制代码
我们向集合总添加了3个元素,然后告诉Redis返回所有元素。如你所见,他们没有排序,Redis在每次调用时按随意顺序返回元素,因为没有与用户有任何元素排序协议。 
    我们有测试成员关系的命令。一个指定的元素存在吗? 

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

复制代码
“3”是集合中的成员,”30”则不是。 
    集合适用于表达对象间关系。例如,我们可以很容易的实现标签。对这个问题的最简单建模,就是有一个为每个需要标记的对象的集合。集合中保存着与对象相关的标记的ID。 
  假设,我们想标记新闻。如果我们的ID为1000的新闻,被标签1,2,5和77标记,我们可以有一个这篇新闻被关联标记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:tags
  • 1. 5
  • 2. 1
  • 3. 77
  • 4. 2

复制代码
注意:在这个例子中,我们假设你有另外一个数据结构,例如,一个Redis哈希,存储标签ID到标签名的映射。 
    还有一些使用正确的Redis命令就很容实现的操作。例如,我们想获取所有被标签1,2,10和27同时标记的对象列表。我们可以使用SINTER命令实现这个,也就是对不同的集合执行交集。我们只需要:

  • > sinter tag:1:news tag:2:news tag:10:news tag:27:news
  • ... results here ...

复制代码
并不仅仅是交集操作,你也可以执行并集,差集,随机抽取元素操作等等。 
    抽取一个元素的命令是SPOP,就方便为很多问题建模。例如,为了实现一个基于web的扑克游戏,你可以将你的一副牌表示为集合。假设我们使用一个字符前缀表示(C)lubs梅花, (D)iamonds方块,(H)earts红心,(S)pades黑桃。 

  • >  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命令完成的,这个命令通常对多个集合执行交集,然后把结果存储在另一个集合中。而对单个集合求交集就是其自身,于是我可以这样拷贝我的这副牌: 

  • > sunionstore game:1:deck deck
  • (integer) 52

复制代码
现在我们准备好为第一个选手提供5张牌: 
  • > 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"

复制代码
只有一对jack,不太理想…… 
    现在是时候介绍提供集合中元素数量的命令。这个在集合理论中称为集合的基数(cardinality,也称集合的势),所以相应的Redis命令称为SCARD。 

  • > scard game:1:deck
  • (integer) 47

复制代码
数学计算式为:52 - 5 = 47。 
    当你只需要获得随机元素而不需要从集合中删除,SRANDMEMBER命令则适合你完成任务。它具有返回重复的和非重复的元素的能力。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值