Redis数据结构整理总结

在redis中一共有五种对象string、hash、list、set和sorted set,我们来简单总结下它们各自的内容:

string—字符串

     字符串类型是Redis中最为基础的数据存储类型。它在Redis中是二进制安全的,可以接受任何格式的数据,如JPEG图像数据、Json对象描述信息等,如果只使用一个key对应一个value这种类型,与memcached类似。但redis功能更丰富(在Redis中字符串类型的value最长数据长度是512M),字符串对象的编码可以是int,raw或者embstr。

  1. 如果一个字符串对象保存的是整数值, 并且这个整数值可以用 long 类型来表示, 那么字符串对象会将整数值保存在字符串对象结构的ptr 属性里面(将 void* 转换成 long ), 并将字符串对象的编码设置为 int 。
  2. 如果字符串对象保存的是一个字符串值, 并且这个字符串值的长度小于等于 39 字节, 那么字符串对象将使用 embstr 编码的方式来保存这个字符串值。
  3. 如果字符串对象保存的是一个字符串值, 并且这个字符串值的长度大于 39 字节, 那么字符串对象将使用一个简单动态字符串(SDS)来保存这个字符串值, 并将对象的编码设置为 raw 。
  4. long double 类型表示的浮点数在 Redis 中也是作为字符串值来保存的: 如果我们要保存一个浮点数到字符串对象里面, 那么程序会先将这个浮点数转换成字符串值, 然后再保存起转换所得的字符串值。在有需要的时候, 程序会将保存在字符串对象里面的字符串值转换回浮点数值, 执行某些操作, 然后再将执行操作所得的浮点数值转换回字符串值, 并继续保存在字符串对象里面。

embstr 编码是专门用于保存短字符串的一种优化编码方式,在执行命令时产生的效果和 raw 编码的字符串对象执行命令时产生的效果是相同的,因为 embstr 编码的字符串对象的所有数据都保存在一块连续的内存里面, 所以这种编码的字符串对象比起 raw 编码的字符串对象能够更好地利用缓存带来的优势。但embstr 编码将创建字符串对象所需的内存分配次数从 raw 编码的两次降低为一次。释放 embstr 编码的字符串对象只需要调用一次内存释放函数, 而释放 raw 编码的字符串对象需要调用两次内存释放函数。

字符串部分命令小结:

命令原型 时间复杂度 命令描述 返回值
APPENDkey value O(1) 如果该Key已经存在,APPEND命令将参数Value的数据追加到已存在Value的末尾。如果该Key不存在,APPEND命令将会创建一个新的Key/Value。 追加后Value的长度。
DECR key O(1) 将指定Key的Value原子性的递减1。该操作的取值范围是64位有符号整型。如果该Key不存在,其初始值为0,在decr之后其值为-1。如果Value的值不能转换为整型值,该操作将执行失败并返回相应的错误信息。 递减后的Value值。
INCR key O(1) 将指定Key的Value原子性的递增1。该操作的取值范围是64位有符号整型。如果该Key不存在,其初始值为0,在incr之后其值为1。如果Value的值不能转换为整型值,该操作将执行失败并返回相应的错误信息。 递增后的Value值。
DECRBY

key decrement

O(1) 将指定Key的Value原子性的减少decrement。该操作的取值范围是64位有符号整型。如果该Key不存在,其初始值为0,在decrby之后其值为-decrement。如果Value的值不能转换为整型值,该操作将执行失败并返回相应的错误信息。 减少后的Value值。
INCRBYFLOAT

key increment

O(1) 为 key 中所储存的值加上浮点数增量 increment 。如果 key 不存在,那么 INCRBYFLOAT 会先将 key 的值设为 0 ,再执行加法操作。如果命令执行成功,那么 key 的值会被更新为执行加法之后的值,并且新值会以字符串的形式返回给调用者。无论加法计算所得的浮点数的实际精度有多长, INCRBYFLOAT 的计算结果也最多只能表示小数点的后十七位。当key 的值不是字符串类型或当前的值或者给定的增量 increment 不能解释为双精度浮点数,返回一个错误。 执行命令之后 key 的值。
INCRBY

key increment

O(1) 将指定Key的Value原子性的增加increment。该操作的取值范围是64位有符号整型。如果该Key不存在,其初始值为0,在incrby之后其值为increment。如果Value的值不能转换为整型值,该操作将执行失败并返回相应的错误信息。 增加后的Value值。
GET key O(1) 获取指定Key的Value。如果与该Key关联的Value不是string类型,Redis将返回错误信息。 与该Key相关的Value,如果该Key不存在,返回nil。
SET key value O(1) 设定该Key持有指定的字符串Value,如果该Key已经存在,则覆盖其原有值。 总是返回”OK”。
GETSET

key value

O(1) 原子性的设置该Key为指定的Value,同时返回该Key的原有值。只能处理string Value,否则Redis将给出相关的错误信息。 返回该Key的原有值,如果该Key之前并不存在,则返回nil。
STRLEN key O(1) 返回指定Key的字符值长度,如果Value不是string类型,Redis将执行失败并给出相关的错误信息。 返回指定Key的Value字符长度,如果该Key不存在,返回0。
SETEX

key

seconds value

O(1) 原子性完成两个操作,一是设置该Key的值为指定字符串,同时设置该Key在Redis服务器中的存活时间(秒数)。  成功返回OK
SETNX

key value

O(1) 如果指定的Key不存在,则设定该Key持有指定字符串Value,此时其效果等价于SET命令。相反,如果该Key已经存在,该命令将不做任何操作并返回。 1表示设置成功,否则0。
SETRANGE

key offset value

