Redis 列表 ( Lists )
Redis的列表是使用链表实现的. 所以头部/尾部操作都是在常量时间完成.
LPUSH/RPUSH 头尾增加
Redis 采用链表来实现列表是因为,对于数据库系统来说,他们认为快速插入一个元素到一个很长的列表非常重要.另外一个即将描述的优势是, Redis列表能在常数时间内获得常数长度.
如果需要快速访问一个拥有大量元素的集合的中间数据,可以用另一个称为有序集合的数据结构.
Redis 列表起步
LPUSH/RPUSH 分别在头部/尾部添加一个元素到列表.
LRANGE 从列表中提取一个范围内元素
注意 LRANGE 命令使用两个索引下表,分别是返回的范围的开始和结束院所索引.两个索引坐标可以是负数,表示从后往前数,所以 -1 表示最后一个元素, -2 表示倒数第二个元素.以此类推.
另:LPUSH/RPUSH 两个命令的参数都是可变长参数.
LPOP/RPOP 分别在头部/尾部弹出一个元素 返回nil值表明列表中没有元素了.
列表的通用场景 ( Common use cases )
具有列表代表性的场景如下:
记录社交网络中用户最近提交的更新.
可以模拟队列操作 ( 生产者消费者模式 ) 不过对于此项 Redis 有提供专用的列表命令来更可靠高效的解决该问题。
上限列表 ( Capped )
Redis 允许使用列表作为一个上限集合,使用 LTRIM 命令仅仅记住最新的N项,丢弃掉所有老的项.
LTRIM 命令类似于 LRANGE, 但是不同于展示指定范围的元素,而是将其作为列表新值存储.所有范围外的元素都将被删除.
eg:
LTRIM 所指定的范围是0到2 所以 数值 4 5 都被删除了.同时又返回了我们需要的数据.
所以但要访问元素列表而又要抛弃老元素的时候,则可以使用 LTRIM 来操作
注意:尽管LRANGE是一个 O ( N ) 时间复杂度的命令,访问列表头尾附近的小范围是常量时间的操作.
列表的阻塞操作 ( blocking )
列表有一个特别的特性:阻塞操作.
简单实现方式:
生产者调用 LPUSH 添加项到列表中
消费者调用 RPOP 从列表提取 / 处理项
如果列表是空的 则RPOP返回NULL.所以消费者被签字等待一段时间并且重试 RPOP 命令.这称为轮询 ( polling ), 由于其具有一些缺点,所以不适合在如下情况中使用:
1.强制 Redis 和客户端处理无用的命令 ( 但列表为空时,所有请求无实际操作 返回NULL ).
2.由于工作者受到一个 NULL 后会等待一段时间,这会延迟对项的处理.
介于以上的原因 Redis 实现了 BRPOP 和 BLPOP 两个命令,它们是但列表为空时 RPOP 和 LPOP 的会阻塞版本: 仅但一个新元素被添加到列表时, 或者到达了用户的指定超时时间, 才返回给调用者. 这个是我们在工作者中调用 BRPOP 的例子:
消费者:
生产者:
brpop tasks 10 从tasks中弹出右边的数据 等待10秒
lpush tasks 5 向tasks中压入数据5到左边
注意: 这里可以使用0作为超时让其消费者一直等待元素,你也可以指定多个列表而不仅仅是一个,同时等待多个列表,但第一个列表收到元素后就能得到通知.
关于BRPOP的一些注意事项
1.客户端按顺序服务:第一个被阻塞等待列表的客户端,将第一个收到其他客户端添加的元素,等等.
2.与 RPOP 的返回值不同: 返回的是一个数组,其中包括键的名字,因为 BRPOP 和 BLPOP 可以阻塞等待多个列表的元素.
3.如果超时时间到达,返回 NULL
还有更多需要知道的关于列表和阻塞的选项,可以具体阅读
使用 RPOLPUSH 构建跟安全的队列和旋转队列.
BRPOPLPUSH 命令是其阻塞命令的变种命令.
自动创建和删除键
当一个列表为空时 Redis 将删除该键,但向一个不存在的列表键 ( 如使用 LPUSH ) 添加一个元素时,将创建一个空的列表.
这并不只是针对列表,适用于所有 Redis 多元素组成的数据类型,因此适用于集合,有序集合和哈希.
基本上我们可以概括为三条规则:
1.但我们向聚合 ( aggregate ) 数据类型添加一个元素,如果键不存在,添加元素前就会自动创建一个空的聚合数据类型.
2.但我们从聚合数据类型删除一个元素,如果值为空,则键也会被销毁.
3.调用一个像 LLEN 的只读命令 ( 返回列表的长度 ),或者一个写命令从空键删除元素, 总是产生和操作一个持有空聚合类型值的键一样的结果.
eg:
规则1.
lpush foo 1 2 3 报错是因为 foo的数据类型是string 而我们用了列表操作lpush
规则2.
如上图所示但所有元素弹出后,键就不存在了
规则3.
Redis 哈希/散列 ( Hashes )
Redis 哈希看起来正如你所期待的那样:
哈希就是字段值对 ( fields-values pairs ) 的集合.由于哈希容易表示对象,事实上哈希中的字段的数量并没有限制,所以我们可以在应用程序以不同的方式来使用哈希.
HMSET 命令为哈希设置多个字段, HGET 检索一个单独的字段. HMGET 类似于 HGET, 但是返回值是数组:
也有一些命令可以针对单个字段执行操作, 例如 HINCRBY:
你可以从命令页找到全部哈希命令列表.
值得注意的是,小的哈希 ( 少量元素,不太大的值 ) 在内存中以一种特殊的方式编码以高效利用内存.
Redis 集合 (Sets)
Redis 集合是无序的字符串集合 ( collections ). SADD 命令添加元素到集合.还可以对集合执行很多其他的操作,例如:测试元素是否存在,对多个集合执行交集/并集/和差集,等等.
如上图给集合添加了 3 个元素,然后告诉 Redis 返回所有元素.
注:这里我用的MS维护的Redis客户端就自动排序了.其实他们是没有排序,Redis在每次调用时按随意顺序返回元素,因为没有与用户有任何元素排序协议.
我们有测试成员关系的命令.一个指定的元素存在吗?
" 3 " 是成员中的元素 反之 " 4 " 不是
集合适用于表达对象间的关系.建模等~
假设,我们想标记新闻.如果我们的 ID 为 1000 的新闻,被标签1, 2, 5 和 77标记,我们可以有一个这篇新闻被关联标记 ID 的集合:
然后有时候我们也想要一些方向的关系 : 被某个标签标记的所有文章:
获取指定对象的标签很简单:
注意:在这个例子中,我们假设有另一个数据结构,例如,一个 Redis 哈希, 存储标签 ID 到标签名的映射.
还有一些 Redis 命令很容易实现该操作.
eg: 我们想获取所有被标签 1, 2, 10 和 27 同时标记的对象列表.
我们可以使用 SINTER 命令实现这个, 也就是对不同的集合执行交集.
并不仅仅是交集操作, 也可以执行并集,差集,随机抽取元素操作等等.
抽取一个元素的命令是 SPOP, 就方便很多问题建模.
eg:实现一个基于 web 的扑克游戏, 我可以将一副牌表示为集合.假设我们使用一个字符前缀表示(C)lubs 梅花, (D)iamonds 方块, (H)earts 红心, (S)pades 黑桃.
现在我们要先copy一副牌数据,然后为每位选手提供5张牌.SPOP命令删除一个随机元素,返回给客户端,是在额个场景下的最佳操作.
SUNIONSTORE 命令通常对多个集合执行交集,然后把结果存储在另外一个集合中.而对单个集合求交集就是其自身,于是:
为第一个选手提供5张牌:
好差的牌~ 哈
SCARD 是提供集合中元素数量的命令.这个在集合理论中称为集合的基数 ( cardinality, 也称集合的势 ).
OK 因为刚刚spop了5次 所以还剩47张牌 ( 元素 ).
SRANDMEMBER 命令是提供获取随机元素的命令,只是他不会将获取到的元素从集合中删除.它具有返回重复元素和非重复元素的特性.