APPEND key value**

时间复杂度:O(1)。均摊时间复杂度是O(1), 因为redis用的动态字符串的库在每次分配空间的时候会增加一倍的可用空闲空间,所以在添加的value较小而且已经存在的 value是任意大小的情况下,均摊时间复杂度是O(1) 。
如果 key 已经存在,并且值为字符串,那么这个命令会把 value 追加到原来值(value)的结尾。 如果 key 不存在,那么它将首先创建一个空字符串的key,再执行追加操作,这种情况 APPEND 将类似于 SET 操作。

返回值

Integer reply:返回append后字符串值(value)的长度。

例子

  1. redis> EXISTS mykey
  2. (integer) 0
  3. redis> APPEND mykey "Hello"
  4. (integer) 5
  5. redis> APPEND mykey " World"
  6. (integer) 11
  7. redis> GET mykey
  8. "Hello World"
  9. redis>

模式:节拍序列(Time series)

APPEND 命令可以用来连接一系列固定长度的样例,与使用列表相比这样更加紧凑. 通常会用来记录节拍序列. 每收到一个新的节拍样例就可以这样记录:

  1. APPEND timeseries "fixed-size sample"

在节拍序列里, 可以很容易地访问序列中的每个元素:

  • STRLEN 可以用来计算样例个数.
  • GETRANGE 允许随机访问序列中的各个元素. 如果序列中有明确的节拍信息, 在Redis 2.6中就可以使用GETRANGE配合Lua脚本来实现一个二分查找算法.
  • SETRANGE 可以用来覆写已有的节拍序列.

该模式的局限在于只能做追加操作. Redis目前缺少剪裁字符串的命令, 所以无法方便地把序列剪裁成指定的尺寸. 但是, 节拍序列在空间占用上效率极好.
小贴士: 在键值中组合Unix时间戳, 可以在构建一系列相关键值时缩短键值长度,更优雅地分配Redis实例.
使用定长字符串进行温度采样的例子(在实际使用时,采用二进制格式会更好).

  1. redis> APPEND ts "0043"
  2. (integer) 4
  3. redis> APPEND ts "0035"
  4. (integer) 8
  5. redis> GETRANGE ts 0 3
  6. "0043"
  7. redis> GETRANGE ts 4 7
  8. "0035"
  9. redis>

SET key value [EX seconds] [PX milliseconds] [NX|XX]

起始版本:1.0.0
时间复杂度:O(1)
Set key to hold the string value. If key already holds a value, it is overwritten, regardless of its type. Any previous time to live associated with the key is discarded on successful SET operation.
将键key设定为指定的“字符串”值。
如果 key 已经保存了一个值,那么这个操作会直接覆盖原来的值,并且忽略原始类型。
set命令执行成功之后,之前设置的过期时间都将失效

选项

从2.6.12版本开始,redis为SET命令增加了一系列选项:

  • EX seconds – Set the specified expire time, in seconds.
  • PX milliseconds – Set the specified expire time, in milliseconds.
  • NX – Only set the key if it does not already exist.
  • XX – Only set the key if it already exist.
  • EX seconds – 设置键key的过期时间,单位时秒
  • PX milliseconds – 设置键key的过期时间,单位时毫秒
  • NX – 只有键key不存在的时候才会设置key的值
  • XX – 只有键key存在的时候才会设置key的值

注意: 由于SET命令加上选项已经可以完全取代SETNX, SETEX, PSETEX的功能,所以在将来的版本中,redis可能会不推荐使用并且最终抛弃这几个命令。

返回值

simple-string-reply:如果SET命令正常执行那么回返回OK,否则如果加了NX 或者 XX选项,但是没有设置条件。那么会返回nil。

例子

  1. redis> SET mykey "Hello"
  2. OK
  3. redis> GET mykey
  4. "Hello"
  5. redis>

设计模式

注意: 下面这种设计模式并不推荐用来实现redis分布式锁。应该参考the Redlock algorithm的实现,因为这个方法只是复杂一点,但是却能保证更好的使用效果。
命令 SET resource-name anystring NX EX max-lock-time 是一种用 Redis 来实现锁机制的简单方法。
如果上述命令返回OK,那么客户端就可以获得锁(如果上述命令返回Nil,那么客户端可以在一段时间之后重新尝试),并且可以通过DEL命令来释放锁。
客户端加锁之后,如果没有主动释放,会在过期时间之后自动释放。
可以通过如下优化使得上面的锁系统变得更加鲁棒:

  • 不要设置固定的字符串,而是设置为随机的大字符串,可以称为token。
  • 通过脚步删除指定锁的key,而不是DEL命令。

上述优化方法会避免下述场景:a客户端获得的锁(键key)已经由于过期时间到了被redis服务器删除,但是这个时候a客户端还去执行DEL命令。而b客户端已经在a设置的过期时间之后重新获取了这个同样key的锁,那么a执行DEL就会释放了b客户端加好的锁。
解锁脚本的一个例子将类似于以下:

  1. if redis.call("get",KEYS[1]) == ARGV[1]
  2. then
  3. return redis.call("del",KEYS[1])
  4. else
  5. return 0
  6. end