O(1) 替换指定Key的部分字符串值。从offset开始,替换的长度为该命令第三个参数value的字符串长度,如果offset的值大于该Key的原有值Value的字符串长度,Redis将会在Value的后面补齐(offset – strlen(value))数量的0x00,之后再追加新值。如果该键不存在,该命令会将其原值的长度假设为0,并在其后添补offset个0x00后再追加新值。鉴于字符串Value的最大长度为512M,因此offset的最大值为536870911。如果该命令在执行时致使指定Key的原有值长度增加,这将会导致Redis重新分配足够的内存以容纳替换后的全部字符串,因此就会带来一定的性能折损。 修改后的字符串Value长度。
GETRANGE

key start end

O(1) 如果截取的字符串长度很短,我们可以该命令的时间复杂度视为O(1),否则就是O(N),这里N表示截取的子字符串长度。该命令在截取子字符串时,将以闭区间的方式同时包含start(0表示第一个字符)和end所在的字符,如果end值超过Value的字符长度,该命令将只是截取从start开始之后所有的字符数据。 子字符串
SETBIT

key offset value

O(1) 设置在指定Offset上BIT的值,该值只能为1或0,在设定后该命令返回该Offset上原有的BIT值。如果指定Key不存在,该命令将创建一个新值,并在指定的Offset上设定参数中的BIT值。如果Offset大于Value的字符长度,Redis将拉长Value值并在指定Offset上设置参数中的BIT值,中间添加的BIT值为0。最后需要说明的是Offset值必须大于0。 在指定Offset上的BIT原有值。
GETBIT

key offset

O(1) 返回在指定Offset上BIT的值,0或1。如果Offset超过string value的长度,该命令将返回0,所以对于空字符串始终返回0。 在指定Offset上的BIT值。
MGET

key [key …]

O(N) N表示获取Key的数量。返回所有指定Keys的Values,如果其中某个Key不存在,或者其值不为string类型,该Key的Value将返回nil。 返回一组指定Keys的Values的列表。
MSET

key value[key value …]

O(N) N表示指定Key的数量。该命令原子性的完成参数中所有key/value的设置操作,其具体行为可以看成是多次迭代执行SET命令。 该命令不会失败,始终返回OK。
MSETNX

key value[key value …]

O(N) N表示指定Key的数量。该命令原子性的完成参数中所有key/value的设置操作,其具体行为可以看成是多次迭代执行SETNX命令。然而这里需要明确说明的是,如果在这一批Keys中有任意一个Key已经存在了,那么该操作将全部回滚,即所有的修改都不会生效。 1表示所有Keys都设置成功,0则表示没有任何Key被修改。

模式拓展

APPEND

模式:时间序列(Time series)

APPEND 可以为一系列定长数据提供一种紧凑的表示方式,通常称之为时间序列。每当一个新数据到达的时候,执行:

然后可以通过以下的方式访问时间序列的各项属性:

1)STRLEN 给出时间序列中数据的数量

2)GETRANGE 可以用于随机访问。只要有相关的时间信息的话,我们就可以在 Redis 2.6 中使用 Lua 脚本和 GETRANGE 命令实现二分查找。

3)SETRANGE 可以用于覆盖或修改已存在的的时间序列。

这个模式的缺陷是我们只能增长时间序列,而不能对时间序列进行缩短,因为 Redis 目前还没有对字符串进行修的命令。也可以考虑使用 UNIX 时间戳作为时间序列的键名,这样一来,可以避免单个 key 因为保存过大的时间序列而占用大量内存,另一方面,也可以节省下大量命名空间。

BITCOUNT

模式:使用 bitmap 实现用户上线次数统计

Bitmap 对于一些特定类型的计算非常有效。假设现在我们希望记录自己网站上的用户的上线频率,比如计算用户 A 上线了多少天,用户 B 上线了多少天,诸如此类 —— 这个模式可以使用 SETBIT 和 BITCOUNT 来实现。每当用户在某一天上线的时候,我们就使用 SETBIT ,以用户名作为 key ,将那天所代表的网站的上线日作为 offset 参数,并将这个 offset 上的为设置为 1 。如果今天是网站上线的第 100 天,而用户 peter 在今天阅览过网站,那么执行命令 SETBIT peter 100 1 ;如果明天 peter 也继续阅览网站,那么执行命令 SETBIT peter 101 1 ,以此类推。当要计算 peter 总共以来的上线次数时,就使用 BITCOUNT 命令:执行 BITCOUNT peter ,得出的结果就是 peter 上线的总天数。上线次数统计例子,即使运行 10 年,占用的空间也只是每个用户 10*365 比特位(bit),也即是每个用户 456 字节。对于这种大小的数据来说, BITCOUNT 的处理速度就像 GET 和 INCR 这种 O(1) 复杂度的操作一样快。如果bitmap 数据非常大,那么可以将一个大的 bitmap 分散到不同的 key 中,作为小的 bitmap 来处理。或使用 BITCOUNT 的 start 和 end 参数,每次只对所需的部分位进行计算,将位的累积工作放到客户端进行,并且对结果进行缓存。

GETSET         

模式:和 INCR 组合使用,实现一个有原子性复位操作的计数器。

每次当某个事件发生时,通常我们还要在一个原子时间内同时完成获得计数器的值和将计数器值复位为某个值两个操作。

INCR

模式:计数器

计数器是 Redis 的原子性自增操作可实现的最直观的模式了,每当某个操作发生时,向 Redis 发送一个 INCR 命令。比如在一个 web 应用程序中,想获取用户在一年中每天的点击量,那么只要将用户 ID 以及相关的日期信息作为键,并在每次用户点击页面时,执行一次自增操作即可。比如用户名是 peter ,点击时间是 2012 年 3 月 22 日,那么执行命令 INCR peter::2012.3.22 。

