全局命令

查看所有键

1
2
# 查看所有键,支持 glob 风格通配符
keys *

这个命令会不加区别地,做全局的键扫描,返回且只返回键。它的时间复杂度是O(N),线上环境因为无法预测键的数量,应该禁用这个命令。

看起来 redis 并没有做一个全局的 keys 的缓存,所以并没有办法优化局部性能,但即使存在一个全局的 keys 列表,对这个列表进行遍历,其时间复杂度依然是 O(N)。

键总数

1
2
# 查看所有键
dbsize

这个操作的性能是 O(1),也就意味着可以直接被线上使用。

它可以作为查询全部数据以前的预优化,至少全局的记录数量可以预先提取出来,以获得分页查询的依据

检查键是否存在

1
2
# 确认 java 作为一个键是否存在
exists java

如果存在返回 1,不存在返回 0。

注1:在存在多个候选返回值的时候,redis会返回语义更加丰富的返回值。如返回成功或失败,可以直接返回true或false,但返回0既可以表示失败,也可以表示操作的操作数(operand)为0,而返回非0不仅可以告诉我们操作成功了,而且还会精确地告诉我们操作了多少个对象,可谓一举两得。这种设计思路遍布 Redis API 中。

问题:估计有个全局优化,能够不返回具体值的情况下得到是否存在某个 key 的结论。

删除键

1
2
# 删除 java 键
del java

如果删除成功则返回 1,否则返回 0

键过期

1
2
3
4
5
6
set hello world

set hello world EX 10 NX

# 在 redis 中,最早支持的时间单位为second,如果不特别指定单位,指定时间时数字都代表秒。这个策略可以推广到其他系统里。美团的 redis 系统里面的默认时间单位就是秒。如果值是负数,键会立即被删除。
expire hello 10

set 是为比较少的返回“OK”的 command。

因为这个命令有复合的EX、PX、NX、XX等选项,所以其他相对的命令(如SETNX)可能会被设为过期,且被移除。

expire 命令的结果也是 0 和 1。

如果使用了 expire 命令,还有一个可以拿来轮询的 ttl 命令,可以告诉我们键的剩余时间:

1
2
3
4
5
6
7
8
9
10
11
12
ttl hello
(integer) 7

......
# 如果返回-2,意味着键已被删除
ttl hello
(integer) -2

# 这时候试着取健值,则得到 nil

get hello
(nil)

键的数据结构类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
set a b
type a
string

# rpush 会强制转化一个 key 到 list 类型
rpush mylist a b c
# 返回结果为 7
(integer) 7
type mylist
list

# 不存在的键
type non_exist_key
# 返回 none

字符串命令

设/取值

1
2
3
4
5
6
7
set hello world
exsts hello
setnx hello wolrd
## 若存在才设值
set hello jedix xx

get hello

批量设/取值

1
2
3
4
5
6
7
mset a 1 b 2 c 3
get a
get b
get c

# 取不到返回nil
mget a b c d

如果使用平凡的取/设值命令,时间开销为:

总开销= n * (一次网络开销 + 一次操作开销)

如果使用批量取/设值命令,时间开销为:

总开销= 一次网络开销 + n * (一次操作开销)

redis每秒可以处理上万的读写操作,相当于每次读写操作的开销小于0.1毫秒,而网络开销很难低于1毫秒。根据阿姆达尔定律,网络开销的减少才是性能优化的大头。

加/减值

因为 Redis 本身是单线程架构,所以本身不需要其他设计中的悲观锁或者 cas 操作保证操作正确性。
返回的自增结果永远是正确的。

1
2
3
4
5
6
7
8
9
10
11
# 值不是整数,返回错误
# 值是整数,返回自增后的结果
# 值不存在,按照结果为0自增,返回结果1
incr java

decr java

incrby java 10
decrby java 5
# 没有 decrbyfloat 命令
incrbyfloat java 4.3

注意,java里涉及数字的缺省值都是0,而且只是缺省值,并不是终值。

位操作命令

背景见:https://redis.io/topics/data-types-intro#bitmaps

Redis 并不只是一个平凡的 kv 数据存储,而是一个拥有许多数据类型的服务器。其中有一种类型是用 String 来解释为位图-“Bitmaps are not an actual data type, but a set of bit-oriented operations defined on the String type”。字符串是safe blobs,最大长度是 512 MB,恰好等于一个2的32次方的位图。字符串的英文字符,都符合(comply to)ascii编码。

所谓的位图,可以用紧凑的方式来表示一个大的 true/false 值域,而且这个 true/false 的点还带有 position 信息。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 把键的 offset 的位取出来,如果offset无值,则取0
getbit hello 10
# 把键的 offset 的位设值,只能设值0或1,返回旧值
setbit hello 1000 1
# Number of set bits in the range
bitcount hello