这个脚本执行方式如下:
EVAL …script… 1 resource-name token-value

SETEX key seconds value

起始版本:2.0.0
时间复杂度:O(1)
设置key对应字符串value,并且设置key在给定的seconds时间之后超时过期。这个命令等效于执行下面的命令:

  1. SET mykey value
  2. EXPIRE mykey seconds

SETEX是原子的,也可以通过把上面两个命令放到MULTI/EXEC块中执行的方式重现。相比连续执行上面两个命令,它更快,因为当Redis当做缓存使用时,这个操作更加常用。

返回值

simple-string-reply

例子

  1. redis> SETEX mykey 10 "Hello"
  2. OK
  3. redis> TTL mykey
  4. (integer) 10
  5. redis> GET mykey
  6. "Hello"
  7. redis>

SETNX key value

起始版本:1.0.0
时间复杂度:O(1)
key设置值为value,如果key不存在,这种情况下等同SET命令。 当key存在时,什么也不做。SETNX是”SET if Not eXists”的简写。

返回值

Integer reply, 特定值:

  • 1 如果key被设置了
  • 0 如果key没有被设置

    例子

    1. redis> SETNX mykey "Hello"
    2. (integer) 1
    3. redis> SETNX mykey "World"
    4. (integer) 0
    5. redis> GET mykey
    6. "Hello"
    7. redis>

    Design pattern: Locking with !SETNX

    设计模式:使用!SETNX加锁

    Please note that:
    请注意:
  1. 不鼓励以下模式来实现the Redlock algorithm ,该算法实现起来有一些复杂,但是提供了更好的保证并且具有容错性。
  2. 无论如何,我们保留旧的模式,因为肯定存在一些已实现的方法链接到该页面作为引用。而且,这是一个有趣的例子说明Redis命令能够被用来作为编程原语的。
  3. 无论如何,即使假设一个单例的加锁原语,但是从 2.6.12 开始,可以创建一个更加简单的加锁原语,相当于使用SET命令来获取锁,并且用一个简单的 Lua 脚本来释放锁。该模式被记录在SET命令的页面中。

也就是说,SETNX能够被使用并且以前也在被使用去作为一个加锁原语。例如,获取键为foo的锁,客户端可以尝试一下操作:

  1. SETNX lock.foo <current Unix time + lock timeout + 1>

如果客户端获得锁,SETNX返回1,那么将lock.foo键的Unix时间设置为不在被认为有效的时间。客户端随后会使用DEL lock.foo去释放该锁。
如果SETNX返回0,那么该键已经被其他的客户端锁定。如果这是一个非阻塞的锁,才能立刻返回给调用者,或者尝试重新获取该锁,直到成功或者过期超时。

处理死锁

以上加锁算法存在一个问题:如果客户端出现故障,崩溃或者其他情况无法释放该锁会发生什么情况?这是能够检测到这种情况,因为该锁包含一个Unix时间戳,如果这样一个时间戳等于当前的Unix时间,该锁将不再有效。
当以下这种情况发生时,我们不能调用DEL来删除该锁,并且尝试执行一个SETNX,因为这里存在一个竞态条件,当多个客户端察觉到一个过期的锁并且都尝试去释放它。

  • C1 和 C2 读lock.foo检查时间戳,因为他们执行完SETNX后都被返回了0,因为锁仍然被 C3 所持有,并且 C3 已经崩溃。
  • C1 发送DEL lock.foo
  • C1 发送SETNX lock.foo命令并且成功返回
  • C2 发送DEL lock.foo
  • C2 发送SETNX lock.foo命令并且成功返回
  • 错误:由于竞态条件导致 C1 和 C2 都获取到了锁

幸运的是,可以使用以下的算法来避免这种情况,请看 C4 客户端所使用的好的算法:

  • C4 发送SETNX lock.foo为了获得该锁
  • 已经崩溃的客户端 C3 仍然持有该锁,所以Redis将会返回0给 C4
  • C4 发送GET lock.foo检查该锁是否已经过期。如果没有过期,C4 客户端将会睡眠一会,并且从一开始进行重试操作
  • 另一种情况,如果因为 lock.foo键的Unix时间小于当前的Unix时间而导致该锁已经过期,C4 会尝试执行以下的操作:

    1. GETSET lock.foo <current Unix timestamp + lock timeout + 1>
  • 由于GETSET 的语意,C4会检查已经过期的旧值是否仍然存储在lock.foo中。如果是的话,C4 会获得锁

  • 如果另一个客户端,假如为 C5 ,比 C4 更快的通过GETSET操作获取到锁,那么 C4 执行GETSET操作会被返回一个不过期的时间戳。C4 将会从第一个步骤重新开始。请注意:即使 C4 在将来几秒设置该键,这也不是问题。

为了使这种加锁算法更加的健壮,持有锁的客户端应该总是要检查是否超时,保证使用DEL释放锁之前不会过期,因为客户端故障的情况可能是复杂的,不止是崩溃,还会阻塞一段时间,阻止一些操作的执行,并且在阻塞恢复后尝试执行DEL(此时,该LOCK已经被其他客户端所持有)