也可以组合使用 INCR 和 EXPIRE ,来达到只在规定的生存时间内进行计数的目的。也可以通过使用 GETSET 命令原子性地获取计数器的当前值并将计数器清零。使用其他自增/自减操作,比如 DECR 和 INCRBY ,用户可以通过执行不同的操作增加或减少计数器的值,比如在游戏中的记分器就可能用到这些命令。

模式:限速器

限速器是特殊化的计算器,它用于限制一个操作可以被执行的速率。限速器的典型用法是限制公开 API 的请求次数,以下是一个限速器实现示例,它将 API 的最大请求数限制在每个 IP 地址每秒钟十个之内:

这个实现每秒钟为每个 IP 地址使用一个不同的计数器,并用 EXPIRE 命令设置生存时间。使用事务打包执行 INCR 命令和 EXPIRE 命令,避免引入竞争条件,保证每次调用 API 时都可以正确地对计数器进行自增操作并设置生存时间。

以下是另一个限速器实现:

这个限速器只使用单个计数器,它的生存时间为一秒钟,如果在一秒钟内,这个计数器的值大于 10 的话,那么访问就会被禁止。这个限速器在思路方面是没有问题的,但在实现方面不够严谨。在 INCR 和 EXPIRE 之间存在着一个竞争条件,假如客户端在执行 INCR 之后,因为某些原因(比如客户端失败)而忘记设置 EXPIRE 的话,那么这个计数器就会一直存在下去,造成每个用户只能访问 10 次。要消灭这个实现中的竞争条件,我们可以将它转化为一个 Lua 脚本,并放到 Redis 中运行(这个方法仅限于 Redis 2.6 及以上的版本):

通过将计数器作为脚本放到 Redis 上运行,我们保证了 INCR 和 EXPIRE 两个操作的原子性,现在这个脚本实现不会引入竞争条件,它可以运作的很好。

关于在 Redis 中运行 Lua 脚本的更多信息,请参考 EVAL 命令。

还有另一种消灭竞争条件的方法,就是使用 Redis 的列表结构来代替 INCR 命令,这个方法无须脚本支持,因此它在 Redis 2.6 以下的版本也可以运行得很好:

新的限速器使用了列表结构作为容器, LLEN 用于对访问次数进行检查,一个事务包裹着 RPUSH 和 EXPIRE 两个命令,用于在第一次执行计数时创建列表,并正确设置地设置过期时间,最后, RPUSHX 在后续的计数操作中进行增加操作。

SET

从 Redis 2.6.12 版本开始, SET 命令的行为可以通过一系列参数来修改:

EX second:设置键的过期时间为 second 秒。SET key value EX second 效果等同于 SETEX key second value 。

PX millisecond :设置键的过期时间为 millisecond 毫秒。 SET key value PX millisecond 效果等同于 PSETEX key millisecond value。

NX :只在键不存在时,才对键进行设置操作。 SET key value NX 效果等同于 SETNX key value 。

XX :只在键已经存在时,才对键进行设置操作。

命令

是一种在 Redis 中实现锁的简单方法。如果服务器返回 OK ,那么这个客户端获得锁。如果服务器返回 NIL ,那么客户端获取锁失败,可以在稍后再重试。设置的过期时间到达之后,锁将自动释放。

防止持有过期锁的客户端误删现有锁:不使用固定的字符串作为键的值,而是设置一个不可猜测的长随机字符串,作为口令串。不使用 DEL 命令来释放锁,而是发送一个 Lua 脚本,这个脚本只在客户端传入的值和键的口令串相匹配时,才对键进行删除。

以下是一个简单的解锁脚本示例:

这个脚本可以通过 EVAL …script… 1 resource-name token-value 命令来调用。

List–列表对象

Redis list基于Linked Lists实现。即使在一个list中有数百万个元素,通过push,pop操作从链表的头部或者尾部添加删除元素,其时间复杂度也是常数级别的。但如果元素插入或删除操作是作用于链表中间,会非常低效。链表的最大长度是2^32。在插入时,如果该键并不存在,Redis将为该键创建一个新的链表。与此相反,如果链表中所有的元素均被移除,那么该键也将会被从数据库中删除。

Redis链表经常会被用于消息队列的服务,以完成多程序之间的消息交换。假设一个应用程序正在执行LPUSH操作向链表中添加新的元素,我们通常将这样的程序称之为”生产者”,而另外一个应用程序正在执行RPOP操作从链表中取出元素,我们称这样的程序为”消费者”。如果此时,消费者程序在取出消息元素后立刻崩溃,由于该消息已经被取出且没有被正常处理,那么我们就可以认为该消息已经丢失,由此可能会导致业务数据丢失,或业务状态的不一致等现象的发生。可以通过使用RPOPLPUSH命令,消费者程序在从主消息队列中取出消息之后再将其插入到备份队列中,直到消费者程序完成正常的处理逻辑后再将该消息从备份队列中删除。同时我们还可以提供一个守护进程,当发现备份队列中的消息过期时,可以重新将其再放回到主消息队列中,以便其它的消费者程序继续处理。

列表对象的编码可以是ziplist或者linkedlist。当列表对象同时满足两个条件时,列表对象使用ziplist编码:

  • 列表对象保存的所有字符串元素的长度都小于64字节;
  • 列表对象保存的元素数量小于512个。

以上两个条件的上限值可以修改配置文件list-max-ziplist-value选项和list-max-ziplist-entries选项。

List命令

