keys

过度使用keys这个命令,将会导致出现性能毛刺。这个命令的时间复杂度是O(N),而且redis又是单线程执行,在执行keys时即使是时间复杂度只有O(1)例如SET或者GET这种简单命令也会堵塞,从而导致这个时间点性能抖动,甚至可能出现timeout。

强烈建议生产环境屏蔽keys命令

scan

既然keys命令不推荐使用,那就就用scan命令。如果把keys命令比作类似select * from users where username like '%afei%'这种SQL,那么scan应该是select * from users where id>? limit 10这种命令。

官方文档用法如下:

1
SCAN cursor [MATCH pattern] [COUNT count]

初始执行scan命令例如scan 0。SCAN命令是一个基于游标的迭代器。这意味着命令每次被调用都需要使用上一次这个调用返回的游标作为该次调用的游标参数,以此来延续之前的迭代过程。当SCAN命令的游标参数被设置为0时,服务器将开始一次新的迭代,而当redis服务器向用户返回值为0的游标时,表示迭代已结束,这是唯一迭代结束的判定方式,而不能通过返回结果集是否为空判断迭代结束。

使用方式:

1
2
3
4
5
6
7
8
9
10
11
12
13
127.0.0.1:6380> scan 0
1) "22"
2) 1) "23"
2) "20"
3) "14"
4) "2"
5) "19"
6) "9"
7) "3"
8) "21"
9) "12"
10) "25"
11) "7"

返回结果分为两个部分:第一部分即1)就是下一次迭代游标,第二部分即2)就是本次迭代结果集。

slowlog

上面提到不能使用keys命令,如果就有开发这么做了呢,我们如何得知?与其他任意存储系统例如mysqlmongodb可以查看慢日志一样,redis也可以,即通过命令slowlog。用法如下:

1
SLOWLOG subcommand [argument]

subcommand主要有:

  • get,用法:slowlog get [argument],获取argument参数指定数量的慢日志。
  • len,用法:slowlog len,总慢日志数量。
  • reset,用法:slowlog reset,清空慢日志。

执行结果如下:

1
2
3
4
5
6
7
8
9
10
127.0.0.1:6380> slowlog get 5
1) 1) (integer) 2
2) (integer) 1532656201
3) (integer) 2033
4) 1) "flushddbb"
2) 1) (integer) 1 ---- 慢日志编码,一般不用care
2) (integer) 1532646897 ---- 导致慢日志的命令执行的时间点,如果api有timeout,可以通过对比这个时间,判断可能是慢日志命令执行导致的
3) (integer) 26424 ---- 导致慢日志执行的redis命令,通过4)可知,执行config rewrite导致慢日志,总耗时26ms+
4) 1) "config"
2) "rewrite"

命令耗时超过多少才会保存到slowlog中,可以通过命令config set slowlog-log-slower-than 2000配置并且不需要重启redis。注意:单位是微秒,2000微秒即2毫秒。

rename-command

为了防止把问题带到生产环境,我们可以通过配置文件重命名一些危险命令,例如keys等一些高危命令。操作非常简单,只需要在conf配置文件增加如下所示配置即可:

1
2
3
rename-command flushdb flushddbb
rename-command flushall flushallall
rename-command keys keysys

bigkeys

随着项目越做越大,缓存使用越来越不规范。我们如何检查生产环境上一些有问题的数据。bigkeys就派上用场了,用法如下:

1
redis-cli -p 6380 --bigkeys

执行结果如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
... ...
-------- summary -------

Sampled 526 keys in the keyspace!
Total key length in bytes is 1524 (avg len 2.90)

Biggest string found 'test' has 10005 bytes
Biggest list found 'commentlist' has 13 items

524 strings with 15181 bytes (99.62% of keys, avg size 28.97)
2 lists with 19 items (00.38% of keys, avg size 9.50)
0 sets with 0 members (00.00% of keys, avg size 0.00)
0 hashs with 0 fields (00.00% of keys, avg size 0.00)
0 zsets with 0 members (00.00% of keys, avg size 0.00)

最后5行可知,没有set,hash,zset几种数据结构的数据。string类型有524个,list类型有两个;通过Biggest ... ...可知,最大string结构的key是test,最大list结构的key是commentlist

