当我们操作 Redis 发现耗时较长时,原因可能有两个:
如果是第一种情况,那么所有服务都会发生网络延迟,只需要联系运维处理即可,这里主要讨论第二种情况
基准性能指 Redis 在一台负载正常的机器上的最大响应延迟和平均响应延迟,我们可以找一台同配置的机器,与原机器比较基准性能,看看 Redis 是不是真的变慢了
从 Redis 2.8.7 开始,redis-cli 命令可以追加 –intrinsic-latency 选项,用于监测和统计某个时间段内 Redis 的最大延迟
# 60 指的是测试时长为 60s,可以任意指定
redis-cli -h 127.0.0.1 -p 6379 --intrinsic-latency 60
Max latency so far: 1 microseconds.
Max latency so far: 157 microseconds.
Max latency so far: 173 microseconds.
Max latency so far: 323 microseconds.
Max latency so far: 324 microseconds.
Max latency so far: 325 microseconds.
Max latency so far: 334 microseconds.
Max latency so far: 520 microseconds.
Max latency so far: 527 microseconds.
Max latency so far: 591 microseconds.
Max latency so far: 1178 microseconds.
Max latency so far: 2299 microseconds.
Max latency so far: 2881 microseconds.
Max latency so far: 4113 microseconds.
597018388 total runs (avg latency: 0.1005 microseconds / 100.50 nanoseconds per run).
Worst run took 40926x longer than the average latency.
从输出结果可以看出 60s 内最大延迟是 4113 微秒
使用以下命令,查看 Redis 的最小、最大、平均访问延迟
``shell
redis-cli -h 127.0.0.1 -p 6379 --latency
min: 0, max: 6, avg: 0.24 (6064 samples)
按 ctrl + c 结束命令,可见平均延迟为0.24ms,共统计了 6064 个样本数据
还可以使用以下命令,查看一段时间内 Redis 的最小、最大、平均访问延迟
```shell
redis-cli -h 127.0.0.1 -p 6379 --latency-history -i 1
min: 0, max: 1, avg: 0.29 (98 samples) -- 1.01 seconds range
min: 0, max: 1, avg: 0.25 (96 samples) -- 1.00 seconds range
min: 0, max: 1, avg: 0.28 (96 samples) -- 1.00 seconds range
min: 0, max: 1, avg: 0.26 (96 samples) -- 1.00 seconds range
min: 0, max: 1, avg: 0.27 (97 samples) -- 1.00 seconds range
...
以上输出结果是,每隔 1 秒采样 Redis 的平均操作耗时,其结果分布在 0.25 ~ 0.29ms 之间
通过以上命令,我们可以在相同配置的服务器上测试比较 Redis 的基准性能,找到可能变慢了的 Redis 实例
找到目标,接下来分析可能导致 Redis 变慢的因素
Redis 提供了慢日志命令的统计功能,它记录了有哪些命令在执行时耗时比较久
查看 Redis 慢日志之前,你需要设置慢日志的阈值,例如,设置慢日志的阈值为 10ms,并且保留最近 128 条慢日志记录
在 redis.conf 中设置,重启 Redis 实例来生效
slowlog-log-slower-than 10000
slowlog-max-len 128
也通过 CONFIG SET 命令动态设置
CONFIG SET slowlog-log-slower-than 10000
CONFIG SET slowlog-max-len 128
执行 slowlog get 命令查询最近记录的慢日志,可以指定返回条数,不指定默认十条
127.0.0.1:6379> SLOWLOG get 5
1) 1) (integer) 32693 # 慢日志ID
2) (integer) 1593763337 # 执行时间戳
3) (integer) 5299 # 执行耗时(微秒)
4) 1) "LRANGE" # 具体执行的命令和参数
2) "user_list:2000"
3) "0"
4) "-1"
2) 1) (integer) 32692
2) (integer) 1593763337
3) (integer) 5044
4) 1) "GET"
2) "user_info:1000"
.....
通过查看慢日志,我们就可以知道在什么时间点,执行了哪些命令比较耗时,一般原因有以下两个:
如果你查询慢日志发现,并不是复杂度过高的命令导致的,而都是 SET / DEL 这种简单命令出现在慢日志中,那么你就要考虑你的实例否写入了 bigkey
Redis 在写入数据时,需要为新的数据分配内存,相对应的,当从 Redis 中删除数据时,它会释放对应的内存空间。如果一个 key 写入的 value 非常大,那么 Redis 在分配内存时就会比较耗时。同样的,当删除这个 key 时,释放内存也会比较耗时,这种类型的 key 我们一般称之为 bigkey
Redis 提供了扫描 bigkey 的命令,用于扫描一个实例 bigkey 的分布情况
redis-cli -h 127.0.0.1 -p 6379 --bigkeys
...
-------- summary -------
Sampled 829675 keys in the keyspace!
Total key length in bytes is 10059825 (avg len 12.13)
Biggest string found 'key:291880' has 10 bytes
Biggest list found 'mylist:004' has 40 items
Biggest set found 'myset:2386' has 38 members
Biggest hash found 'myhash:3574' has 37 fields
Biggest zset found 'myzset:2704' has 42 members
36313 strings with 363130 bytes (04.38% of keys, avg size 10.00)
787393 lists with 896540 items (94.90% of keys, avg size 1.14)
1994 sets with 40052 members (00.24% of keys, avg size 20.09)
1990 hashs with 39632 fields (00.24% of keys, avg size 19.92)
1985 zsets with 39750 members (00.24% of keys, avg size 20.03)
针对 bigkey 导致延迟的问题,有两点可以优化:
如果有大量的 key 在某个固定时间点集中过期,在这个时间点访问 Redis 时,就有可能导致延时变大,如果此时需要过期删除的是一个 bigkey,那么这个耗时会更久
解决办法是分散过期时间,可以为集中过期 key 增加一个随机过期时间。如果你使用的 Redis 是 4.0 以上版本,可以开启 lazy-free 机制(lazyfree-lazy-expire yes),当删除过期 key 时,把释放内存的操作放到后台线程中执行,避免阻塞主线程
Redis 可以设置最大可用内存(maxmemory),默认是实例内存。当 Redis 达到 maxmemory 时,Redis 必须清除部分数据,从而造成延迟。如果存储了 bigkey,那么耗时会更久
使用 info memory 命令查看 Redis 内存占用情况,比较 used_memory 和 maxmemory
localhost:6379> info memory
used_memory:692264 # redis 服务器分配的内存总量,也就是内部存储数据的内存占用量
used_memory_human:676.04K # 以可读形式返回 used_memory
used_memory_rss:655336 # 从操作系统的角度返回 redis 进程占用的物理内存总量
.....
maxmemory:0 # redis 能使用的最大内存上限,0 表示没有上限
maxmemory_human:0B # 以可读形式返回 maxmemory
可以调整 Redis 的内存淘汰策略,比如改为随机淘汰,随机淘汰速度要快很多,尽可能减少耗时。如果使用的是 Redis 4.0 以上版本,开启 layz-free 机制,把淘汰 key 释放内存的操作放到后台线程中执行(lazyfree-lazy-eviction = yes)
当 Redis 开启了后台 RDB 和 AOF rewrite 后,需要主进程创建出一个子进程进行数据的持久化。主进程创建子进程,会调用操作系统提供的 fork 函数。而 fork 在执行过程中,主进程需要拷贝自己的内存页表给子进程,如果这个实例很大,那么这个拷贝的过程也会比较耗时
可以执行 info stats 命令获取到 latest_fork_usec 指标,表示 Redis 最近一次 fork 操作耗时,如果耗时很大,比如超过1秒,则需要做出优化调整
localhost:6379> info stats
...
latest_fork_usec:59477 # 上一次 fork 耗时,单位微秒
...
推荐在低峰期进行备份,而对于丢失数据不敏感的业务(例如把 Redis 当做纯缓存使用)可以关闭持久化