命令原型 时间复杂度 命令描述 返回值
LPUSHkey value [value …] O(1) 在指定Key所关联的List Value的头部插入参数中给出的所有Values。如果该Key不存在,该命令将在插入之前创建一个与该Key关联的空链表,之后再将数据从链表的头部(列表左边)插入。如果该键的Value不是链表类型,该命令将返回相关的错误信息。 插入后链表中元素的数量。
LPUSHXkey value O(1) 仅有当参数中指定的Key存在时,该命令才会在其所关联的List Value的头部插入参数中给出的Value,否则将不会有任何操作发生。 插入后链表中元素的数量。
LRANGEkey start stop O(S+N)时间复杂度中的S为start参数表示的偏移量,N表示元素的数量。 该命令的参数start和end都是0-based。即0表示链表头部的第一个元素。其中start的值也可以为负值,-1将表示链表中的最后一个元素,即尾部元素,-2表示倒数第二个并以此类推。该命令在获取元素时,start和end位置上的元素也会被取出。如果start的值大于链表中元素的数量,空链表将会被返回。如果end的值大于元素的数量,该命令则获取从start(包括start)开始,链表中剩余的所有元素。 返回指定范围内元素的列表。
LPOP key O(1) 返回并弹出指定Key关联的链表中列表左边的第一个元素(头部元素)。如果该Key不存,返回nil。 链表头部的元素。
LLEN key O(1) 返回指定Key关联的链表中元素的数量,如果该Key不存在,则返回0。如果与该Key关联的Value的类型不是链表,则返回相关的错误信息。 链表中元素的数量。
LREM key count value O(N) 在指定Key关联的链表中,删除前count个值等于value的元素。如果count大于0,从头向尾遍历并删除,如果count小于0,则从尾向头遍历并删除。如果count等于0,则删除链表中所有等于value的元素。如果指定的Key不存在,则直接返回0。 返回被删除的元素数量。
LSET key index value O(N) 时间复杂度中N表示链表中元素的数量。但是设定头部或尾部的元素时,其时间复杂度为O(1)。设定链表中指定位置的值为新值,其中0表示第一个元素,即头部元素,-1表示尾部元素。如果索引值Index超出了链表中元素的数量范围,该命令将返回相关的错误信息。
LINDEX key index O(N) 对于头部或尾部元素,其时间复杂度为O(1)。该命令将返回链表中指定位置的元素,index是0-based,表示头部元素,如果index为-1,表示尾部元素。如果与该Key关联的不是链表,该命令将返回相关的错误信息。 返回请求的元素,如果index超出范围,则返回nil。
LTRIMkey start stop O(N) 该命令将仅保留指定范围内的元素,从而保证链接中的元素数量相对恒定。start和stop参数都是0-based,0表示头部元素。和其他命令一样,start和stop也可以为负值,-1表示尾部元素。如果start大于链表的尾部,或start大于stop,该命令不错报错,而是返回一个空的链表,与此同时该Key也将被删除。如果stop大于元素的数量,则保留从start开始剩余的所有元素。
LINSERTkeyBEFORE|AFTERpivotvalue O(N) 该命令的功能是在pivot元素的前面或后面插入参数中的元素value。如果Key不存在,该命令将不执行任何操作。如果与Key关联的Value类型不是链表,相关的错误信息将被返回。 成功插入后链表中元素的数量,如果没有找到pivot,返回-1,如果key不存在,返回0。
RPUSH key value [value …] O(1) 在指定Key所关联的List Value的尾部插入参数中给出的所有Values。如果该Key不存在,该命令将在插入之前创建一个与该Key关联的空链表,之后再将数据从链表的尾部插入。如果该键的Value不是链表类型,该命令将返回相关的错误信息。 插入后链表中元素的数量。
RPUSHX key value O(1) 仅有当参数中指定的Key存在时,该命令才会在其所关联的List Value的尾部插入参数中给出的Value,否则将不会有任何操作发生。 插入后链表中元素的数量。
RPOP key O(1) 返回并弹出指定Key关联的链表中的最后一个元素,即尾部元素,。如果该Key不存,返回nil。 链表尾部的元素。
RPOPLPUSH source destination O(1) 原子性的从与source键关联的链表尾部弹出一个元素,同时再将弹出的元素插入到与destination键关联的链表的头部。如果source键不存在,该命令将返回nil,同时不再做任何其它的操作了。如果source和destination是同一个键,则相当于原子性的将其关联链表中的尾部元素移到该链表的头部。 返回弹出和插入的元素。
BLPOPKey [key …] timeout O(1) BLPOP 是阻塞式列表的弹出原语。 它是命令 LPOP 的阻塞版本,这是因为当给定列表内没有任何元素可供弹出的时候, 连接将被 BLPOP 命令阻塞。 当给定多个 key 参数时,按参数 key 的先后顺序依次检查各个列表,弹出第一个非空列表的头元素。非阻塞行为当 BLPOP 被调用时,如果给定 key 内至少有一个非空列表,那么弹出遇到的第一个非空列表的头元素,并和被弹出元素所属的列表的名字 key 一起,组成结果返回给调用者。当存在多个给定 key 时, BLPOP 按给定 key 参数排列的先后顺序,依次检查各个列表。 我们假设 key list1 不存在,而list2 和 list3 都是非空列表。考虑以下的命令:BLPOP list1 list2 list3 0BLPOP 保证返回一个存在于 list2 里的元素(因为它是从 list1 –> list2 –> list3 这个顺序查起的第一个非空列表)。阻塞行为

如果所有给定 key 都不存在或包含空列表,那么 BLPOP 命令将阻塞连接, 直到有另一个客户端对给定的这些 key 的任意一个执行 LPUSH 或RPUSH 命令为止。