SETRANGE key offset value

起始版本:2.2.0
时间复杂度:O(1), not counting the time taken to copy the new string in place. Usually, this string is very small so the amortized complexity is O(1). Otherwise, complexity is O(M) with M being the length of the value argument.
这个命令的作用是覆盖key对应的string的一部分,从指定的offset处开始,覆盖value的长度。如果offset比当前key对应string还要长,那这个string后面就补0以达到offset。不存在的keys被认为是空字符串,所以这个命令可以确保key有一个足够大的字符串,能在offset处设置value。
注意,offset最大可以是2-1(536870911),因为redis字符串限制在512M大小。如果你需要超过这个大小,你可以用多个keys。
警告:当set最后一个字节并且key还没有一个字符串value或者其value是个比较小的字符串时,Redis需要立即分配所有内存,这有可能会导致服务阻塞一会。在一台2010MacBook Pro上,set536870911字节(分配512MB)需要~300ms,set134217728字节(分配128MB)需要~80ms,set33554432比特位(分配32MB)需要~30ms,set8388608比特(分配8MB)需要8ms。注意,一旦第一次内存分配完,后面对同一个key调用SETRANGE就不会预先得到内存分配。

模式

正因为有了SETRANGE和类似功能的GETRANGE命令,你可以把Redis的字符串当成线性数组,随机访问只要O(1)复杂度。这在很多真实场景应用里非常快和高效。

返回值

integer-reply:该命令修改后的字符串长度

例子

基本使用方法:

  1. redis> SET key1 "Hello World"
  2. OK
  3. redis> SETRANGE key1 6 "Redis"
  4. (integer) 11
  5. redis> GET key1
  6. "Hello Redis"
  7. redis>

补0的例子:

  1. redis> SETRANGE key2 6 "Redis"
  2. (integer) 11
  3. redis> GET key2
  4. "\x00\x00\x00\x00\x00\x00Redis"
  5. redis>

STRLEN key

起始版本:2.2.0
时间复杂度:O(1)
返回key的string类型value的长度。如果key对应的非string类型,就返回错误。

返回值

integer-reply:key对应的字符串value的长度,或者0(key不存在)

例子

  1. redis> SET mykey "Hello world"
  2. OK
  3. redis> STRLEN mykey
  4. (integer) 11
  5. redis> STRLEN nonexisting
  6. (integer) 0
  7. redis>

SETBIT key offset value

起始版本:2.2.0
时间复杂度:O(1)
设置或者清空key的value(字符串)在offset处的bit值。
那个位置的bit要么被设置,要么被清空,这个由value(只能是0或者1)来决定。当key不存在的时候,就创建一个新的字符串value。要确保这个字符串大到在offset处有bit值。参数offset需要大于等于0,并且小于232(限制bitmap大小为512)。当key对应的字符串增大的时候,新增的部分bit值都是设置为0。
警告:当set最后一个bit(offset等于2-1)并且key还没有一个字符串value或者其value是个比较小的字符串时,Redis需要立即分配所有内存,这有可能会导致服务阻塞一会。在一台2010MacBook Pro上,offset为2-1(分配512MB)需要~300ms,offset为2-1(分配128MB)需要~80ms,offset为2-1(分配32MB)需要~30ms,offset为2-1(分配8MB)需要8ms。注意,一旦第一次内存分配完,后面对同一个key调用SETBIT就不会预先得到内存分配。

返回值

integer-reply:在offset处原来的bit值

例子

  1. redis> SETBIT mykey 7 1
  2. (integer) 0
  3. redis> SETBIT mykey 7 0
  4. (integer) 1
  5. redis> GET mykey
  6. "\x00"
  7. redis>

GET key

起始版本:1.0.0
时间复杂度:O(1)
返回keyvalue。如果key不存在,返回特殊值nil。如果keyvalue不是string,就返回错误,因为GET只处理string类型的values

返回值

simple-string-reply:key对应的value,或者nil(key不存在时)

例子

  1. redis> GET nonexisting
  2. (nil)
  3. redis> SET mykey "Hello"
  4. OK
  5. redis> GET mykey
  6. "Hello"
  7. redis>

GETBIT key offset

起始版本:2.2.0
时间复杂度:O(1)
返回key对应的string在offset处的bit值 当offset超出了字符串长度的时候,这个字符串就被假定为由0比特填充的连续空间。当key不存在的时候,它就认为是一个空字符串,所以offset总是超出范围,然后value也被认为是由0比特填充的连续空间。到内存分配。

返回值

integer-reply:在offset处的bit值

例子

  1. redis> SETBIT mykey 7 1
  2. (integer) 0
  3. redis> GETBIT mykey 0
  4. (integer) 0
  5. redis> GETBIT mykey 7
  6. (integer) 1
  7. redis> GETBIT mykey 100
  8. (integer) 0
  9. redis>

GETRANGE key start end

