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命令从列表中提取一个范围内的元素。
复制代码 注意LRANGE命令使用两个索引下标,分别是返回的范围的开始和结束元素。两个索引坐标可以是负数,表示从后往前数,所以-1表示最后一个元素,-2表示倒数第二个元素,等等。 如你所见,RPUSH添加元素到列表的右边,LPUSH添加元素到列表的左边。 两个命令都是可变参数命令,也就是说,你可以在一个命令调用中自由的添加多个元素到列表中:
复制代码 定义在Redis列表上的一个重要操作是弹出元素。弹出元素指的是从列表中检索元素,并同时将其从列表中清楚的操作。你可以从左边或者右边弹出元素,类似于你可以从列表的两端添加元素。
复制代码 我们添加了三个元素并且又弹出了三个元素,所以这一串命令执行完以后列表是空的,没有元素可以弹出了。如果我们试图再弹出一个元素,就会得到如下结果:
复制代码 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,但是不同于展示指定范围的元素,而是将其作为列表新值存储。所有范围外的元素都将被删除。 举个例子你就更清楚了:
复制代码 上面LTRIM命令告诉Redis仅仅保存第0到2个元素,其他的都被抛弃。这可以让你实现一个简单而又有用的模式,一个添加操作和一个修剪操作一起,实现新增一个元素抛弃超出元素。
复制代码 上面的组合增加一个元素到列表中,同时只持有最新的1000个元素。使用LRANGE命令你可以访问前几个元素而不用记录非常老的数据。 注意:尽管LRANGE是一个O(N)时间复杂度的命令,访问列表头尾附近的小范围是常量时间的操作。 列表的阻塞操作(blocking) 列表有一个特别的特性使得其适合实现队列,通常作为进程间通信系统的积木:阻塞操作。 假设你想往一个进程的列表中添加项,用另一个进程来处理这些项。这就是通常的生产者消费者模式,可以使用以下简单方式实现: 生产者调用LPUSH添加项到列表中。 消费者调用RPOP从列表提取/处理项。 然而有时候列表是空的,没有需要处理的,RPOP就返回NULL。所以消费者被强制等待一段时间并重试RPOP命令。这称为轮询 (polling),由于其具有一些缺点,所以不合适在这种情况下: 1.强制Redis和客户端处理无用的命令(当列表为空时的所有请求都没有执行实际的工作,只会返回NULL)。 2.由于工作者受到一个NULL后会等待一段时间,这会延迟对项的处理。 于是Redis实现了BRPOP和BLPOP两个命令,它们是当列表为空时RPOP和LPOP的会阻塞版本:仅当一个新元素被添加到列表时,或者到达了用户的指定超时时间,才返回给调用者。 这个是我们在工作者中调用BRPOP的例子:
复制代码 上面的意思是”等待tasks列表中的元素,如果5秒后还没有可用元素就返回”。 注意,你可以使用0作为超时让其一直等待元素,你也可以指定多个列表而不仅仅只是一个,同时等待多个列表,当第一个列表收到元素后就能得到通知。 关于BRPOP的一些注意事项。 1.客户端按顺序服务:第一个被阻塞等待列表的客户端,将第一个收到其他客户端添加的元素,等等。 2.与RPOP的返回值不同:返回的是一个数组,其中包括键的名字,因为BRPOP和BLPOP可以阻塞等待多个列表的元素。 3.如果超时时间到达,返回NULL。 还有更多你需要知道的关于列表和阻塞选项,建议你阅读下面的页面: 使用RPOLPUSH构建更安全的队列和旋转队列。 BRPOPLPUSH命令是其阻塞变种命令。 自动创建和删除键 到目前为止的例子中,我们还没有在添加元素前创建一个空的列表,也没有删除一个没有元素的空列表。要注意,当列表为空时Redis将删除该键,当向一个不存在的列表键(如使用LPUSH)添加一个元素时,将创建一个空的列表。 这并不只是针对列表,适用于所有Redis多元素组成的数据类型,因此适用于集合,有序集合和哈希。 基本上我们可以概括为三条规则: 1.当我们向聚合(aggregate)数据类型添加一个元素,如果目标键不存在,添加元素前将创建一个空的聚合数据类型。 2.当我们从聚合数据类型删除一个元素,如果值为空,则键也会被销毁。 3.调用一个像LLEN的只读命令(返回列表的长度),或者一个写命令从空键删除元素,总是产生和操作一个持有空聚合类型值的键一样的结果。 规则1的例子:
复制代码 然而,我们不能执行一个错误键类型的操作:
复制代码 规则2的例子:
复制代码 当所有元素弹出后,键就不存在了。 规则3的例子:
复制代码 Redis哈希/散列 (Hashes) Redis哈希看起来正如你期待的那样:
复制代码 哈希就是字段值对(fields-values pairs)的集合。由于哈希容易表示对象,事实上哈希中的字段的数量并没有限制,所以你可以在你的应用程序以不同的方式来使用哈希。 HMSET命令为哈希设置多个字段,HGET检索一个单独的字段。HMGET类似于HGET,但是返回值的数组:
复制代码 也有一些命令可以针对单个字段执行操作,例如HINCRBY:
复制代码 你可以从命令页找到全部哈希命令列表。 值得注意的是,小的哈希(少量元素,不太大的值)在内存中以一种特殊的方式编码以高效利用内存。 Redis集合(Sets) Redis集合是无序的字符串集合(collections)。SADD命令添加元素到集合。还可以对集合执行很多其他的操作,例如,测试元素是否存在,对多个集合执行交集、并集和差集,等等。
复制代码 我们向集合总添加了3个元素,然后告诉Redis返回所有元素。如你所见,他们没有排序,Redis在每次调用时按随意顺序返回元素,因为没有与用户有任何元素排序协议。 我们有测试成员关系的命令。一个指定的元素存在吗?
复制代码 “3”是集合中的成员,”30”则不是。 集合适用于表达对象间关系。例如,我们可以很容易的实现标签。对这个问题的最简单建模,就是有一个为每个需要标记的对象的集合。集合中保存着与对象相关的标记的ID。 假设,我们想标记新闻。如果我们的ID为1000的新闻,被标签1,2,5和77标记,我们可以有一个这篇新闻被关联标记ID的集合:
复制代码 然而有时候我们也想要一些反向的关系:被某个标签标记的所有文章:
复制代码 获取指定对象的标签很简单:
复制代码 注意:在这个例子中,我们假设你有另外一个数据结构,例如,一个Redis哈希,存储标签ID到标签名的映射。 还有一些使用正确的Redis命令就很容实现的操作。例如,我们想获取所有被标签1,2,10和27同时标记的对象列表。我们可以使用SINTER命令实现这个,也就是对不同的集合执行交集。我们只需要:
复制代码 并不仅仅是交集操作,你也可以执行并集,差集,随机抽取元素操作等等。 抽取一个元素的命令是SPOP,就方便为很多问题建模。例如,为了实现一个基于web的扑克游戏,你可以将你的一副牌表示为集合。假设我们使用一个字符前缀表示(C)lubs梅花, (D)iamonds方块,(H)earts红心,(S)pades黑桃。
复制代码 现在我们为每位选手提供5张牌。SPOP命令删除一个随机元素,返回给客户端,是这个场景下的最佳操作。 然而,如果我们直接对这副牌调用,下一局我们需要再填充一副牌,这个可能不太理想。所以我们一开始要复制一下deck键的集合到game:1:deck键。 这是通过使用SUNIONSTORE命令完成的,这个命令通常对多个集合执行交集,然后把结果存储在另一个集合中。而对单个集合求交集就是其自身,于是我可以这样拷贝我的这副牌:
复制代码 现在我们准备好为第一个选手提供5张牌:
复制代码 只有一对jack,不太理想…… 现在是时候介绍提供集合中元素数量的命令。这个在集合理论中称为集合的基数(cardinality,也称集合的势),所以相应的Redis命令称为SCARD。
复制代码 数学计算式为:52 - 5 = 47。 当你只需要获得随机元素而不需要从集合中删除,SRANDMEMBER命令则适合你完成任务。它具有返回重复的和非重复的元素的能力。 |