一旦有新的数据出现在其中一个列表里,那么这个命令会解除阻塞状态,并且返回 key 和弹出的元素值。

当 BLPOP 命令引起客户端阻塞并且设置了一个非零的超时参数 timeout 的时候, 若经过了指定的 timeout 仍没有出现一个针对某一特定 key 的 push 操作,则客户端会解除阻塞状态并且返回一个 nil 的多组合值(multi-bulk value)。

timeout 参数表示的是一个指定阻塞的最大秒数的整型值。当 timeout 为 0 是表示阻塞时间无限制。

 

BRPOPKey [key …] timeout O(1) 同上
BRPOPLPUSHsourcedestination timeout O(1) BRPOPLPUSH 是 RPOPLPUSH 的阻塞版本。 当 source 包含元素的时候,这个命令表现得跟 RPOPLPUSH 一模一样。 当source 是空的时候,Redis将会阻塞这个连接,直到另一个客户端 push 元素进入或者达到 timeout 时限。 timeout 为 0 能用于无限期阻塞客户端。 批量回复: 元素从 source 中弹出来,并压入 destination 中。 如果达到 timeout 时限,会返回一个 空的多批量回复

补充:

BLPOP

模式:事件提醒

为了等待一个新元素到达数据中,需要使用轮询的方式对数据进行探查。另一种更好的方式是,使用系统提供的阻塞原语,在新元素到达时立即进行处理,而新元素还没到达时,就一直阻塞住,避免轮询占用资源。对于 Redis ,我们需要一个阻塞版的 SPOP 命令,实际上,使用 BLPOP 或者 BRPOP 就能很好地解决这个问题。

使用元素的客户端(消费者)可以执行类似以下的代码:

添加元素的客户端(生产者)则执行以下代码:

  • 当客户端为多个key 尝试阻塞的时候,若至少存在一个 key 拥有元素,那么返回的键值对就是从左到右数第一个拥有一个或多个元素的key。 在这种情况下客户端不会被阻塞。比如对于 BLPOP key1 key2 key3 key4 0,假设 key2 和 key4 都非空, 那么就会返回 key2 里的一个元素。若多个 key 的元素同时可用(事务或者某个Lua脚本向多个list添加元素),那么客户端会解除阻塞。在处理每个 key 的时候,只要这个 key 里有元素, Redis就会对所有等待这个key的客户端按照“先进先出”(FIFO)的顺序进行服务。
  • 当多个客户端为同一个 key 阻塞的时候,从第一个被阻塞的处理到最后一个被阻塞的。

当多个元素被 push 进入一个 list 时 BLPOP 的行为

有时候一个 list 会在同时接收到多个元素:像 LPUSH mylist a b c 这样的可变 push 操作。当多个元素被 push 进入一个被客户端阻塞着的 list 的时候,Redis 2.4 和 Redis 2.6 或者更新的版本所采取行为是不一样的。对于 Redis 2.6 来说,所采取的行为是先执行多个 push 命令,然后在执行了这个命令之后再去服务被阻塞的客户端。看看下面命令顺序。

Redis 2.6 或更高版本的服务器上,客户端 A 会接收到 c 元素,因为在 LPUSH 命令执行后,list 包含了 c,b,a 这三个元素,所以从左边取一个元素就会返回 c。需要注意的是,一个Lua脚本或者一个 MULTI/EXEC 块可能会push一堆元素进入一个 list 后,再删除这个list。 在这种情况下,被阻塞的客户端完全不会被服务,并且只要在执行某个单一命令、事务或者脚本后 list 中没有出现元素,它就会被继续阻塞下去。

Redis 2.4 工作方式:客户端会在 push 操作的上下文中被服务,所以当 LPUSH foo a b c 开始往 list 中 push 第一个元素,它就被传送给客户端A,也就是客户端A会接收到 a(第一个被 push 的元素)。Redis 2.4的这种行为会在复制或者持续把数据存入AOF文件的时候引发很多问题,所以为了防止这些问题,很多更一般性的、并且在语义上更简单的行为被引入到 Redis 2.6 中。

在一个 MULTI / EXEC 事务中的 BLPOP

BLPOP 可以用于流水线(pipeline,发送多个命令并且批量读取回复),特别是当它是流水线里的最后一个命令的时候,这种设定更加有意义。在一个 MULTI / EXEC 块里面使用 BLPOP 并没有很大意义,因为它要求整个服务器被阻塞以保证块执行时的原子性,这就阻止了其他客户端执行一个 push 操作。因此,一个在 MULTI / EXEC 里面的 BLPOP 命令会在 list 为空的时候返回一个 nil 值,这跟超时的时候发生的一样。

返回值

多批量回复:当没有元素的时候会弹出一个 nil 的多批量值,并且 timeout 过期。当有元素弹出时会返回一个双元素的多批量值,其中第一个元素是弹出元素的 key,第二个元素是 value。

例子

RPOPLPUSH 和BRPOPLPUSH

模式:安全的队列

Redis通常都被用做一个处理各种后台工作或消息任务的消息服务器。 一个简单的队列模式就是:生产者把消息放入一个列表中,等待消息的消费者用 RPOP 命令(用轮询方式), 或者用 BRPOP 命令(如果客户端使用阻塞操作会更好)来得到这个消息。然而,因为消息有可能会丢失,所以这种队列并是不安全的。例如,当接收到消息后,出现了网络问题或者消费者端崩溃了, 那么这个消息就丢失了。RPOPLPUSH (或者其阻塞版本的 BRPOPLPUSH) 提供了一种方法来避免这个问题:消费者端取到消息的同时把该消息放入一个正在处理中的列表。 当消息被处理了之后,该命令会使用 LREM 命令来移除正在处理中列表中的对应消息。