起始版本:2.4.0
时间复杂度:O(N) N是字符串长度,复杂度由最终返回长度决定,但由于通过一个字符串创建子字符串是很容易的,它可以被认为是O(1)。
警告:这个命令是被改成GETRANGE的,在小于2.0的Redis版本中叫SUBSTR。 返回key对应的字符串value的子串,这个子串是由start和end位移决定的(两者都在string内)。可以用负的位移来表示从string尾部开始数的下标。所以-1就是最后一个字符,-2就是倒数第二个,以此类推。
这个函数处理超出范围的请求时,都把结果限制在string内。
返回值
bulk-reply
例子

  1. redis> SET mykey "This is a string"
  2. OK
  3. redis> GETRANGE mykey 0 3
  4. "This"
  5. redis> GETRANGE mykey -3 -1
  6. "ing"
  7. redis> GETRANGE mykey 0 -1
  8. "This is a string"
  9. redis> GETRANGE mykey 10 100
  10. "string"
  11. redis>

GETSET key value

起始版本:1.0.0
时间复杂度:O(1)
自动将key对应到value并且返回原来key对应的value。如果key存在但是对应的value不是字符串,就返回错误。
设计模式
GETSET可以和INCR一起使用实现支持重置的计数功能。举个例子:每当有事件发生的时候,一段程序都会调用INCR给key mycounter加1,但是有时我们需要获取计数器的值,并且自动将其重置为0。这可以通过GETSET mycounter “0”来实现:

  1. INCR mycounter
  2. GETSET mycounter "0"
  3. GET mycounter

返回值

bulk-string-reply: 返回之前的旧值,如果之前Key不存在将返回nil

例子

  1. redis> INCR mycounter
  2. (integer) 1
  3. redis> GETSET mycounter "0"
  4. "1"
  5. redis> GET mycounter
  6. "0"
  7. redis>

BITCOUNT key [start end]

起始版本:2.6.0
时间复杂度:O(N)
统计字符串被设置为1的bit数.
一般情况下,给定的整个字符串都会被进行计数,通过指定额外的 start 或 end 参数,可以让计数只在特定的位上进行。
start 和 end 参数的设置和 GETRANGE 命令类似,都可以使用负数值:比如 -1 表示最后一个位,而 -2 表示倒数第二个位,以此类推。
不存在的 key 被当成是空字符串来处理,因此对一个不存在的 key 进行 BITCOUNT 操作,结果为 0 。

返回值

Integer reply
被设置为 1 的位的数量。

例子

  1. redis> SET mykey "foobar"
  2. OK
  3. redis> BITCOUNT mykey
  4. (integer) 26
  5. redis> BITCOUNT mykey 0 0
  6. (integer) 4
  7. redis> BITCOUNT mykey 1 1
  8. (integer) 6
  9. redis>

模式:使用 bitmap 实现用户上线次数统计
Bitmap 对于一些特定类型的计算非常有效。
假设现在我们希望记录自己网站上的用户的上线频率,比如说,计算用户 A 上线了多少天,用户 B 上线了多少天,诸如此类,以此作为数据,从而决定让哪些用户参加 beta 测试等活动 —— 这个模式可以使用 SETBITBITCOUNT 来实现。
比如说,每当用户在某一天上线的时候,我们就使用 SETBIT ,以用户名作为 key ,将那天所代表的网站的上线日作为 offset 参数,并将这个 offset 上的为设置为 1 。
举个例子,如果今天是网站上线的第 100 天,而用户 peter 在今天阅览过网站,那么执行命令 SETBIT peter 100 1 ;如果明天 peter 也继续阅览网站,那么执行命令 SETBIT peter 101 1 ,以此类推。
当要计算 peter 总共以来的上线次数时,就使用 BITCOUNT 命令:执行 BITCOUNT peter ,得出的结果就是 peter 上线的总天数。
更详细的实现可以参考博文 Fast, easy, realtime metrics using Redis bitmaps (需要翻墙)

性能

前面的上线次数统计例子,即使运行 10 年,占用的空间也只是每个用户 10*365 比特位(bit),也即是每个用户 456 字节。对于这种大小的数据来说, BITCOUNT 的处理速度就像 GETINCR 这种 O(1) 复杂度的操作一样快。
如果你的 bitmap 数据非常大,那么可以考虑使用以下两种方法:

  • 将一个大的 bitmap 分散到不同的 key 中,作为小的 bitmap 来处理。使用 Lua 脚本可以很方便地完成这一工作。
  • 使用 BITCOUNT 的 start 和 end 参数,每次只对所需的部分位进行计算,将位的累积工作(accumulating)放到客户端进行,并且对结果进行缓存 (caching)。

    BITFIELD key [GET type offset] [SET type offset value] [INCRBY type offset increment] [OVERFLOW WRAP|SAT|FAIL]

    起始版本:3.2.0
    时间复杂度:O(1) for each subcommand specified
    本命令会把Redis字符串当作位数组,并能对变长位宽和任意未字节对齐的指定整型位域进行寻址。在实践中,可以使用该命令对一个有符号的5位整型数的1234位设置指定值,也可以对一个31位无符号整型数的4567位进行取值。类似地,在对指定的整数进行自增和自减操作,本命令可以提供有保证的、可配置的上溢和下溢处理操作。
    BITFIELD命令能操作多字节位域,它会执行一系列操作,并返回一个响应数组,在参数列表中每个响应数组匹配相应的操作。
    例如,下面的命令是对一个8位有符号整数偏移100位自增1,并获取4位无符号整数的值:
    1. > BITFIELD mykey INCRBY i5 100 1 GET u4 0
    2. 1) (integer) 1
    3. 2) (integer) 0
    提示:
  1. GET指令对超出当前字符串长度的位(含key不存在的情况)进行寻址,执行操作的结果会对缺失部分的位(bits)赋值为0。
  2. SETINCRBY指令对超出当前字符串长度的位(含key不存在的情况)进行寻址,将会扩展字符串并对扩展部分进行补0,扩展方式包括:按需扩展、按最小长度扩展和按最大寻址能力扩展。

    支持子命令和整型

    下面是已支持的命令列表:
  • GET <type> <offset> – 返回指定的位域
  • SET <type> <offset> <value> – 设置指定位域的值并返回它的原值
  • INCRBY <type> <offset> <increment> – 自增或自减(如果increment为负数)指定位域的值并返回它的新值