# 1st position of 1 or 0 in the key in range. O(N)
BITPOS hello 0

# 与或非
SET key1 "foobar"
SET key2 "abcdef"

# and or not xor
BITOP AND dest key1 key2
GET dest

其他命令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 返回最后结果的长度
append hello 123

# 返回结果的长度
strlen hello

# 原子化地设值并返回旧值
getset hello world123

# 设值指定位置的字符,返回修改后的字符串长度
setrange hello 1 a

# 设定指定位置的值,并返回。start 和 end 都是闭区间
getrange hello 1 2

哈希命令

设/取值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 设值,在 key 和 value 之间加入了一个 field
hset user:1 name tom

# 取值,在 key 和 value 之间加入了一个 field
hget user:1 name

# 删除 field,而不删除key
hdel user:1 name

# 计算 key 数目
hlen user:1

# 若 field 不存在,则设值
hsetnx user:1 name jerry

注意,len 往往是计数,而 strlen 往往是计值。

批量设/取值

1
2
3
4
5
# 批量设值
hmset user:1 name tom age 20 city beijing

# 批量获取
hmget user:1 name age city

对于mset的升级,就是把数据结构写在最前头。set -> mset -> hmset。

数值操作

1
2
3
4
5
# 按照指定值加值,这里的命令没有缺省被加数1了,必须显式指定被加数
hincrby user:1 age 10

# 按照指定值加浮点值,这里的命令没有缺省被加数1了,必须显式指定被加数
hincrbyfloat user:1 age 5.1

其他操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 判断一个 field 是否存在
hexists user:1 name

# 遍历一个 key 下面所有的 fields,实际上应该叫 hfields 比较恰当。时间复杂度为O(N)。一个key下面的N比较小的时候,可以直接在生产上使用。
hkeys user:1

# 遍历一个 key 下面所有的 fields 的值
hvals user:1

# 遍历一个 key 下面所有的 fields 和值
hgetall user:1

# 获取一个 key 下面指定 field 的字符串长度
hstrlen user:1 name

列表命令

增删操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# 从右至左插入元素,这个命令天然就是多参数的
rpush listkey c b a

# 从左至右插入元素
lpush listkey c b a

# 按范围取元素。index从左至右为0至N-1,从右至左为-1至-N。
lrange listkey 0 -1

# 在枢轴前后插入元素,注意方向和前后的关系
linsert listkey before b java

# 获取指定 index 的元素
LINDEX listkey -1

# 获取指定 key 的 size
llen listkey

# 列表类型总是没有删除操作,而使用弹出操作作为替代品
lpop listkey

# 从左至右删除元素,1表示从左至右删除1个元素,0表示删除全部元素,-1表示从右至左删除1个元素
lrem listkey 1 a

# 保留列表的1到最后一个元素-即去掉第一个元素
LTRIM listkey 1 -1

# 设置特定 index 的元素的 value,这里的 index 不能 out of bound。
LSET listkey 1 python

redis里涉及区间的 end,都是闭区间的 end。

阻塞操作

1
2
# 从左至右阻塞操作,第一个返回值总是 listkey1,只要有一个元素可以弹出,blpop就会立即返回。如果时间参数为0,则无限阻塞。可以使用这个命令制造一个延时队列、阻塞队列。blpop 可以同时监听多个 key,类似 selector 方案。每个 value 只能在多个客户端中被 pop 一次。
blpop listkey1 listkey2 20

集合命令

增删操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 往集合里加元素,因为是无序的,所以也无所谓insert和左右
sadd myset a b c d

# 指名道姓直接删除集合里的元素
srem myset a b

# 计算集合的大小,注意这不是len。直接使用内置变量,所以其操作时间复杂度为 O(1)。
scard myset

# 确认 d 是不是 myset 的元素
sismember myset d

# 从集合中随机弹出1个元素。因为强调 set 是无序的,所以产生了这个操作。
srandmember myset 1
# 默认值为1
srandmember myset

# 从集合中弹出一个元素,元素会被删除
spop myset 1

# 获取集合中的所有元素
smembers myset

集合操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 求交集
sinter myset1 myset2

# 求并集
sunion myset1 myset2

# 求差集
sdiff myset1 myset2

# 因为集合操作比较耗时(m*n的乘法二项式复杂度),求交集并存储到目标key里。
sinterstore interset myset1 myset2

# 因为集合操作比较耗时(m*n的乘法二项式复杂度),求并集并存储到目标key里。
sunionstore unionset myset1 myset2

# 因为集合操作比较耗时(m*n的乘法二项式复杂度),求差集并存储到目标key里。
sdiff myset1 myset2

有序集合命令