另外,可以添加一个客户端来监控这个正在处理中列表,如果有某些消息已经在这个列表中存在很长时间了(即超过一定的处理时限), 那么这个客户端会把这些超时消息重新加入到队列中。

模式:循环列表

RPOPLPUSH 命令的 source 和 destination 是相同的话, 那么客户端在访问一个拥有n个元素的列表时,可以在 O(N) 时间里一个接一个获取列表元素, 而不用像 LRANGE 那样需要把整个列表从服务器端传送到客户端。我们可以很容易地实现这样一个系统:有 N 个客户端,需要连续不断地对一批元素进行处理,而且处理的过程必须尽可能地快。 一个典型的例子就是服务器上的监控程序:它们需要在尽可能短的时间内,并行地检查一批网站,确保它们的可访问性。上面这种模式即使在以下两种情况下照样能很好地工作:

1)有多个客户端同时对同一个列表进行旋转(rotating):它们会取得不同的元素,直到列表里所有元素都被访问过,又从头开始这个操作。

2)有其他客户端在往列表末端加入新的元素。

使用这个模式的客户端是易于扩展(scalable)且安全的(reliable),因为即使客户端把接收到的消息丢失了, 这个消息依然存在于队列中,等下次迭代到它的时候,由其他客户端进行处理。

哈希—Hash

在memcached中,经常把一些结构化的信息(比如用户名、年龄等)打包成hashmap,在客户端系列化后存储为一个字符串的值(一般是json格式)。当我们需要修改其中某一项时,通常需要将json取出,然后进行反序列化,修改某一项的值,再序列化成json存储回去。这样简单修改一个属性的代价略大,也不适用于并发操作的场合。redis的hash结构可以解决这个问题。

Hash命令

命令原型 时间复杂度 命令描述 返回值
HSETkey field value O(1) 为指定的Key设定Field/Value对如果Key不存在,该命令将创建新Key以参数中的Field/Value对如果参数中的Field在该Key中已经存在,则用新值覆盖其原有值。 1表示新的Field被设置了新值,0表示Field已经存在,用新值覆盖原有值。
HGET key field O(1) 返回指定Key中指定Field的关联值。 返回参数中Field的关联值,如果参数中的Key或Field不存,返回nil。
HEXISTSkey field O(1) 判断指定Key中的指定Field是否存在。 1表示存在,0表示参数中的Field或Key不存在。
HLEN key O(1) 获取该Key所包含的Field的数量。 返回Key包含的Field数量,如果Key不存在,返回0。
HDELkey field [field …] O(N) 从指定Key的Hashes Value中删除参数中指定的多个字段,如果不存在的字段将被忽略。如果Key不存在,则将其视为空Hashes,并返回0. 实际删除的Field数量。
HSETNXkey field value O(1) 只有当参数中的Key或Field不存在的情况下,为指定的Key设定Field/Value对,否则该命令不会进行任何操作。 1表示新的Field被设置了新值,0表示Key或Field已经存在,该命令没有进行任何操作。
HINCRBYkeyfieldincrement O(1) 增加指定Key中指定Field关联的Value的值。如果Key或Field不存在,该命令将会创建一个新Key或新Field,并将其关联的Value初始化为0,之后再指定数字增加的操作。该命令支持的数字是64位有符号整型,即increment可以负数。 返回运算后的值。
HINCRBYFLOAT key field increment O(1) 为哈希表 key 中的域 field 加上浮点数增量 increment 。如果哈希表中没有域 field ,那么 HINCRBYFLOAT 会先将域 field 的值设为 0 ,然后再执行加法操作。如果键 key 不存在,那么 HINCRBYFLOAT 会先创建一个哈希表,再创建域 field ,最后再执行加法操作。当以下任意一个条件发生时,返回一个错误:1)域 field 的值不是字符串类型2)域 field 当前的值或给定的增量 increment 不能解释为双精度浮点数 执行加法操作之后 field 域的值。
HGETALLkey O(N) 时间复杂度中的N表示Key包含的Field数量。获取该键包含的所有Field/Value。其返回格式为一个Field、一个Value,并以此类推。 Field/Value的列表。
HKEYSkey O(N) 时间复杂度中的N表示Key包含的Field数量。返回指定Key的所有Fields名。 Field的列表。
HVALSkey O(N) 时间复杂度中的N表示Key包含的Field数量。返回指定Key的所有Values名。 Value的列表。
HMGETkeyfield[field …] O(N) 获取和参数中指定Fields关联的一组Values。如果请求的Field不存在,其值返回nil。如果Key不存在,该命令将其视为空Hash,因此返回一组nil。 返回和请求Fields关联的一组Values,其返回顺序等同于Fields的请求顺序。
HMSETkeyfield value[field value …] O(N) 逐对依次设置参数中给出的Field/Value对。如果其中某个Field已经存在,则用新值覆盖原有值。如果Key不存在,则创建新Key,同时设定参数中的Field/Value。

set—集合

set是一个未排序的集合,和List类型一样,我们也可以在该类型的数据值上执行添加、删除或判断某一元素是否存在等操作。这些操作的时间复杂度为O(1)。Set集合可包含的最大元素数量是4294967295。和List类型不同的是,Set集合中不允许出现重复的元素,利用redis提供的set数据结构,可以存储一些集合性的数据。比如在微博应用中,可以将一个用户所有的关注人存在一个集合中,将其所有粉丝存在一个集合。因为 Redis 非常人性化的为集合提供了求交集、并集、差集等操作,那么就可以非常方便的实现如共同关注、共同喜好、二度好友等功能,对上面的所有集合操作,还可以使用不同的命令选择将结果返回给客户端还是存集到一个新的集合中。