还有一个命令通过设置溢出行为来改变调用INCRBY指令的后序操作:

  • OVERFLOW [WRAP|SAT|FAIL]

当需要一个整型时,有符号整型需在位数前加i,无符号在位数前加u。例如,u8是一个8位的无符号整型,i16是一个16位的有符号整型。
有符号整型最大支持64位,而无符号整型最大支持63位。对无符号整型的限制,是由于当前Redis协议不能在响应消息中返回64位无符号整数。

位和位偏移

bitfield命令有两种方式来指定位偏移。如果未定带数字的前缀,将会以字符串的第0位作为起始位。
不过,如果偏移量带有#前缀,那么指定的偏移量需要乘以整型宽度,例如:

  1. BITFIELD mystring SET i8 #0 100 i8 #1 200

将会在第1个i8整数的偏移0位和第2个整数的偏移8位进行设值。如果想得到一个给定长度的普通整型数组,则不一定要在客户端进行计算。

溢出控制

使用OVERFLOW命令,用户可以通过指定下列其中一种行为来调整自增或自减操作溢出(或下溢)后的行为:

  • WRAP: 回环算法,适用于有符号和无符号整型两种类型。对于无符号整型,回环计数将对整型最大值进行取模操作(C语言的标准行为)。对于有符号整型,上溢从最负的负数开始取数,下溢则从最大的正数开始取数,例如,如果i8整型的值设为127,自加1后的值变为-128。
  • SAT: 饱和算法,下溢之后设为最小的整型值,上溢之后设为最大的整数值。例如,i8整型的值从120开始加10后,结果是127,继续增加,结果还是保持为127。下溢也是同理,但量结果值将会保持在最负的负数值。
  • FAIL: 失败算法,这种模式下,在检测到上溢或下溢时,不做任何操作。相应的返回值会设为NULL,并返回给调用者。

注意每种溢出(OVERFLOW)控制方法,仅影响紧跟在INCRBY命令后的子命令,直到重新指定溢出(OVERFLOW)控制方法。
如果没有指定溢出控制方法,默认情况下,将使用WRAP算法。

  1. > BITFIELD mykey incrby u2 100 1 OVERFLOW SAT incrby u2 102 1
  2. 1) (integer) 1
  3. 2) (integer) 1
  4. > BITFIELD mykey incrby u2 100 1 OVERFLOW SAT incrby u2 102 1
  5. 1) (integer) 2
  6. 2) (integer) 2
  7. > BITFIELD mykey incrby u2 100 1 OVERFLOW SAT incrby u2 102 1
  8. 1) (integer) 3
  9. 2) (integer) 3
  10. > BITFIELD mykey incrby u2 100 1 OVERFLOW SAT incrby u2 102 1
  11. 1) (integer) 0
  12. 2) (integer) 3

返回值

本命令返回一个针对子命令给定位置的处理结果组成的数组。OVERFLOW子命令在响应消息中,不会统计结果的条数。
下面是OVERFLOW FAIL返回NULL的样例:

  1. > BITFIELD mykey OVERFLOW FAIL incrby u2 102 1
  2. 1) (nil)

动机(Motivations)

本命令的动机是为了能够在单个大位图(large bitmap)中高效地存储多个小整数(或对键分成多个key,避免出现超大键),同时开放Redis提供的新使用案例,尤其是在实时分析领域。这种使用案例可以通过指定的溢出控制方法来支持。

性能考虑(Performance considerations)

通常,BITFIELD是一个非常快的命令,但是注意,对短字符串的远地址(fat bits)寻址,将会比在存在的位执行命令更加耗时。

字节序(Orders of bits)

BITFIELD命令使用的位图表现形式,可看作是从0位开始的,例如:把一个5位的无符号整数23,对一个所有位事先置0的位图,从第7位开始赋值,其结果如下所示:

  1. +--------+--------+
  2. |00000001|01110000|
  3. +--------+--------+

当偏移量和整型大小是字节边界对齐时,此时与大端模式(big endian)相同,但是,当字节边界未对齐时,那么理解字节序将变得非常重要。

