格式:set key value
将字符串值 value 关联到 key 。 如果 key 已经持有其他值, SET 就覆写旧值,无视类型。
可用版本:
>=1.0.0
时间复杂度:
O(1)
返回值:
总是返回 OK ,因为 SET 不可能失败。
示例代码:
# 对字符串类型的 key 进行 SET
redis> SET apple www.apple.com
OK
redis> GET apple
"www.apple.com"
# 对非字符串类型的 key 进行 SET
redis> LPUSH greet_list "hello" # 建立一个列表
(integer) 1
redis> TYPE greet_list
list
redis> SET greet_list "yooooooooooooooooo" # 覆盖列表类型
OK
redis> TYPE greet_list
string
格式:setnx key value
将 key 的值设为 value ,当且仅当 key 不存在。 若给定的 key 已经存在,则 SETNX 不做任何动作。 SETNX 是『SET if Not eXists』(如果不存在,则 SET)的简写。
可用版本:
>=1.0.0
时间复杂度:
O(1)
返回值:
设置成功,返回 1 。 设置失败,返回 0 。
示例代码:
redis> EXISTS job # job 不存在
(integer) 0
redis> SETNX job "programmer" # job 设置成功
(integer) 1
redis> SETNX job "code-farmer" # 尝试覆盖 job ,失败
(integer) 0
redis> GET job # 没有被覆盖
"programmer"
模式:将SETNX用于加锁(locking)
警告:已经证实这个加锁算法带有竞争条件,在特定情况下会造成错误,请不要使用这个加锁算法。
SETNX可以用作加锁原语(lcking primitive)。比如说,要对关键字(key)foo加锁,客户端可以尝试以下方式: SETNX lock.foo 如果 SETNX 返回 1 ,说明客户端已经获得了锁,key设置的unix时间则制定了锁失效的时间。之后客户端可以通过 DEL lock.foo 来释放锁。 如果 SETNX 返回 0 ,说明key已经被其他客户端上锁了。如果锁是非阻塞(non blocking lock)的,我们可以选择返回调用,或者进入一个重试循环,直到成功获得锁或重试超时(timeout)。
处理死锁(deadlock)
上面的锁算法有一个问题:如果因为客户端失败、崩溃或其他原因导致没有办法释放锁的话,怎么办? 这种状况可以通过检测发现——因为上锁的 key 保存的是 unix 时间戳,假如 key 值的时间戳小于当前的时间戳,表示锁已经不再有效。 但是,当有多个客户端同时检测一个锁是否过期并尝试释放它的时候,我们不能简单粗暴地删除死锁的 key ,再用 SETNX 上锁,因为这时竞争条件(race condition)已经形成了: 1.C1 和 C2 读取 lock.foo 并检查时间戳,SETNX 都返回 0 ,因为它已经被 C3 锁上了,但 C3 在上锁之后就崩溃(crashed)了。 2.C1 向 lock.foo 发送 DEL 命令。 3.C1 向 lock.foo 发送 SETNX 并成功。 4.C2 向 lock.foo 发送 DEL 命令。 5.C2 向 lock.foo 发送 SETNX 并成功。 6.出错:因为竞争条件的关系,C1 和 C2 两个都获得了锁。幸好,以下算法可以避免以上问题。来看看我们聪明的 C4 客户端怎么办: 7.C4 向 lock.foo 发送 SETNX 命令。 8.因为崩溃掉的 C3 还锁着 lock.foo ,所以 Redis 向 C4 返回 0 。 9.C4 向 lock.foo 发送 GET 命令,查看 lock.foo 的锁是否过期。如果不,则休眠(sleep)一段时间,并在之后重试。 10.另一方面,如果 lock.foo 内的 unix 时间戳比当前时间戳老,C4 执行以下命令: GETSET lock.foo 11.因为 GETSET 的作用,C4 可以检查看 GETSET 的返回值,确定 lock.foo 之前储存的旧值仍是那个过期时间戳,如果是的话,那么 C4 获得锁。 12.如果其他客户端,比如 C5,比 C4 更快地执行了 GETSET 操作并获得锁,那么 C4的 GETSET 操作返回的就是一个未过期的时间戳(C5 设置的时间戳)。C4 只好从第 一步开始重试。 注意,即便 C4 的 GETSET 操作对 key 进行了修改,这对未来也没什么影响。
警告:为了让这个加锁算法更健壮,获得锁的客户端应该常常检查过期时间以免锁因诸如 DEL 等命令的执行而被意外解开,因为客户端失败的情况非常复杂,不仅仅是崩溃这么简单,还可能是客户端因为某些操作被阻塞了相当长时间,紧接着 DEL 命令被尝试执行(但这时锁却在另外的客户端手上)。
格式:setex key seconds value
将值 value 关联到 key ,并将 key 的生存时间设为seconds (以秒为单位)。 如果 key 已经存在, SETEX 命令将覆写旧值。 这个命令类似于以下两个命令:
SET key value
EXPIRE key seconds # 设置生存时间
不同之处是, SETEX 是一个原子性(atomic)操作,关联值和设置生存时间两个动作会在同一时间内完成,该命令在 Redis 用作缓存时,非常实用。
可用版本:
>=2.0.0
时间复杂度:
O(1)
返回值:
设置成功时返回 OK 。 当 seconds 参数不合法时,返回一个错误。
示例代码:
# 在 key 不存在时进行 SETEX
redis> SETEX cache_user_id 60 10086
OK
redis> GET cache_user_id # 值
"10086"
redis> TTL cache_user_id # 剩余生存时间
(integer) 49
# key 已经存在时,SETEX 覆盖旧值
redis> SET cd "timeless"
OK
redis> SETEX cd 3000 "goodbye my love"
OK
redis> GET cd
"goodbye my love"
redis> TTL cd
(integer) 2997
格式:psetex key milliseconds value
这个命令和 SETEX 命令相似,但它以毫秒为单位设置key的生存时间,而不是像SETEX 命令那样,以秒为单位。
可用版本:
>=2.6.0
时间复杂度:
O(1)
返回值:
设置成功时返回 OK 。
示例代码:
redis> PSETEX mykey 1000 "Hello"
OK
redis> PTTL mykey
(integer) 999
redis> GET mykey
"Hello"
格式:setrange key offset value
用 value 参数覆写(overwrite)给定 key 所储存的字符串值,从偏移量 offset 开始。 不存在的 key 当作空白字符串处理。 SETRANGE 命令会确保字符串足够长以便将 value 设置在指定的偏移量上,如果给定key 原来储存的字符串长度比偏移量小(比如字符串只有 5 个字符长,但你设置的offset是 10 ),那么原字符和偏移量之间的空白将用零字节(zerobytes, “\x00” )来填充。 注意你能使用的最大偏移量是 2^29-1(536870911) ,因为 Redis 字符串的大小被限制在 512 兆(megabytes)以内。如果你需要使用比这更大的空间,你可以使用多个 key 。
警告:当生成一个很长的字符串时,Redis 需要分配内存空间,该操作有时候可能会造成服务器阻塞(block)。在2010 年的 Macbook Pro 上,设置偏移量为536870911(512MB 内存分配),耗费约 300 毫秒,设置偏移量为 134217728(128MB 内存分配),耗费约 80 毫秒,设置偏移量 33554432(32MB 内存分配),耗费约 30 毫秒,设置偏移量为 8388608(8MB 内存分配),耗费约 8 毫秒。注意若首次内存分配成功之后,再对同一个 key 调用 SETRANGE操作,无须再重新内存。
可用版本:
>=2.2.0
时间复杂度:
对小(small)的字符串,平摊复杂度 O(1)。(关于什么字符串是”小”的,请参考 APPEND命令) 否则为 O(M), M 为 value 参数的长度。
返回值:
被 SETRANGE 修改之后,字符串的长度。
示例代码:
# 对非空字符串进行 SETRANGE
redis> SET greeting "hello world"
OK
redis> SETRANGE greeting 6 "Redis"
(integer) 11
redis> GET greeting
"hello Redis"
# 对空字符串/不存在的 key 进行 SETRANGE
redis> EXISTS empty_string
(integer) 0
redis> SETRANGE empty_string 5 "Redis!" # 对不存在的 key 使 用
SETRANGE
(integer) 11
redis> GET empty_string # 空白处被"\x00"填充
"\x00\x00\x00\x00\x00Redis!"
因为有了 SETRANGE 和 GETRANGE 命令,你可以将 Redis 字符串用作具有 O(1)随机访问时间的线性数组,这在很多真实用例中都是非常快速且高效的储存方式,具体请参考APPEND 命令的『模式:时间序列』部分。
格式:mset key value [key value …]
同时设置一个或多个 key-value 对。 如果某个给定 key 已经存在,那么 MSET 会用新值覆盖原来的旧值,如果这不是你所希望的效果,请考虑使用 MSETNX 命令:它只会在所有给定 key 都不存在的情况下进行设置操作。 MSET 是一个原子性(atomic)操作,所有给定 key 都会在同一时间内被设置,某些给定key 被更新而另一些给定 key 没有改变的情况,不可能发生。
可用版本:
>=1.0.1
时间复杂度:
O(N), N 为要设置的 key 数量。
返回值:
总是返回 OK (因为 MSET 不可能失败)
示例代码:
redis> MSET date "2012.3.30" time "11:00 a.m." weather "sunny"
OK
redis> MGET date time weather
1) "2012.3.30"
2) "11:00 a.m."
3) "sunny"
# MSET 覆盖旧值例子
redis> SET google "google.hk"
OK
redis> MSET google "google.com"
OK
redis> GET google
"google.com"
格式:msetnx key value [key value …]
同时设置一个或多个 key-value 对,当且仅当所有给定key 都不存在。 即使只有一个给定 key 已存在, MSETNX 也会拒绝执行所有给定 key 的设置操作。 MSETNX 是原子性的,因此它可以用作设置多个不同 key 表示不同字段(field)的唯一性逻辑对象(unique logic object),所有字段要么全被设置,要么全不被设置。
可用版本:
>=1.0.1
时间复杂度:
O(N), N 为要设置的 key 数量。
返回值:
当所有 key 都成功设置,返回 1 。 如果所有给定 key 都设置失败(至少有一个 key 已经存在),那么返回 0 。
示例代码:
# 对不存在的 key 进行 MSETNX
redis> MSETNX rmdbs "MySQL" nosql "MongoDB" key-value-store "redis"
(integer) 1
redis> MGET rmdbs nosql key-value-store
1) "MySQL"
2) "MongoDB"
3) "redis"
# MSET 的给定 key 当中有已存在的 key
redis> MSETNX rmdbs "Sqlite" language "python" # rmdbs 键已经存在,操作失败
(integer) 0
redis> EXISTS language # 因为 MSET 是原子性操作,language 没有被设置
(integer) 0
redis> GET rmdbs # rmdbs 也没有被修改
"MySQL"
格式:append key value
如果 key 已经存在并且是一个字符串, APPEND 命令将 value 追加到 key 原来的值的末尾。 如果 key 不存在, APPEND 就简单地将给定 key 设为 value ,就像执行 SET keyvalue 一样。
可用版本:
>=2.0.0
时间复杂度:
平摊 O(1)
返回值:
追加 value 之后, key 中字符串的长度。
示例代码:
# 对不存在的 key 执行 APPEND
redis> EXISTS myphone # 确保 myphone 不存在
(integer) 0
redis> APPEND myphone "nokia" # 对不存在的 key 进行 APPEND ,等同
于 SET myphone "nokia"
(integer) 5 # 字符长度
# 对已存在的字符串进行 APPEN
redis> APPEND myphone " - 1110" # 长度从 5 个字符增加到 12 个字符
(integer) 12
redis> GET myphone
"nokia - 1110"
模式:时间序列(Time series)
APPEND 可以为一系列定长(fixed-size)数据(sample)提供一种紧凑的表示方式,通常 称之为时间序列。 每当一个新数据到达的时候,执行以下命令: APPEND timeseries “fixed-size sample” 然后可以通过以下的方式访问时间序列的各项属性: 1.STRLEN 给出时间序列中数据的数量 2.GETRANGE 可以用于随机访问。只要有相关的时间信息的话,我们就可以在 Redis2.6 中使用 Lua 脚本和GETRANGE 命令实现二分查找。 3.SETRANGE 可以用于覆盖或修改已存在的的时间序列。 这个模式的唯一缺陷是我们只能增长时间序列,而不能对时间序列进行缩短,因为Redis 目前还没有对字符串进行修剪(tirm)的命令,但是,不管怎么说,这个模式的储存方式还是可以节省下大量的空间。
注:可以考虑使用 UNIX 时间戳作为时间序列的键名,这样一来,可以避免单个 key 因为保存过大的时间序列而占用大量内存,另一方面,也可以节省下大量命名空间。
下面是一个时间序列的例子:
redis> APPEND ts "0043"
(integer) 4
redis> APPEND ts "0035"
(integer) 8
redis> GETRANGE ts 0 3
"0043"
redis> GETRANGE ts 4 7
"0035"
格式:get key
返回 key 所关联的字符串值。 如果 key 不存在那么返回特殊值 nil 。 假如 key 储存的值不是字符串类型,返回一个错误,因为 GET 只能用于处理字符串值。
可用版本:
>=1.0.0
时间复杂度:
O(1)
返回值:
当 key 不存在时,返回 nil ,否则,返回 key 的值。 如果 key 不是字符串类型,那么返回一个错误。
示例代码:
redis> GET db
(nil)
redis> SET db redis
OK
redis> GET db
"redis"
# 对不是字符串类型的 key 进行 GET
redis> DEL db
(integer) 1
redis> LPUSH db redis mongodb mysql
(integer) 3
redis> GET db
(error) ERR Operation against a key holding the wrong kind of value
格式:mget key [key …]
返回所有(一个或多个)给定 key 的值。 如果给定的 key 里面,有某个 key 不存在,那么这个key 返回特殊值 nil 。因此,该命令永不失败。
可用版本:
>=1.0.0
时间复杂度:
O(N) , N 为给定 key 的数量。
返回值:
一个包含所有给定 key 的值的列表。
示例代码:
redis> SET redis redis.com
OK
redis> SET mongodb mongodb.org
OK
redis> MGET redis mongodb
1) "redis.com"
2) "mongodb.org"
redis> MGET redis mongodb mysql # 不存在的 mysql 返回 nil
1) "redis.com"
2) "mongodb.org"
3) (nil)
格式:getrange key start end
返回 key 中字符串值的子字符串,字符串的截取范围由 start 和 end 两个偏移量决定(包括 start 和 end 在内)。 负数偏移量表示从字符串最后开始计数,-1 表示最后一个字符,-2 表示倒数第二个,以此类推。 GETRANGE 通过保证子字符串的值域(range)不超过实际字符串的值域来处理超出范围的值域请求。
注:在 <= 2.0 的版本里,GETRANGE 被叫作 SUBSTR。
可用版本:
>=2.4.0
时间复杂度:
O(N), N 为要返回的字符串的长度。 复杂度最终由字符串的返回值长度决定,但因为从已有字符串中取出子字符串的操作非常廉价(cheap),所以对于长度不大的字符串,该操作的复杂度也可看作 O(1)。
返回值:
截取得出的子字符串。
示例代码:
redis> SET greeting "hello, my friend"
OK
redis> GETRANGE greeting 0 4 # 返回索引 0-4 的字符,包括 4。
"hello"
redis> GETRANGE greeting -1 -5 # 不支持回绕操作
""
redis> GETRANGE greeting -3 -1 # 负数索引
"end"
redis> GETRANGE greeting 0 -1 # 从第一个到最后一个
"hello, my friend"
redis> GETRANGE greeting 0 1008611 # 值域范围不超过实际字符串,超过部分
自动被符略
"hello, my friend"
格式:getset key value
将给定 key 的值设为 value ,并返回 key 的旧值(old value)。 当 key 存在但不是字符串类型时,返回一个错误。
可用版本:
>=1.0.0
时间复杂度:
O(1)
返回值:
返回给定 key 的旧值。 当 key 没有旧值时,也即是, key 不存在时,返回nil。
示例代码:
redis> GETSET db mongodb # 没有旧值,返回 nil
(nil)
redis> GET db
"mongodb"
redis> GETSET db redis # 返回旧值 mongodb
"mongodb"
redis> GET db
"redis"
模式
GETSET 可以和 INCR 组合使用,实现一个有原子性(atomic)复位操作的计数器(counter)。 举例来说,每次当某个事件发生时,进程可能对一个名为mycount 的 key 调用 INCR操作,通常我们还要在一个原子时间内同时完成获得计数器的值和将计数器值复位为 0 两 个操作。
可以用命令 GETSET mycounter 0 来实现这一目标。
redis> INCR mycount
(integer) 11
redis> GETSET mycount 0 # 一个原子内完成 GET mycount 和 SET mycount 0 操作
"11"
redis> GET mycount # 计数器被重置
"0"
格式:strlen key
返回 key 所储存的字符串值的长度。 当 key 储存的不是字符串值时,返回一个错误。
可用版本:
>=2.2.0
时间复杂度:
O(1)
返回值:
字符串值的长度。 当 key 不存在时,返回 0 。
示例代码:
redis> SET mykey "Hello world"
OK
redis> STRLEN mykey
(integer) 11
# 不存在的 key 长度为 0
redis> STRLEN nonexisting
(integer) 0
格式:decr key
将 key 中储存的数字值减一。 如果 key 不存在,那么 key 的值会先被初始化为 0 ,然后再执行 DECR 操作。 如果值包含错误的类型,或字符串类型的值不能表示为数字,那么返回一个错误。 本操作的值限制在 64 位(bit)有符号数字表示之内。 关于递增(increment) / 递减(decrement)操作的更多信息,请参见 INCR 命令。
可用版本:
>=1.0.0
时间复杂度:
O(1)
返回值:
执行 DECR 命令之后 key 的值。
示例代码:
redis> SET failure_times 10
OK
redis> DECR failure_times
(integer) 9
# 对不存在的 key 值进行 DECR
redis> EXISTS count
(integer) 0
redis> DECR count
(integer) -1
# 对存在但不是数值的 key 进行 DECR
redis> SET company YOUR_CODE_SUCKS.LLC
OK
redis> DECR company
(error) ERR value is not an integer or out of range
格式:decrby key decrement
将 key 所储存的值减去减量 decrement 。 如果 key 不存在,那么 key 的值会先被初始化为 0 ,然后再执行 DECRBY 操作。 如果值包含错误的类型,或字符串类型的值不能表示为数字,那么返回一个错误。 本操作的值限制在 64 位(bit)有符号数字表示之内。 关于更多递增(increment) / 递减(decrement)操作的更多信息,请参见 INCR 命令。
可用版本:
>=1.0.0
时间复杂度:
O(1)
返回值:
减去 decrement 之后, key 的值。
示例代码:
# 对已存在的 key 进行 DECRBY
redis> SET count 100
OK
redis> DECRBY count 20
(integer) 80
# 对不存在的 key 进行 DECRBY
redis> EXISTS pages
(integer) 0
redis> DECRBY pages 10
(integer) -10
格式:incr key
将 key 中储存的数字值增一。 如果 key 不存在,那么 key 的值会先被初始化为 0 ,然后再执行 INCR 操作。 如果值包含错误的类型,或字符串类型的值不能表示为数字,那么返回一个错误。 本操作的值限制在 64 位(bit)有符号数字表示之内。
注:这是一个针对字符串的操作,因为 Redis 没有专用的整数类型,所以 key 内储存的字符串被解释为十进制 64 位有符号整数来执行 INCR 操作。
可用版本:
>=1.0.0
时间复杂度:
O(1)
返回值:
执行 INCR 命令之后 key 的值。
示例代码:
redis> SET page_view 20
OK
redis> INCR page_view
(integer) 21
redis> GET page_view # 数字值在 Redis 中以字符串的形式保存
"21"
模式:计数器
计数器是 Redis 的原子性自增操作可实现的最直观的模式了,它的想法相当简单:每当某个操作发生时,向 Redis 发送一个 INCR 命令。
比如在一个 web 应用程序中,如果想知道用户在一年中每天的点击量,那么只要将用户 ID 以及相关的日期信息作为键,并在每次用户点击页面时,执行一次自增操作即可。
比如用户名是 peter ,点击时间是 2012 年 3 月 22 日,那么执行命令: INCR peter::2012.3.22 。 可以用以下几种方式扩展这个简单的模式: 1.可以通过组合使用 INCR 和 EXPIRE ,来达到只在规定的生存时间内进行计数(counting)的目的。 2.客户端可以通过使用 GETSET 命令原子性地获取计数器的当前值并将计数器清零,更多信息请参考 GETSET 命令。 3.使用其他自增/自减操作,比如 DECR 和 INCRBY ,用户可以通过执行不同的操作增加或减少计数器的值,比如在游戏中的记分器就可能用到这些命令。
模式:限速器
限速器是特殊化的计算器,它用于限制一个操作可以被执行的速率(rate)。 限速器的典型用法是限制公开 API 的请求次数,以下是一个限速器实现示例,它将 API的最大请求数限制在每个 IP 地址每秒钟十个之内:
FUNCTION LIMIT_API_CALL(ip)
ts = CURRENT_UNIX_TIME()
keyname = ip+":"+ts
current = GET(keyname)
IF current != NULL AND current > 10 THEN
ERROR "too many requests per second"
END
IF current == NULL THEN
MULTI
INCR(keyname, 1)
EXPIRE(keyname, 1)
EXEC
ELSE
INCR(keyname, 1)
END
PERFORM_API_CALL()
这个实现每秒钟为每个 IP 地址使用一个不同的计数器,并用 EXPIRE 命令设置生存时间(这样 Redis 就会负责自动删除过期的计数器)。 注意,我们使用事务打包执行 INCR 命令和 EXPIRE 命令,避免引入竞争条件,保证每次调用 API 时都可以正确地对计数器进行自增操作并设置生存时间。
以下是另一个限速器实现:
FUNCTION LIMIT_API_CALL(ip):
current = GET(ip)
IF current != NULL AND current > 10 THEN
ERROR "too many requests per second"
ELSE
value = INCR(ip)
IF value == 1 THEN
EXPIRE(ip,1)
END
PERFORM_API_CALL()
END
这个限速器只使用单个计数器,它的生存时间为一秒钟,如果在一秒钟内,这个计数器的值大于 10 的话,那么访问就会被禁止。
这个新的限速器在思路方面是没有问题的,但它在实现方面不够严谨,如果我们仔细观察一下的话,就会发现在 INCR 和 EXPIRE 之间存在着一个竞争条件,假如客户端在执行INCR 之后,因为某些原因(比如客户端失败)而忘记设置 EXPIRE 的话,那么这个计数器就会一直存在下去,造成每个用户只能访问 10 次,噢,这简直是个灾难!
要消灭这个实现中的竞争条件,我们可以将它转化为一个 Lua 脚本,并放到 Redis 中运行(这个方法仅限于 Redis 2.6 及以上的版本):
local current
current = redis.call("incr",KEYS[1])
if tonumber(current) == 1 then
redis.call("expire",KEYS[1],1)
end
通过将计数器作为脚本放到 Redis 上运行,我们保证了 INCR 和 EXPIRE 两个操作的原子性,现在这个脚本实现不会引入竞争条件,它可以运作的很好。
关于在 Redis 中运行 Lua 脚本的更多信息,请参考 EVAL 命令。
还有另一种消灭竞争条件的方法,就是使用 Redis 的列表结构来代替 INCR 命令,这个方法无须脚本支持,因此它在 Redis 2.6 以下的版本也可以运行得很好:
FUNCTION LIMIT_API_CALL(ip)
current = LLEN(ip)
IF current > 10 THEN
ERROR "too many requests per second"
ELSE
IF EXISTS(ip) == FALSE
MULTI
RPUSH(ip,ip)
EXPIRE(ip,1)
EXEC
ELSE
RPUSHX(ip,ip)
END
PERFORM_API_CALL()
END
新的限速器使用了列表结构作为容器, LLEN 用于对访问次数进行检查,一个事务包裹着 RPUSH 和 EXPIRE 两个命令,用于在第一次执行计数时创建列表,并正确设置地设置过期时间,最后, RPUSHX 在后续的计数操作中进行增加操作。
格式:incrby key increment
将 key 所储存的值加上增量 increment 。 如果 key 不存在,那么 key 的值会先被初始化为 0 ,然后再执行 INCRBY 命令。 如果值包含错误的类型,或字符串类型的值不能表示为数字,那么返回一个错误。 本操作的值限制在 64 位(bit)有符号数字表示之内。 关于递增(increment) / 递减(decrement)操作的更多信息,参见 INCR 命令。
可用版本:
>=1.0.0
时间复杂度:
O(1)
返回值:
加上 increment 之后, key 的值。
示例代码:
redis> SET rank 50
OK
redis> INCRBY rank 20
(integer) 70
redis> GET rank
"70"
# key 不存在时
redis> EXISTS counter
(integer) 0
redis> INCRBY counter 30
(integer) 30
redis> GET counter
"30"
# key 不是数字值时
redis> SET book "long long ago..."
OK
redis> INCRBY book 200
(error) ERR value is not an integer or out of range
格式:incrbyfloat key increment
为 key 中所储存的值加上浮点数增量 increment 。
如果 key 不存在,那么 INCRBYFLOAT 会先将 key 的值设为 0 ,再执行加法操作。
如果命令执行成功,那么 key 的值会被更新为(执行加法之后的)新值,并且新值会以字符串的形式返回给调用者。
无论是 key 的值,还是增量 increment ,都可以使用像2.0e7 、 3e5 、 90e-2 那样的指数符号(exponential notation)来表示,但是,执行 INCRBYFLOAT 命令之后的值总是以同样的形式储存,也即是,它们总是由一个数字,一个(可选的)小数点和一个任意位的小数部分组成(比如 3.14 、 69.768 ,诸如此类),小数部分尾随的 0 会被移除,如果有需要的话,还会将浮点数改为整数(比如 3.0 会被保存成 3 )。
除此之外,无论加法计算所得的浮点数的实际精度有多长, INCRBYFLOAT 的计算结果也最多只能表示小数点的后十七位。
当以下任意一个条件发生时,返回一个错误: 1.key 的值不是字符串类型(因为 Redis 中的数字和浮点数都以字符串的形式保存,所以它们都属于字符串类型) 2.key 当前的值或者给定的增量 increment 不能解释(parse)为双精度浮点数(double precision floating point number)
可用版本:
>=2.6.0
时间复杂度:
O(1)
返回值:
执行命令之后 key 的值。
示例代码:
# 值和增量都不是指数符号
redis> SET mykey 10.50
OK
redis> INCRBYFLOAT mykey 0.1
"10.6"
# 值和增量都是指数符号
redis> SET mykey 314e-2
OK
redis> GET mykey # 用 SET 设置的值可以是指数符号
"314e-2"
redis> INCRBYFLOAT mykey 0 #但执行 INCRBYFLOAT 之后格式会被改成非指数
符号
"3.14"
# 可以对整数类型执行
redis> SET mykey 3
OK
redis> INCRBYFLOAT mykey 1.1
"4.1"
# 后跟的 0 会被移除
redis> SET mykey 3.0
OK
redis> GET mykey # SET 设置的值小数部分
可以是 0
"3.0"
redis> INCRBYFLOAT mykey 1.000000000000000000000 # 但 INCRBYFLOAT
会将无用的 0 忽略掉,有需要的话,将浮点变为整数
"4"
redis> GET mykey
"4"
格式:setbit key offset value
对 key 所储存的字符串值,设置或清除指定偏移量上的位(bit)。
位的设置或清除取决于 value 参数,可以是 0 也可以是 1 。
当 key 不存在时,自动生成一个新的字符串值。
字符串会进行伸展(grown)以确保它可以将 value 保存在指定的偏移量上。当字符串值进行伸展时,空白位置以 0 填充。
offset 参数必须大于或等于 0 ,小于 2^32 (bit 映射被限制在 512 MB 之内)。
警告:对使用大的 offset 的 SETBIT 操作来说,内存分配可能造成 Redis 服务器被阻塞。具体参考 SETRANGE 命令,warning(警告)部分。
可用版本:
>=2.2.0
时间复杂度:
O(1)
返回值:
指定偏移量原来储存的位。
示例代码:
redis> SETBIT bit 10086 1
(integer) 0
redis> GETBIT bit 10086
(integer) 1
redis> GETBIT bit 100 # bit 默认被初始化为 0
(integer) 0
格式:getbit key offset
对 key 所储存的字符串值,获取指定偏移量上的位(bit)。 当 offset 比字符串值的长度大,或者 key 不存在时,返回 0 。
可用版本:
>=2.2.0
时间复杂度:
O(1)
返回值:
字符串值指定偏移量上的位(bit)。
示例代码:
# 对不存在的 key 或者不存在的 offset 进行 GETBIT, 返回 0
redis> EXISTS bit
(integer) 0
redis> GETBIT bit 10086
(integer) 0
# 对已存在的 offset 进行 GETBIT
redis> SETBIT bit 10086 1
(integer) 0
redis> GETBIT bit 10086
(integer) 1
格式:bitop operation destkey key [key …]
对一个或多个保存二进制位的字符串 key 进行位元操作,并将结果保存到 destkey 上。
operation 可以是 AND 、 OR 、 NOT 、 XOR 这四种操作中的任意一种: 1.BITOP AND destkey key [key …] ,对一个或多个 key 求逻辑并,并将结果保存到 destkey 。 2.BITOP OR destkey key [key …] ,对一个或多个 key 求逻辑或,并将结果保存 到 destkey 。 3.BITOP XOR destkey key [key …] ,对一个或多个 key 求逻辑异或,并将结果 保存到 destkey 。 4.BITOP NOT destkey key ,对给定 key 求逻辑非,并将结果保存到 destkey 。
除了 NOT 操作之外,其他操作都可以接受一个或多个 key 作为输入。
处理不同长度的字符串
当 BITOP 处理不同长度的字符串时,较短的那个字符串所缺少的部分会被看作 0 。 空的 key 也被看作是包含 0 的字符串序列。
可用版本:
>=2.6.0
时间复杂度:
O(N)
返回值:
保存到 destkey 的字符串的长度,和输入 key 中最长的字符串长度相等。
注:BITOP 的复杂度为 O(N) ,当处理大型矩阵(matrix)或者进行大数据量的统计时,最好将任务指派到附属节点(slave)进行,避免阻塞主节点。
示例代码:
redis> SETBIT bits-1 0 1 # bits-1 = 1001
(integer) 0
redis> SETBIT bits-1 3 1
(integer) 0
redis> SETBIT bits-2 0 1 # bits-2 = 1011
(integer) 0
redis> SETBIT bits-2 1 1
(integer) 0
redis> SETBIT bits-2 3 1
(integer) 0
redis> BITOP AND and-result bits-1 bits-2
(integer) 1
redis> GETBIT and-result 0 # and-result = 1001
(integer) 1
redis> GETBIT and-result 1
(integer) 0
redis> GETBIT and-result 2
(integer) 0
redis> GETBIT and-result 3
(integer) 1
格式:bitcount key [start] [end]
计算给定字符串中,被设置为 1 的比特位的数量。
一般情况下,给定的整个字符串都会被进行计数,通过指定额外的 start 或 end 参数,可以让计数只在特定的位上进行。
start 和 end 参数的设置和 GETRANGE 命令类似,都可以使用负数值:比如 -1 表示最后一个位,而 -2 表示倒数第二个位,以此类推。
不存在的 key 被当成是空字符串来处理,因此对一个不存在的 key 进行 BITCOUNT 操作,结果为 0 。
可用版本:
>=2.6.0
时间复杂度:
O(N)
返回值:
被设置为 1 的位的数量。
示例代码:
redis> BITCOUNT bits
(integer) 0
redis> SETBIT bits 0 1 # 0001
(integer) 0
redis> BITCOUNT bits
(integer) 1
redis> SETBIT bits 3 1 # 1001
(integer) 0
redis> BITCOUNT bits
(integer) 2
模式:使用bitmap实现用户上线次数统计
Bitmap 对于一些特定类型的计算非常有效。
假设现在我们希望记录自己网站上的用户的上线频率,比如说,计算用户 A 上线了多少天,用户 B 上线了多少天,诸如此类,以此作为数据,从而决定让哪些用户参加 beta 测试等活动 —— 这个模式可以使用 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 数据非常大,那么可以考虑使用以下两种方法: 1.将一个大的 bitmap 分散到不同的 key 中,作为小的 bitmap 来处理。使用 Lua脚本可以很方便地完成这一工作。 2.使用 BITCOUNT 的 start 和 end 参数,每次只对所需的部分位进行计算,将位的累积工作(accumulating)放到客户端进行,并且对结果进行缓存 (caching)