集合命令

命令原型 时间复杂度 命令描述 返回值
SADD key member [member …] O(N) 时间复杂度中的N表示操作的成员数量。如果在插入的过程用,参数中有的成员在Set中已经存在,该成员将被忽略,而其它成员仍将会被正常插入。如果执行该命令之前,该Key并不存在,该命令将会创建一个新的Set,此后再将参数中的成员陆续插入。如果该Key的Value不是Set类型,该命令将返回相关的错误信息。 本次操作实际插入的成员数量。
SCARD key O(1) 获取Set中成员的数量。 返回Set中成员的数量,如果该Key并不存在,返回0。
SISMEMBER key member O(1) 判断参数中指定成员是否已经存在于与Key相关联的Set集合中。 1表示已经存在,0表示不存在,或该Key本身并不存在。
SMEMBERS key O(N) 时间复杂度中的N表示Set中已经存在的成员数量。获取与该Key关联的Set中所有的成员。 返回Set中所有的成员。
SPOP key O(1) 随机的移除并返回Set中的某一成员。 由于Set中元素的布局不受外部控制,因此无法像List那样确定哪个元素位于Set的头部或者尾部。 返回移除的成员,如果该Key并不存在,则返回nil。
SREMkey member [member …] O(N) 时间复杂度中的N表示被删除的成员数量。从与Key关联的Set中删除参数中指定的成员,不存在的参数成员将被忽略,如果该Key并不存在,将视为空Set处理。 从Set中实际移除的成员数量,如果没有则返回0。
SRANDMEMBER key O(1) 和SPOP一样,随机的返回Set中的一个成员,不同的是该命令并不会删除返回的成员。 返回随机位置的成员,如果Key不存在则返回nil。
SMOVEsource destination member O(1) 原子性的将参数中的成员从source键移入到destination键所关联的Set中。因此在某一时刻,该成员或者出现在source中,或者出现在destination中。如果该成员在source中并不存在,该命令将不会再执行任何操作并返回0,否则,该成员将从source移入到destination。如果此时该成员已经在destination中存在,那么该命令仅是将该成员从source中移出。如果和Key关联的Value不是Set,将返回相关的错误信息。 1表示正常移动,0表示source中并不包含参数成员。
SDIFF key [key …] O(N) 时间复杂度中的N表示所有Sets中成员的总数量。返回参数中第一个Key所关联的Set和其后所有Keys所关联的Sets中成员的差异。如果Key不存在,则视为空Set。 差异结果成员的集合。
SDIFFSTOREdestination key [key …] O(N) 该命令和SDIFF命令在功能上完全相同,两者之间唯一的差别是SDIFF返回差异的结果成员,而该命令将差异成员存储在destination关联的Set中。如果destination键已经存在,该操作将覆盖它的成员。 返回差异成员的数量。
SINTER key [key …] O(N*M) 时间复杂度中的N表示最小Set中元素的数量,M则表示参数中Sets的数量。该命令将返回参数中所有Keys关联的Sets中成员的交集。因此如果参数中任何一个Key关联的Set为空,或某一Key不存在,那么该命令的结果将为空集。 交集结果成员的集合。
SINTERSTOREdestination key [key …] O(N*M) 该命令和SINTER命令在功能上完全相同,两者之间唯一的差别是SINTER返回交集的结果成员,而该命令将交集成员存储在destination关联的Set中。如果destination键已经存在,该操作将覆盖它的成员。 返回交集成员的数量。
SUNION key [key …] O(N) 时间复杂度中的N表示所有Sets中成员的总数量。该命令将返回参数中所有Keys关联的Sets中成员的并集。 并集结果成员的集合。
SUNIONSTOREdestination key [key …] O(N) 该命令和SUNION命令在功能上完全相同,两者之间唯一的差别是SUNION返回并集的结果成员,而该命令将并集成员存储在destination关联的Set中。如果destination键已经存在,该操作将覆盖它的成员。 返回并集成员的数量。

 有序集合   sorted set

有序集合的编码可以是ziplist或者skiplist,和set相比,sorted set是将set中的元素增加了一个权重参数score,使得集合中的元素能用按照score进行有序排列。

命令