BITOP operation destkey key [key …]

起始版本:2.6.0
时间复杂度:O(N)
对一个或多个保存二进制位的字符串 key 进行位元操作,并将结果保存到 destkey 上。
BITOP 命令支持 AND 、 OR 、 NOT 、 XOR 这四种操作中的任意一种参数:

  • BITOP AND destkey srckey1 srckey2 srckey3 ... srckeyN ,对一个或多个 key 求逻辑并,并将结果保存到 destkey 。
  • BITOP OR destkey srckey1 srckey2 srckey3 ... srckeyN,对一个或多个 key 求逻辑或,并将结果保存到 destkey 。
  • BITOP XOR destkey srckey1 srckey2 srckey3 ... srckeyN,对一个或多个 key 求逻辑异或,并将结果保存到 destkey 。
  • BITOP NOT destkey srckey,对给定 key 求逻辑非,并将结果保存到 destkey 。

除了 NOT 操作之外,其他操作都可以接受一个或多个 key 作为输入。
执行结果将始终保持到destkey里面。

处理不同长度的字符串

当 BITOP 处理不同长度的字符串时,较短的那个字符串所缺少的部分会被看作 0 。
空的 key 也被看作是包含 0 的字符串序列。

返回值

Integer reply
保存到 destkey 的字符串的长度,和输入 key 中最长的字符串长度相等。

例子

  1. redis> SET key1 "foobar"
  2. OK
  3. redis> SET key2 "abcdef"
  4. OK
  5. redis> BITOP AND dest key1 key2
  6. (integer) 6
  7. redis> GET dest
  8. "`bc`ab"
  9. redis>

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

BITOP是对BITCOUNT命令一个很好的补充。
不同的bitmaps进行组合操作可以获得目标bitmap以进行人口统计操作。
Fast easy realtime metrics using Redis bitmaps这篇文章介绍了一个有趣的用例。

性能

BITOP可能是一个缓慢的命令,它的时间复杂度是O(N)。 在处理长字符串时应注意一下效率问题。
对于实时的指标和统计,涉及大输入一个很好的方法是 使用bit-wise操作以避免阻塞主实例。

BITPOS key bit [start] [end]

起始版本:2.8.7
时间复杂度:O(N)
返回字符串里面第一个被设置为1或者0的bit位。
返回一个位置,把字符串当做一个从左到右的字节数组,第一个符合条件的在位置0,其次在位置8,等等。
GETBITSETBIT 相似的也是操作字节位的命令。
默认情况下整个字符串都会被检索一次,只有在指定start和end参数(指定start和end位是可行的),该范围被解释为一个字节的范围,而不是一系列的位。所以start=0 并且 end=2是指前三个字节范围内查找。
注意,返回的位的位置始终是从0开始的,即使使用了start来指定了一个开始字节也是这样。
GETRANGE命令一样,start和end也可以包含负值,负值将从字符串的末尾开始计算,-1是字符串的最后一个字节,-2是倒数第二个,等等。
不存在的key将会被当做空字符串来处理。

返回值

Integer reply
命令返回字符串里面第一个被设置为1或者0的bit位。
如果我们在空字符串或者0字节的字符串里面查找bit为1的内容,那么结果将返回-1。
如果我们在字符串里面查找bit为0而且字符串只包含1的值时,将返回字符串最右边的第一个空位。如果有一个字符串是三个字节的值为0xff的字符串,那么命令BITPOS key 0将会返回24,因为0-23位都是1。
基本上,我们可以把字符串看成右边有无数个0。
然而,如果你用指定start和end范围进行查找指定值时,如果该范围内没有对应值,结果将返回-1。

例子

  1. redis> SET mykey "\xff\xf0\x00"
  2. OK
  3. redis> BITPOS mykey 0 # 查找字符串里面bit值为0的位置
  4. (integer) 12
  5. redis> SET mykey "\x00\xff\xf0"
  6. OK
  7. redis> BITPOS mykey 1 0 # 查找字符串里面bit值为1从第0个字节开始的位置
  8. (integer) 8
  9. redis> BITPOS mykey 1 2 # 查找字符串里面bit值为1从第2个字节(12)开始的位置
  10. (integer) 16
  11. redis> set mykey "\x00\x00\x00"
  12. OK
  13. redis> BITPOS mykey 1 # 查找字符串里面bit值为1的位置
  14. (integer) -1
  15. redis>

DECR key

起始版本:1.0.0
时间复杂度:O(1)
对key对应的数字做减1操作。如果key不存在,那么在操作之前,这个key对应的值会被置为0。如果key有一个错误类型的value或者是一个不能表示成数字的字符串,就返回错误。这个操作最大支持在64位有符号的整型数字。
查看命令INCR了解关于增减操作的额外信息。

返回值

数字:减小之后的value

例子

  1. redis> SET mykey "10"
  2. OK
  3. redis> DECR mykey
  4. (integer) 9
  5. redis> SET mykey "234293482390480948029348230948"
  6. OK
  7. redis> DECR mykey
  8. ERR value is not an integer or out of range
  9. redis>

