文章目录
- 1 Redis监控
- 1.1 Redis监控指标
- 1.1.1 性能指标: Performance
- 1.1.2 内存指标: Memory
- 1.1.3 基本活动指标:Basic activity
- 1.1.4 持久性指标: Persistence
- 1.1.5 错误指标:Error
- 1.2 监控方式
- 1.2.1 info
- 1.2.2 性能监控
- 1.2.3 内存监控
- 1.2.4 基本活动指标
- 1.2.5 持久性指标
- 1.2.6 错误指标
- 1.3 redis性能测试命令
- 1.4 Redis响应慢
- 1.4.1 延迟基线测量
- 1.4.2 慢指令监控
- 1.4.2.1 慢日志功能
- 1.4.2.2 Latency Monitoring
- 1.4.3 如何解决 Redis 变慢
- 1.4.3.1 网络通信导致的延迟
- 1.4.3.2 慢指令导致的延迟
- 1.4.3.3 Fork生成 RDB导致的延迟
- 1.4.3.4 内存大页(transparent huge pages)
- 1.4.3.5 swap:操作系统分页
- 1.4.3.5.1 获取 Redis 实例 pid
- 1.4.3.5.2 解决方案
- 1.4.3.6 AOF 和磁盘 I/O 导致的延迟
- 1.4.3.7 expires淘汰过期数据
- 1.4.3.7.1 解决方案
- 1.4.3.8 bigkey
- 1.4.3.8.1 查找bigkey
- 1.4.3.8.2 解决方案
1 Redis监控
1.1 Redis监控指标
监控指标
- 性能指标:
Performance
- 内存指标:
Memory
- 基本活动指标:
Basic activity
- 持久性指标:
Persistence
- 错误指标:
Error
1.1.1 性能指标: Performance
Name | Description |
---|---|
latency | Redis响应一个请求的时间 |
instantaneous_ops_per_sec | 平均每秒处理请求总数 |
hi rate(calculated)` | 缓存命中率(计算出来的 |
1.1.2 内存指标: Memory
Name | Description |
---|---|
used_memory | 已使用内存 |
mem_fragmentation_ratio | 内存碎片率 |
evicted_keys | 由于最大内存限制被移除的key的数量 |
blocked_clients | 由于BLPOP,BRPOP,or BRPOPLPUSH而备阻塞的客户端 |
1.1.3 基本活动指标:Basic activity
Name | Description |
---|---|
connected_clients | 客户端连接数 |
conected_laves | slave数量 |
master_last_io_seconds_ago | 最近一次主从交互之后的秒数 |
keyspace | 数据库中的key值总数 |
1.1.4 持久性指标: Persistence
Name | Description |
---|---|
rdb_last_save_time | 最后一次持久化保存磁盘的时间戳 |
rdb_changes_sice_last_save | 自最后一次持久化以来数据库的更改数 |
1.1.5 错误指标:Error
Name | Description |
---|---|
rejected_connections | 由于达到maxclient限制而被拒绝的连接数 |
keyspace_misses key | 值查找失败(没有命中)次数 |
master_link_down_since_seconds | 主从断开的持续时间(以秒为单位) |
1.2 监控方式
Redis
监控方式:
- redis-benchmark
- redis-stat
- redis-faina
- redislive
- redis-cli
- monitor
- showlog
get
:获取慢查询日志
len
:获取慢查询日志条目数
reset
:重置慢查询日志
相关配置:
slowlog-log-slower-than 1000
# 设置慢查询的时间下线,单位:微秒
slowlog-max-len 100
# 设置慢查询命令对应的日志显示长度,单位:命令数
1.2.1 info
info(可以一次性获取所有的信息,也可以按块获取信息)
server
:服务器运行的环境参数clients
:客户端相关信息memory
:服务器运行内存统计数据persistence
:持久化信息stats
:通用统计数据Replication
:主从复制相关信息CPU
:CPU使用情况cluster
:集群信息Keypass
:键值对统计数量信息
终端info
命令使用
./redis-cli info 按块获取信息 | grep 需要过滤的参数
./redis-cli info stats | grep ops
交互式info
命令使用
./redis-cli > info server
1.2.2 性能监控
redis-cli info | grep ops # 每秒操作数
1.2.3 内存监控
内存监控
./redis-cli info | grep used | grep human
used_memory_human:2.99M
#内存分配器从操作系统分配的内存总量
used_memory_rss_human:8.04M
#操作系统看到的内存占用,top
命令看到的内存
used_memory_peak_human:7.77M
# redis内存消耗的峰值
used_memory_lua_human:37.00K
# lua脚本引擎占用的内存大小
由于BLPOP,BRPOP,or BRPOPLPUSH而备阻塞的客户端,使用如下命令:
./redis-cli info | grep
blocked_clientsblocked_clients:0
由于最大内存限制被移除的key
的数量
./redis-cli info | grep
evicted_keysevicted_keys:0 #
内存碎片率
./redis-cli info | grep
mem_fragmentation_ratiomem_fragmentation_ratio:2.74
已使用内存
./redis-cli info | grep
used_memory:used_memory:3133624
1.2.4 基本活动指标
redis
连接了多少客户端 通过观察其数量可以确认是否存在意料之外的连接。如果发现数量不对劲,就可以使用lcient list
指令列出所有的客户端链接地址来确定源头。
[root@CombCloud-2020110836 src]# ./redis-cli info | grep connected_clientsconnected_clients:1
[root@CombCloud-2020110836 src]# ./redis-cli info | grep connectedconnected_clients:1 # 客户端连接数量connected_slaves:1 # slave连接数量
1.2.5 持久性指标
持久性指标
[root@CombCloud-2020110836 src]# ./redis-cli info | grep rdb_last_save_timerdb_last_save_time:1591876204 # 最后一次持久化保存磁盘的时间戳
[root@CombCloud-2020110836 src]# ./redis-cli info | grep rdb_changes_since_last_saverdb_changes_since_last_save:0 # 自最后一次持久化以来数据库的更改数
1.2.6 错误指标
由于超出最大连接数限制而被拒绝的客户端连接次数,如果这个数字很大,则意味着服务器的最大连接数设置得过低,需要调整maxclients
[root@CombCloud-2020110836 src]# ./redis-cli info | grep
connected_clientsconnected_clients:1
key
值查找失败(没有命中)次数,出现多次可能是被hei ke gongjji
[root@CombCloud-2020110836 src]# ./redis-cli info | grep
keyspacekeyspace_misses:0
主从断开的持续时间(以秒为单位)
[root@CombCloud-2020110836 src]# ./redis-cli info | grep
rdb_changes_since_last_saverdb_changes_since_last_save:0
复制积压缓冲区如果设置得太小,会导致里面的指令被覆盖掉找不到偏移量,从而触发全量同步
[root@CombCloud-2020110836 src]# ./redis-cli info | grep
backlog_sizerepl_backlog_size:1048576
通过查看sync_partial_err
变量的次数来决定是否需要扩大积压缓冲区,它表示主从半同步复制失败的次数
[root@CombCloud-2020110836 src]# ./redis-cli info | grep sync_partial_errsync_partial_err:1
1.3 redis性能测试命令
./redis-benchmark -c 100 -n 5000
说明:100个连接,5000次请求对应的性能
1.4 Redis响应慢
一旦 Redis
请求延迟增加,可能就会导致业务系统雪崩
1.4.1 延迟基线测量
redis-cli
命令提供了--intrinsic-latency
选项,用来监测和统计测试期间内的最大延迟(以毫秒为单位),这个延迟可以作为 Redis
的基线性能。
redis-cli --latency -h host -p port
比如执行如下指令:
redis-cli --intrinsic-latency 100
Max latency so far: 4 microseconds.
Max latency so far: 18 microseconds.
Max latency so far: 41 microseconds.
Max latency so far: 57 microseconds.
Max latency so far: 78 microseconds.
Max latency so far: 170 microseconds.
Max latency so far: 342 microseconds.
Max latency so far: 3079 microseconds.
45026981 total runs (avg latency: 2.2209 microseconds / 2220.89 nanoseconds per run).
Worst run took 1386x longer than the average latency.
注意
:参数100
是测试将执行的秒数
。我们运行测试的时间越长,我们就越有可能发现延迟峰值。
通常运行 100 秒通常是合适的,足以发现延迟问题了,当然我们可以选择不同时间运行几次,避免误差。
此次运行的最大延迟是 3079 微秒,所以基线性能是 3079 (3 毫秒)微秒。
需要注意的是,我们要在 Redis
的服务端运行,而不是客户端。这样,可以避免网络对基线性能的影响。
可以通过 -h host -p port
来连接服务端,如果想监测网络对 Redis
的性能影响,可以使用 Iperf 测量客户端到服务端的网络延迟。
如果网络延迟几百毫秒,说明网络可能有其他大流量的程序在运行导致网络拥塞,需要找运维协调网络的流量分配。
1.4.2 慢指令监控
如何判断是否是慢指令呢?
看操作复杂度是否是O(N)
。官方文档对每个命令的复杂度都有介绍,尽可能使用O(1)
和 O(log N)
命令。
涉及到集合操作的复杂度一般为O(N)
,比如集合全量查询HGETALL、SMEMBERS
,以及集合的聚合操作:SORT、LREM、 SUNION
等。
那么有监控数据可以观测呢?代码不是我写的,不知道有没有人用了慢指令。
有两种方式可以排查到:
- 使用
Redis
慢日志功能查出慢命令; latency-monitor
(延迟监控)工具。
此外,可以使用自己(top、htop、prstat
等)快速检查 Redis
主进程的 CPU
消耗。如果 CPU
使用率很高而流量不高,通常表明使用了慢速命令。
1.4.2.1 慢日志功能
Redis
中的 slowlog
命令可以让我们快速定位到那些超出指定执行时间的慢命令,默认情况下命令若是执行时间超过 10ms
就会被记录到日志。
slowlog
只会记录其命令执行的时间,不包含 io
往返操作,也不记录单由网络延迟引起的响应慢。
我们可以根据基线性能来自定义慢命令的标准(配置成基线性能最大延迟的 2 倍),调整触发记录慢命令的阈值。
可以在 redis-cli
中输入以下命令配置记录 6 毫秒以上的指令:
redis-cli CONFIG SET slowlog-log-slower-than 6000
也可以在 Redis.config
配置文件中设置,以微秒
为单位。
想要查看所有执行时间比较慢的命令,可以通过使用 Redis-cli
工具,输入 slowlog get
命令查看,返回结果的第三个字段以微秒位单位显示命令的执行时间。
假如只需要查看最后 2 个慢命令,输入 slowlog get 2
即可。
示例:获取最近2个慢查询命令
127.0.0.1:6381> SLOWLOG get 2
1) 1) (integer) 6
2) (integer) 1458734263
3) (integer) 74372
4) 1) "hgetall"
2) "max.dsp.blacklist"
2) 1) (integer) 5
2) (integer) 1458734258
3) (integer) 5411075
4) 1) "keys"
2) "max.dsp.blacklist"
以第一个 HGET
命令为例分析,每个 slowlog
实体共 4 个字段:
字段 1
:1 个整数,表示这个slowlog
出现的序号,server
启动后递增,当前为 6。字段 2
:表示查询执行时的Unix
时间戳。字段 3
:表示查询执行微秒数
,当前是 74372 微秒,约 74ms。字段 4
: 表示查询的命令
和参数
,如果参数很多或很大,只会显示部分参数个数。当前命令是hgetall max.dsp.blacklist。
1.4.2.2 Latency Monitoring
Redis
在 2.8.13
版本引入了 Latency Monitoring
功能,用于以秒为粒度监控各种事件的发生频率。
启用延迟监视器的第一步是设置延迟阈值(单位毫秒)
。只有超过该阈值的时间才会被记录,比如我们根据基线性能(3ms
)的 3 倍设置阈值为 9 ms。
可以用 redis-cli
设置也可以在 Redis.config
中设置;
CONFIG SET latency-monitor-threshold 9
工具记录的相关事件的详情可查看官方文档:https://redis.io/topics/latency-monitor
如获取最近的 latency
127.0.0.1:6379> debug sleep 2
OK
(2.00s)
127.0.0.1:6379> latency latest
1) 1) "command"
2) (integer) 1645330616
3) (integer) 2003
4) (integer) 2003
以上字段说明:
- 事件的名称;
- 事件发生的最新延迟的
Unix
时间戳; - 毫秒为单位的时间延迟;
- 该事件的最大延迟。
1.4.3 如何解决 Redis 变慢
Redis
的数据读写由单线程执行,如果主线程执行的操作时间太长,就会导致主线程阻塞。
那么分析下都有哪些操作会阻塞主线程,我们又该如何解决?
1.4.3.1 网络通信导致的延迟
客户端使用 TCP/IP
连接或 Unix
域连接连接到 Redis
。1 Gbit/s
网络的典型延迟约为 200 us
。
redis 客户端执行一条命令分 4 个过程:
发送命令-〉 命令排队 -〉 命令执行-〉 返回结果
这个过程称为 Round trip time
(简称 RTT, 往返时间
),mget mset
有效节约了 RTT
,但大部分命令(如 hgetall
,并没有 mhgetall
)不支持批量操作,需要消耗 N
次 RTT
,这个时候需要 pipeline
来解决这个问题。
Redis pipeline
将多个命令连接在一起来减少网络响应往返次数。
1.4.3.2 慢指令导致的延迟
根据上文的慢指令监控查询文档,查询到慢查询指令。可以通过以下两种方式解决:
- 在
Cluster
集群中,将聚合运算等O(N)
操作运行在slave
上,或者在客户端完成。 - 使用高效的命令代替。使用增量迭代的方式,避免一次查询大量数据,具体请查看
SCAN、SSCAN、HSCAN和ZSCAN
命令。
除此之外,生产中禁用KEYS
命令,它只适用于调试。因为它会遍历所有的键值对,所以操作延时高。
1.4.3.3 Fork生成 RDB导致的延迟
生成 RDB
快照,Redis
必须 fork
后台进程。fork
操作(在主线程中运行)本身会导致延迟
Redis
使用操作系统的多进程写时复制技术 COW(Copy On Write)
来实现快照持久化,减少内存占用。
但 fork
会涉及到复制大量链接对象,一个 24 GB
的大型 Redis
实例需要 24 GB / 4 kB * 8 = 48 MB
的页表。
执行 bgsave
时,这将涉及分配和复制 48 MB
内存。
此外,从库加载 RDB
期间无法提供读写服务,所以主库的数据量大小控制在 2~4G
左右,让从库快速的加载完成。
1.4.3.4 内存大页(transparent huge pages)
常规的内存页是按照 4 KB
来分配,Linux
内核从 2.6.38 开始支持内存大页机制,该机制支持 2MB
大小的内存页分配。
Redis
使用了 fork
生成 RDB
做持久化提供了数据可靠性保证。
当生成 RDB
快照的过程中,Redis
采用 写时复制
技术使得主线程依然可以接收客户端的写请求。
也就是当数据被修改的时候,Redis 会复制一份这个数据,再进行修改。
采用了内存大页,生成 RDB
期间,即使客户端修改的数据只有 50B
的数据,Redis
需要复制 2MB
的大页。当写的指令比较多的时候就会导致大量的拷贝,导致性能变慢。
使用以下指令禁用 Linux
内存大页即可:
echo never > /sys/kernel/mm/transparent_hugepage/enabled
1.4.3.5 swap:操作系统分页
当物理内存(内存条)不够用的时候,将部分内存上的数据交换到 swap
空间上,以便让系统不会因内存不够用而导致 oom
或者更致命的情况出现。
当某进程向 OS
请求内存发现不足时,OS
会把内存中暂时不用的数据交换出去,放在 SWAP
分区中,这个过程称为 SWAP OUT
当某进程又需要这些数据且 OS
发现还有空闲物理内存时,又会把 SWAP
分区中的数据交换回物理内存中,这个过程称为 SWAP IN
内存 swap
是操作系统里将内存数据在内存
和磁盘
间来回换入和换出的机制,涉及到磁盘的读写。
触发 swap 的情况有哪些呢?
对于Redis
而言,有两种常见的情况:
Redis
使用了比可用内存更多的内存;- 与
Redis
在同一机器运行的其他进程在执行大量的文件读写I/O
操作(包括生成大文件的RDB
文件和AOF
后台线程),文件读写占用内存,导致Redis
获得的内存减少,触发了swap
。
那么如何排查是否因为 swap
导致的性能变慢呢?
Linux
提供了很好的工具来排查这个问题,所以当怀疑由于交换导致的延迟时,只需按照以下步骤排查。
1.4.3.5.1 获取 Redis 实例 pid
$ redis-cli info | grep process_id
process_id:13160
进入此进程的 /proc
文件系统目录:
cd /proc/13160
在这里有一个 smaps
的文件,该文件描述了 Redis
进程的内存布局,运行以下指令,用 grep
查找所有文件中的 Swap
字段。
$ cat smaps | egrep '^(Swap|Size)'
Size: 316 kB
Swap: 0 kB
Size: 4 kB
Swap: 0 kB
Size: 8 kB
Swap: 0 kB
Size: 40 kB
Swap: 0 kB
Size: 132 kB
Swap: 0 kB
Size: 720896 kB
Swap: 12 kB
每行 Size
表示 Redis
实例所用的一块内存大小,和 Size
下方的 Swap
对应这块 Size
大小的内存区域有多少数据已经被换出到磁盘上了。
如果 Size == Swap
则说明数据被完全换出了。
可以看到有一个 720896 kB 的内存大小有 12 kb 被换出到了磁盘上(仅交换了 12 kB),这就没什么问题。
Redis
本身会使用很多大小不一的内存块,所以,你可以看到有很多 Size
行,有的很小,就是 4KB
,而有的很大,例如 720896KB
。不同内存块被换出到磁盘上的大小也不一样。
注意
: 如果 Swap 一切都是 0 kb,或者零星的 4k ,那么一切正常。
当出现百 MB,甚至 GB 级别的 swap 大小时,就表明,此时,Redis 实例的内存压力很大,很有可能会变慢。
1.4.3.5.2 解决方案
- 增加机器内存;
- 将
Redis
放在单独的机器上运行,避免在同一机器上运行需要大量内存的进程,从而满足Redis
的内存需求; - 增加
Cluster
集群的数量分担数据量,减少每个实例所需的内存。
1.4.3.6 AOF 和磁盘 I/O 导致的延迟
为了保证数据可靠性,Redis
使用 AOF
和 RDB
快照实现快速恢复和持久化。
可以使用 appendfsync
配置将 AOF
配置为以三种不同的方式在磁盘上执行 write
或者 fsync
(可以在运行时使用 CONFIG SET
命令修改此设置,比如:redis-cli CONFIG SET appendfsync no
)。
no
:Redis
不执行fsync
,唯一的延迟来自于write
调用,write
只需要把日志记录写到内核缓冲区就可以返回。everysec
:Redis
每秒执行一次fsync
。使用后台子线程异步完成fsync
操作。最多丢失 1s 的数据。always
:每次写入操作都会执行fsync
,然后用OK
代码回复客户端(实际上Redis
会尝试将同时执行的许多命令聚集到单个fsync
中),没有数据丢失。在这种模式下,性能通常非常低,强烈建议使用快速磁盘和可以在短时间内执行fsync
的文件系统实现。
我们通常将 Redis
用于缓存,数据丢失完全可以从数据获取,并不需要很高的数据可靠性,建议设置成 no
或者 everysec
。
除此之外,避免 AOF
文件过大, Redis
会进行 AOF
重写,生成缩小的 AOF
文件。
可以把配置项 no-appendfsync-on-rewrite
设置为 yes
,表示在 AOF
重写时,不进行 fsync
操作。也就是说,Redis
实例把写命令写到内存后,不调用后台线程进行 fsync
操作,就直接返回了。
1.4.3.7 expires淘汰过期数据
Redis
有两种方式淘汰过期数据:
惰性删除
:当接收请求的时候发现key
已经过期,才执行删除;定时删除
:每100
毫秒删除一些过期的key
。
定时删除的算法如下:
- 随机采样
ACTIVE_EXPIRE_CYCLE_LOOKUPS_PER_LOOP
个数的key
,删除所有过期的key
; - 如果发现还有超过
25%
的key
已过期,则执行步骤一。
ACTIVE_EXPIRE_CYCLE_LOOKUPS_PER_LOOP
默认设置为 20
,每秒执行 10
次,删除 200
个 key
问题不大。
如果触发了第二条,就会导致 Redis
一致在删除过期数据去释放内存。而删除是阻塞的
那么触发条件是什么
就是大量的
key
设置了相同的时间参数。同一秒内,大量key
过期,需要重复删除多次才能降低到25%
以下。
简而言之:大量同时到期的 key 可能会导致性能波动。
1.4.3.7.1 解决方案
如果一批 key
的确是同时过期,可以在 EXPIREAT
和 EXPIRE
的过期时间参数上,加上一个一定大小范围内的随机数,这样,既保证了 key
在一个邻近时间范围内被删除,又避免了同时过期造成的压力。
1.4.3.8 bigkey
通常我们会将含有较大数据或含有大量成员、列表数的 Key
称之为大 Key
,下面我们将用几个实际的例子对大 Key 的特征进行描述:
- 一个
STRING
类型的Key
,它的值为5MB
(数据过大) - 一个
LIST
类型的Key
,它的列表数量为10000
个(列表数量过多) - 一个
ZSET
类型的Key
,它的成员数量为10000
个(成员数量过多) - 一个
HASH
格式的Key
,它的成员数量虽然只有1000
个但这些成员的value
总大小为10MB
(成员体积过大)
bigkey
带来问题如下:
Redis
内存不断变大引发OOM
,或者达到maxmemory
设置值引发写阻塞或重要Key
被逐出;Redis Cluster
中的某个node
内存远超其余node
,但因Redis Cluster
的数据迁移最小粒度为Key
而无法将node
上的内存均衡化;bigkey
的读请求占用过大带宽,自身变慢的同时影响到该服务器上的其它服务;- 删除一个
bigkey
造成主库较长时间的阻塞并引发同步中断或主从切换;
1.4.3.8.1 查找bigkey
使用 redis-rdb-tools
工具以定制化方式找出大 Key
1.4.3.8.2 解决方案
- 对大
key
拆分
如将一个含有数万成员的HASH Key
拆分为多个HASH Key
,并确保每个Key
的成员数量在合理范围,在Redis Cluster
结构中,大Key
的拆分对node
间的内存平衡能够起到显著作用。 - 异步清理大
key
Redis
自 4.0 起提供了UNLINK
命令,该命令能够以非阻塞
的方式缓慢逐步的清理传入的Key
,通过UNLINK
,可以安全的删除大Key
甚至特大Key