增删操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
# 给一个 zset 增加,成员,先写score再写元素。因为有这个排序操作,这个操作的时间复杂度是O(log(n))。也支持 nx、xx、ch、incr 几个 argument。
zadd user:ranking 250 tom 1 kris 91 mike 200 frank 220 tim 250 martin

# 获取集合大小。时间复杂度O(1)。
zcard user:ranking

# 获取集合中指定成员的分数
zscore user:ranking tom

# 获取集合中指定成员的从低到高排名
zrank user:ranking tom

# 获取集合中指定成员的从高到低排名
zrevrank user:ranking tom

# 删除集合中指定成员
zrem user:ranking tom

# 增加集合中指定成员的score
zincrby user:ranking 10 tom

# 返回集合中从低到高指定排名的成员,withscores 可以被去掉
zrange user:ranking 0 1 withscores

# 返回集合中从高到低指定排名的成员,withscores 可以被去掉
zrevrange user:ranking 0 1 withscores

# 返回集合中从低到高指定分数范围的成员,withscores 可以被去掉。此外也支持开、闭区间、正负无穷参数。
zrangebyscore user:ranking 200 250 withscores

# 返回集合中从高到低指定分数范围的成员(注意,参数是反的),withscores 可以被去掉。此外也支持开、闭区间、正负无穷参数。
zrevrangebyscore user:ranking 250 200 withscores

# 返回集合中指定分数段的成员的数量
zcount user:ranking 200 250

# 删除集合中指定排名的成员
zremrangebyrank user:ranking 0 2

集合操作

1
2
3
4
5
6
7
8
# 交集聚合存储操作,2代表了要做交集的 keys 数目。如果不指定聚合类型,结果就是加权后相加sum。
zinterstore user:ranking:1_inter_0 2 user:ranking:1 user:ranking:2

# 交集聚合存储操作,2代表了要做交集的 keys 数目。1代表集合1的权重,0.5代表集合2的权重。
zinterstore user:ranking:1_inter_0 2 user:ranking:1 user:ranking:2 weights 1 0.5 aggregate max

# 并集聚合存储操作
zunionstore user:ranking:1_inter_0 2 user:ranking:1 user:ranking:2

键管理

单个键管理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

# 换 key 名称,rename会删除掉 java 的旧值,所以值比较大的时候可能会阻塞 redis 实例(类似关系型数据库的 alter)。python会直接返回nil。
rename python java

# 如果key不存在,则换 key 名称。这是为了防止误操作。
renamenx python java

# 随机返回一个key
randomkey

# 让一个键在一个特定的秒级时间戳过期
expireat hello 2000

# 让一个键在10毫秒后过期
pexpire hello 10

# 清除过期时间
persist hello

注意,如果字符串的value为string类型,set 操作也会去除过期时间。

Redis不支持二级数据结构(例如列表或者哈希)里元素的过期时间。

迁移键

1
2
3
4
5
6
7
8
9
10
# 在一个 redis 实例内的多个 db 迁移。
move key db

# 先导出一个键
dump hello
# 再另一个实例里面重建key。0代表没有ttl,非0代表ttl。
restore hello 0 "\x00\x05world\t\x00\xc9#mH\x84/\x11s"

# migrate 原子化地结合了 dump、restore和del的操作,可以支持多个键操作。批量地迁移键到目标主机和端口,超时时间为5000.
migrate 127.0.0.1 6380 "" 0 5000 keys key1 key2 key3

遍历键

1
2
# 遍历 videos 模式的键值对,删除键
redis-cli keys videos | xargs redis-cli del

keys命令本身很容易阻塞redis节点,在以下情况下可以考虑使用:

  • 在不对外服务的从节点上使用。但会影响主从复制。
  • 如果确认键值总数比较少,可以使用以下命令

其他情况下,应该考虑使用 scan 命令,渐进式地遍历所有键。

scan的模式为: scan cursor [match pattern] [count number]

其中 cursor 是必须的,从0开始,到0又结束,周而复始。count 默认为10。

1
2
3
4
5
6
7
# 返回若干个键,而不是值。scan并不是并发安全的。相当于隔离性非常差的事务。
scan 0

# 衍生命令
HSCAN hello 0
SSCAN hello 0
zscan hello 0

数据库管理

关系型数据库用字符来区分不同的数据库名不同。Redis只是使用数字来区分不同的数据库。
Redis 默认拥有16个数据库,默认选中的数据库的 dbindex 是0。

1
2
# 切换到 db 0
SELECT 0

未来的 redis-cluster 功能默认只使用 db 0。不同的db之间的数据(KV)是相互隔离的。但用不同的 redis instance 一样可以实现这个效果。

1
2
3
4
# 清除当前db的所有数据!慎用
flushdb
# 清除所有db的所有数据!慎用
flushall