DECRBY key decrement

起始版本:1.0.0
时间复杂度:O(1)
将key对应的数字减decrement。如果key不存在,操作之前,key就会被置为0。如果key的value类型错误或者是个不能表示成数字的字符串,就返回错误。这个操作最多支持64位有符号的正型数字。
查看命令INCR了解关于增减操作的额外信息。似。

返回值

返回一个数字:减少之后的value值。

例子

  1. redis> SET mykey "10"
  2. OK
  3. redis> DECRBY mykey 5
  4. (integer) 5
  5. redis>

INCR key

起始版本:1.0.0
时间复杂度:O(1)
对存储在指定key的数值执行原子的加1操作。
如果指定的key不存在,那么在执行incr操作之前,会先将它的值设定为0
如果指定的key中存储的值不是字符串类型(fix:)或者存储的字符串类型不能表示为一个整数,
那么执行这个命令时服务器会返回一个错误(eq:(error) ERR value is not an integer or out of range)。
这个操作仅限于64位的有符号整型数据。
注意: 由于redis并没有一个明确的类型来表示整型数据,所以这个操作是一个字符串操作。
执行这个操作的时候,key对应存储的字符串被解析为10进制的64位有符号整型数据
事实上,Redis 内部采用整数形式(Integer representation)来存储对应的整数值,所以对该类字符串值实际上是用整数保存,也就不存在存储整数的字符串表示(String representation)所带来的额外消耗。

返回值

integer-reply:执行递增操作后key对应的值。

例子

  1. redis> SET mykey "10"
  2. OK
  3. redis> INCR mykey
  4. (integer) 11
  5. redis> GET mykey
  6. "11"
  7. redis>

实例:计数器

Redis的原子递增操作最常用的使用场景是计数器。
使用思路是:每次有相关操作的时候,就向Redis服务器发送一个incr命令。
例如这样一个场景:我们有一个web应用,我们想记录每个用户每天访问这个网站的次数。
web应用只需要通过拼接用户id和代表当前时间的字符串作为key,每次用户访问这个页面的时候对这个key执行一下incr命令。
这个场景可以有很多种扩展方法:

  • 通过结合使用INCREXPIRE命令,可以实现一个只记录用户在指定间隔时间内的访问次数的计数器
  • 客户端可以通过GETSET命令获取当前计数器的值并且重置为0
  • 通过类似于DECR或者INCRBY等原子递增/递减的命令,可以根据用户的操作来增加或者减少某些值 比如在线游戏,需要对用户的游戏分数进行实时控制,分数可能增加也可能减少。

    实例: 限速器

    限速器是一种可以限制某些操作执行速率的特殊场景。
    传统的例子就是限制某个公共api的请求数目。
    假设我们要解决如下问题:限制某个api每秒每个ip的请求次数不超过10次。
    我们可以通过incr命令来实现两种方法解决这个问题。

    实例: 限速器 1

    更加简单和直接的实现如下:

    1. FUNCTION LIMIT_API_CALL(ip)
    2. ts = CURRENT_UNIX_TIME()
    3. keyname = ip+":"+ts
    4. current = GET(keyname)
    5. IF current != NULL AND current > 10 THEN
    6. ERROR "too many requests per second"
    7. ELSE
    8. MULTI
    9. INCR(keyname,1)
    10. EXPIRE(keyname,10)
    11. EXEC
    12. PERFORM_API_CALL()
    13. END

    这种方法的基本点是每个ip每秒生成一个可以记录请求数的计数器。
    但是这些计数器每次递增的时候都设置了10秒的过期时间,这样在进入下一秒之后,redis会自动删除前一秒的计数器。
    注意上面伪代码中我们用到了MULTIEXEC命令,将递增操作和设置过期时间的操作放在了一个事务中, 从而保证了两个操作的原子性。

    实例: 限速器 2

    另外一个实现是对每个ip只用一个单独的计数器(不是每秒生成一个),但是需要注意避免竟态条件。 我们会对多种不同的变量进行测试。

    1. FUNCTION LIMIT_API_CALL(ip):
    2. current = GET(ip)
    3. IF current != NULL AND current > 10 THEN
    4. ERROR "too many requests per second"
    5. ELSE
    6. value = INCR(ip)
    7. IF value == 1 THEN
    8. EXPIRE(value,1)
    9. END
    10. PERFORM_API_CALL()
    11. END

    上述方法的思路是,从第一个请求开始设置过期时间为1秒。如果1秒内请求数超过了10个,那么会抛异常。
    否则,计数器会清零。
    上述代码中,可能会进入竞态条件,比如客户端在执行INCR之后,没有成功设置EXPIRE时间。这个ip的key 会造成内存泄漏,直到下次有同一个ip发送相同的请求过来。
    把上述INCR和EXPIRE命令写在lua脚本并执行EVAL命令可以避免上述问题(只有redis版本>=2.6才可以使用)

    1. local current
    2. current = redis.call("incr",KEYS[1])
    3. if tonumber(current) == 1 then
    4. redis.call("expire",KEYS[1],1)
    5. end

    还可以通过使用redis的list来解决上述问题避免进入竞态条件。
    实现代码更加复杂并且利用了一些redis的新的feature,可以记录当前请求的客户端ip地址。这个有没有好处 取决于应用程序本身。

    1. FUNCTION LIMIT_API_CALL(ip)
    2. current = LLEN(ip)
    3. IF current > 10 THEN
    4. ERROR "too many requests per second"
    5. ELSE
    6. IF EXISTS(ip) == FALSE
    7. MULTI
    8. RPUSH(ip,ip)
    9. EXPIRE(ip,1)
    10. EXEC
    11. ELSE
    12. RPUSHX(ip,ip)
    13. END
    14. PERFORM_API_CALL()
    15. END

    The RPUSHX command only pushes the element if the key already exists.
    RPUSHX命令会往list中插入一个元素,如果key存在的话
    上述实现也可能会出现竞态,比如我们在执行EXISTS指令之后返回了false,但是另外一个客户端创建了这个key。
    后果就是我们会少记录一个请求。但是这种情况很少出现,所以我们的请求限速器还是能够运行良好的。

    INCRBY key increment

    起始版本:1.0.0
    时间复杂度:O(1)
    将key对应的数字加decrement。如果key不存在,操作之前,key就会被置为0。如果key的value类型错误或者是个不能表示成数字的字符串,就返回错误。这个操作最多支持64位有符号的正型数字。
    查看命令INCR了解关于增减操作的额外信息。

    返回值

    integer-reply: 增加之后的value值。

    例子

    1. redis> SET mykey "10"
    2. OK
    3. redis> INCRBY mykey 5
    4. (integer) 15
    5. redis>

    INCRBYFLOAT key increment

    起始版本:2.6.0
    时间复杂度:O(1)
    通过指定浮点数key来增长浮点数(存放于string中)的值. 当键不存在时,先将其值设为0再操作.下面任一情况都会返回错误:

  • key 包含非法值(不是一个string).

  • 当前的key或者相加后的值不能解析为一个双精度的浮点值.(超出精度范围了)