需要注意的是,这个bigkeys得到的最大,不一定是最大。说明原因前,首先说明bigkeys的原理,非常简单,通过scan命令遍历,各种不同数据结构的key,分别通过不同的命令得到最大的key:

  • 如果是string结构,通过strlen判断;
  • 如果是list结构,通过llen判断;
  • 如果是hash结构,通过hlen判断;
  • 如果是set结构,通过scard判断;
  • 如果是sorted set结构,通过zcard判断。

正因为这样的判断方式,虽然string结构肯定可以正确的筛选出最占用缓存,也可以说最大的key。但是list不一定,例如,现在有两个list类型的key,分别是:numberlist--[0,1,2]stringlist--["123456789123456789"],由于通过llen判断,所以numberlist要大于stringlist。而事实上stringlist更占用内存。其他三种数据结构hashsetsorted set都会存在这个问题。使用bigkeys一定要注意这一点。

monitor

假设生产环境没有屏蔽keys等一些高危命令,并且slowlog中还不断有新的keys导致慢日志。那如何揪出这些命令是由谁执行的呢?这就是monitor的用处,用法如下:

1
redis-cli -p 6380 monitor

如果当前redis环境OPS比较高,那么建议结合linux管道命令优化,只输出keys命令的执行情况:

1
2
3
[afei@redis ~]# redis-cli -p 6380 monitor | grep keys 
1532645266.656525 [0 10.0.0.1:43544] "keyss" "*"
1532645287.257657 [0 10.0.0.1:43544] "keyss" "44*"

执行结果中很清楚的看到keys命名执行来源。通过输出的IP和端口信息,就能在目标服务器上找到执行这条命令的进程,揪出元凶,勒令整改。

info

如果说哪个命令能最全面反映当前redis运行情况,那么非info莫属。用法如下:

1
INFO [section]

section可选值有:

  • Server:运行的redis实例一些信息,包括:redis版本,操作系统信息,端口,GCC版本,配置文件路径等;
  • Clients:redis客户端信息,包括:已连接客户端数量,阻塞客户端数量等;
  • Memory:使用内存,峰值内存,内存碎片率,内存分配方式。这几个参数都非常重要;
  • Persistence:AOF和RDB持久化信息;
  • Stats:一些统计信息,最重要三个参数:OPS(instantaneous_ops_per_sec),keyspace_hitskeyspace_misses两个参数反应缓存命中率;
  • Replication:redis集群信息;
  • CPU:CPU相关信息;
  • Keyspace:redis中各个DB里key的信息;

config

config是一个非常有价值的命令,主要体现在对redis的运维。因为生产环境一般是不允许随意重启的,不能因为需要调优一些参数就修改conf配置文件并重启。redis作者早就想到了这一点,通过config命令能热修改一些配置,不需要重启redis实例,可以通过如下命令查看哪些参数可以热修改:

1
config get *

热修改就比较容易了,执行如下命令即可:

1
config set

例如:config set slowlog-max-len 100config set maxclients 1024

这样修改的话,如果以后由于某些原因redis实例故障需要重启,那通过config热修改的参数就会被配置文件中的参数覆盖,所以我们需要通过一个命令将config热修改的参数刷到redis配置文件中持久化,通过执行如下命令即可:

1
config rewrite

执行该命令后,我们能在config文件中看到类似这种信息:

1
2
3
4
5
6
# 如果conf中本来就有这个参数,通过执行config set,那么redis直接原地修改配置文件
maxclients 1024
# 如果conf中没有这个参数,通过执行config set,那么redis会追加在Generated by CONFIG REWRITE字样后面
# Generated by CONFIG REWRITE
save 600 60
slowlog-max-len 100

set

官方文档介绍的用法如下:

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

你可能用的比较多的就是set key value,或者SETEX key seconds value,所以很多同学用redis实现分布式锁分为两步:首先执行SETNX key value,然后执行EXPIRE key seconds。很明显,这种实现有很严重的问题,因为两步执行不具备原子性,如果执行第一个命令后出现某些未知异常导致无法执行EXPIRE key seconds,那么分布式锁就会一直无法得到释放。

通过SET命令实现分布式锁的正式姿势应该是SET key value EX seconds NX(EX和PX任选,取决于对过期时间精度要求)。另外,value也有要求,最好是一个类似UUID这种具备唯一性的字符串。当然如果问你redis是否还有其他实现分布式锁的方案。你能说出redlock,那对方一定眼前一亮,心里对你竖起大拇指,但嘴上不会说。

参考

  • 阿飞的博客,公众号