命令原型 时间复杂度 命令描述 返回值
ZADDkeyscore member[score] [member] O(log(N)) 时间复杂度中的N表示Sorted-Sets中成员的数量。添加参数中指定的所有成员及其分数到指定key的Sorted-Set中,在该命令中我们可以指定多组score/member作为参数。如果在添加时参数中的某一成员已经存在,该命令将更新此成员的分数为新值,同时再将该成员基于新值重新排序。如果键不存在,该命令将为该键创建一个新的Sorted-Sets Value,并将score/member对插入其中。如果该键已经存在,但是与其关联的Value不是Sorted-Sets类型,相关的错误信息将被返回。 本次操作实际插入的成员数量。
ZCARD key O(1) 获取与该Key相关联的Sorted-Sets中包含的成员数量。 返回Sorted-Sets中的成员数量,如果该Key不存在,返回0。
ZCOUNTKeymin max O(log(N)+M) 时间复杂度中的N表示Sorted-Sets中成员的数量,M则表示min和max之间元素的数量。该命令用于获取分数(score)在min和max之间的成员数量。针对minmax参数需要额外说明的是,-inf+inf分别表示Sorted-Sets中分数的最高值和最低值。缺省情况下,minmax表示的范围是闭区间范围,即min <= score <= max内的成员将被返回。然而我们可以通过在minmax的前面添加“(“字符来表示开区间,如(min max表示min < score <= max,而(min (max表示min < score < max 分数指定范围内成员的数量。
ZINCRBYkeyincrement member O(log(N)) 时间复杂度中的N表示Sorted-Sets中成员的数量。该命令将为指定Key中的指定成员增加指定的分数。如果成员不存在,该命令将添加该成员并假设其初始分数为0,此后再将其分数加上increment。如果Key不存,该命令将创建该Key及其关联的Sorted-Sets,并包含参数指定的成员,其分数为increment参数。如果与该Key关联的不是Sorted-Sets类型,相关的错误信息将被返回。 以字符串形式表示的新分数。
ZRANGEkeystart stop[WITHSCORES] O(log(N)+M) 时间复杂度中的N表示Sorted-Set中成员的数量,M则表示返回的成员数量。该命令返回顺序在参数start和stop指定范围内的成员,这里start和stop参数都是0-based,即0表示第一个成员,-1表示最后一个成员。如果start大于该Sorted-Set中的最大索引值,或start > stop,此时一个空集合将被返回。如果stop大于最大索引值,该命令将返回从start到集合的最后一个成员。如果命令中带有可选参数WITHSCORES选项,该命令在返回的结果中将包含每个成员的分数值,如value1,score1,value2,score2…。   返回索引在start和stop之间的成员列表。
ZRANGEBYSCOREkeymin max[WITHSCORES][LIMIT offset count] O(log(N)+M) 时间复杂度中的N表示Sorted-Set中成员的数量,M则表示返回的成员数量。该命令将返回分数在min和max之间的所有成员,即满足表达式min <= score <= max的成员,其中返回的成员是按照其分数从低到高的顺序返回,如果成员具有相同的分数,则按成员的字典顺序返回。可选参数LIMIT用于限制返回成员的数量范围。可选参数offset表示从符合条件的第offset个成员开始返回,同时返回count个成员。可选参数WITHSCORES的含义参照ZRANGE中该选项的说明。最后需要说明的是参数中minmax的规则可参照命令ZCOUNT 返回分数在指定范围内的成员列表。
ZRANKkeymember O(log(N)) 时间复杂度中的N表示Sorted-Set中成员的数量。Sorted-Set中的成员都是按照分数从低到高的顺序存储,该命令将返回参数中指定成员的位置值,其中0表示第一个成员,它是Sorted-Set中分数最低的成员。 如果该成员存在,则返回它的位置索引值。否则返回nil。
ZREMkeymember[member …] O(M log(N)) 时间复杂度中N表示Sorted-Set中成员的数量,M则表示被删除的成员数量。该命令将移除参数中指定的成员,其中不存在的成员将被忽略。如果与该Key关联的Value不是Sorted-Set,相应的错误信息将被返回。 实际被删除的成员数量。
ZREVRANGEkeystart stop[WITHSCORES] O(log(N)+M) 时间复杂度中的N表示Sorted-Set中成员的数量,M则表示返回的成员数量。该命令的功能和ZRANGE基本相同,唯一的差别在于该命令是通过反向排序获取指定位置的成员,即从高到低的顺序。如果成员具有相同的分数,则按降序字典顺序排序。 返回指定的成员列表。
ZREVRANKkey member O(log(N)) 时间复杂度中的N表示Sorted-Set中成员的数量。该命令的功能和ZRANK基本相同,唯一的差别在于该命令获取的索引是从高到低排序后的位置,同样0表示第一个元素,即分数最高的成员。 如果该成员存在,则返回它的位置索引值。否则返回nil。
ZSCORE key member O(1) 获取指定Key的指定成员的分数。 如果该成员存在,以字符串的形式返回其分数,否则返回nil。
ZREVRANGEBYSCOREkeymax min[WITHSCORES][LIMIT offset count] O(log(N)+M) 时间复杂度中的N表示Sorted-Set中成员的数量,M则表示返回的成员数量。该命令除了排序方式是基于从高到低的分数排序之外,其它功能和参数含义均与ZRANGEBYSCORE相同。 返回分数在指定范围内的成员列表。
ZREMRANGEBYRANKkeystart stop O(log(N)+M) 时间复杂度中的N表示Sorted-Set中成员的数量,M则表示被删除的成员数量。删除索引位置位于start和stop之间的成员,start和stop都是0-based,即0表示分数最低的成员,-1表示最后一个成员,即分数最高的成员。 被删除的成员数量。
ZREMRANGEBYSCOREkeymin max O(log(N)+M) 时间复杂度中的N表示Sorted-Set中成员的数量,M则表示被删除的成员数量。删除分数在min和max之间的所有成员,即满足表达式min <= score <= max的所有成员。对于min和max参数,可以采用开区间的方式表示,具体规则参照ZCOUNT。 被删除的成员数量。
ZREMRANGEBYLEX key min max O(log(N)+M) 其中 N 为有序集合的元素数量, 而 M 则为被移除的元素数量。对于一个所有成员的分值都相同的有序集合键 key 来说, 这个命令会移除该集合中, 成员介于 min 和 max 范围内的所有元素。这个命令的 min 参数和 max 参数的意义和 ZRANGEBYLEX 命令的 min 参数和 max 参数的意义一样。 整数回复:被移除的元素数量

 

参考资料:

http://redisdoc.com/

《redis设计与实现》

发表评论

电子邮件地址不会被公开。 必填项已用*标注

您可以使用这些HTML标签和属性: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code class="" title="" data-url=""> <del datetime=""> <em> <i> <q cite=""> <strike> <strong> <pre class="" title="" data-url=""> <span class="" title="" data-url="">