如果操作命令成功, 相加后的值将替换原值存储在对应的键值上, 并以string的类型返回. string中已存的值或者相加参数可以任意选用指数符号,但相加计算的结果会以科学计数法的格式存储. 无论各计算的内部精度如何, 输出精度都固定为小数点后17位.

返回值

Bulk-string-reply: 当前key增加increment后的值。

例子

  1. redis> SET mykey 10.50
  2. OK
  3. redis> INCRBYFLOAT mykey 0.1
  4. "10.6"
  5. redis> SET mykey 5.0e3
  6. OK
  7. redis> INCRBYFLOAT mykey 2.0e2
  8. "5200"
  9. redis>

执行细节

该命令总是衍生为一个链接复制以及追加文件的set操作 , 所以底层浮点数的实现的差异并不是造成不一致的源头???

MGET key [key …]

起始版本:1.0.0
时间复杂度:O(N) where N is the number of keys to retrieve.
返回所有指定的key的value。对于每个不对应string或者不存在的key,都返回特殊值nil。正因为此,这个操作从来不会失败。

返回值

array-reply: 指定的key对应的values的list

例子

  1. redis> SET key1 "Hello"
  2. OK
  3. redis> SET key2 "World"
  4. OK
  5. redis> MGET key1 key2 nonexisting
  6. 1) "Hello"
  7. 2) "World"
  8. 3) (nil)
  9. redis>

MSET key value [key value …]

起始版本:1.0.1
时间复杂度:O(N) where N is the number of keys to set.
对应给定的keys到他们相应的values上。MSET会用新的value替换已经存在的value,就像普通的SET命令一样。如果你不想覆盖已经存在的values,请参看命令MSETNX
MSET是原子的,所以所有给定的keys是一次性set的。客户端不可能看到这种一部分keys被更新而另外的没有改变的情况。

返回值

simple-string-reply:总是OK,因为MSET不会失败。

例子

  1. redis> MSET key1 "Hello" key2 "World"
  2. OK
  3. redis> GET key1
  4. "Hello"
  5. redis> GET key2
  6. "World"
  7. redis>

MSETNX key value [key value …]

起始版本:1.0.1
时间复杂度:O(N) where N is the number of keys to set.
对应给定的keys到他们相应的values上。只要有一个key已经存在,MSETNX一个操作都不会执行。 由于这种特性,MSETNX可以实现要么所有的操作都成功,要么一个都不执行,这样可以用来设置不同的key,来表示一个唯一的对象的不同字段。
MSETNX是原子的,所以所有给定的keys是一次性set的。客户端不可能看到这种一部分keys被更新而另外的没有改变的情况。

返回值

integer-reply,只有以下两种值:

  • 1 如果所有的key被set
  • 0 如果没有key被set(至少其中有一个key是存在的)

    例子

    1. redis> MSETNX key1 "Hello" key2 "there"
    2. (integer) 1
    3. redis> MSETNX key2 "there" key3 "world"
    4. (integer) 0
    5. redis> MGET key1 key2 key3
    6. 1) "Hello"
    7. 2) "there"
    8. 3) (nil)
    9. redis>