我在年纪尚轻经验尚浅的时候,接到过一个任务。有个功能需要测试性能,设计上大量使用 Redis,我需要搭建一个 Redis 的性能测试环境,来找出最佳配置。对于当时只会用 API 实现需求的我来说,这显然不是个简单的任务。我面对的是许多 Redis 配置选项的作用和原理,以及这些配置选项背后的操作系统功能。一连好几天,我迷失在各种晦涩的英文文档和大量可疑的中文博客里,如同依靠一块磁铁就想在亚马逊丛林里找出一条可以复用的路线。那个任务我完成得并不好,最后是靠着一步一步测试摸索出一个似乎还行的组合。我念了一串不明所以的咒语,得到了一只长得像鸽子的白色鸟类。
时至今日,我又经历了许多次丛林历险。虽然丛林仍是丛林,没有变成灌木丛,但我多少掌握了一些「野外生存技巧」。我总结了一个 Checklist,记录了关于 Redis 运维配置的一些思路,作为曾经丛林历险生活的纪念品。
配置 checklist
Linux 环境调整
- 允许内核超量使用内存
vm.overcommit_memory=1
。 - 降低内存 swap 概率
swappiness
。 - 禁用内存透明大页 THP。
- 增大进程同时打开文件数量。
- 增大 TCP 全连接队列长度。
- 配置网络防火墙。
- 使用非 root 用户启动。
- (可选)定时备份数据。
Redis 服务配置
- 设置最大内存。
- 开启持久化。
- 设置慢日志阈值。
- 重命名高危命令防止误操作。
- 修改默认端口防止扫描。
- (可选)调整 hash、zset 等数据类型的 listpack (ziplist)使用条件。
- 监控 bigkey。
- 监控热点 Key。
- 启用延迟监控功能。
- (可选)监控内存碎片化。
高并发场景
- 增加最大连接客户端数量。
- 增加 tcp-backlog。
- 读写分离。
- (可选)增加 I/O 子线程。
- (可选)Redis 绑定 CPU 核心,提高缓存命中率。
Linux 环境配置
内存设置
允许内核超量使用内存
vm.overcommit_memory=1
。RDB 持久化和 AOF 重写需要 fork 子进程,当可用内存(物理+swap)不足时,fork 会失败。调整为 1 后,允许内存不足时 fork。
1
2
3
4
5
6# 查看
cat /proc/sys/vm/overcommit_memory
# 临时修改,重启后还原
sysctl vm.overcommit_memory=1
# 持久修改,重启后生效
echo "vm.overcommit_memory=1" >> /etc/sysctl.conf💡 /proc 不是一个真实的目录,因此对其的修改不持久,系统重启后会还原。类似
sysctl vm.overcommit_memory=1
的效果。💡 /etc/sysctl.conf 文件用于设置 Linux 内核参数,系统重启后才生效,可以用
sysctl -p
命令加载而无须重启。参考How-to-modify-sysctl-settings重定向提示权限不够时,用
tee
命令。1
echo "vm.overcommit_memory=1" | sudo tee -a /etc/sysctl.conf > /dev/null
配合
maxmemory
使用,设置 70%~80% 物理内存。降低内存 swap 概率
swappiness
。swap 影响 Redis 命令性能。
swappiness
越大,swap 概率越高。最小值为 0,但 Linux 3.5+ 时swappiness=0
代表内存不足时会杀死用户进程(包括 Redis) 回收内存,应该避免。取值策略:尽量小,宁愿用 swap 也不能杀死 redis 进程。- ≤ Linux 3.4,
swappiness
取 0。 - Linux 3.5+,
swappiness
取 1。
1
2
3
4
5
6# 临时修改,重启后还原
echo 1 > /proc/sys/vm/swappiness
# 临时修改,重启后还原
sysctl vm.swappiness=1
# 持久修改,重启后生效
echo "vm.swappiness=1" >> /etc/sysctl.conf相关命令
1
2
3
4
5
6uname -a # 查看内核版本
cat /proc/version # 查看内核版本
free -h # 查看系统 swap
vmstat 1 # 每隔 1s 输出一次,关注 si so 两列
cat /proc/{pid}」/smaps | grep -i swap # 查看 pid 进程的 swap 使用
sysctl -p # 立即加载 /etc/sysctl.conf 文件- ≤ Linux 3.4,
禁用内存透明大页 THP。
开启 THP 能提升内存访问效率,但会降低内存时复制的性能。根据 Redis 作者这篇 Linux 内核如何影响 Redis 性能的文章,Redis 依靠子进程实现 RDB 持久化和 AOF 重写,开启 THP 会增加重写期间父进程的内存消耗,也会拖慢重写期间命令执行速度。
1
2
3
4
5# 检查 THP
cat /sys/kernel/mm/transparent_hugepage/enabled
# 临时禁止 THP
echo never > /sys/kernel/mm/transparent_hugepage/enabled
echo never > /sys/kernel/mm/transparent_hugepage/defrag持久修改 THP 需要根据 Linux 发行版采取不同设置。
关于 THP 的详细接收,可以参考下面两篇文章。
6.3. 禁用 Transparent Huge Pages 功能 | Red Hat Product Documentation
我们为什么要禁用 THP 丨TiDB 应用实践 | PingCAP
💡 常用数据库 MySQL、MongoDB 都会建议关闭 THP。
网络设置
增大进程同时打开文件数量。
Linux 秉持「一切皆文件」的思想,将网络连接(套接字)也对应为一个文件。同时,Linux 限制每个进程能同时打开的文件描述符数量,也就限制了进程的最大连接数。一旦超过上限,会报 error: too many open files 错误。
通过
ulimit -n
可以查看进程的文件描述符限制,通常为 1024。通过ulimit
命令设置阈值,有两种阈值。- soft 代表警告阈值,可以超过这个值,超出会警告。
- hard 代表严格阈值,超出就报错。
1
2
3
4
5ulimit -Sn # 查看警告阈值
ulimit -Hn # 查看严格阈值
ulimit -Hn # 设置警告阈值
ulimit -Sn 10032 # 设置警告阈值通过 ulimit 设置的阈值只在当前会话内有效。想要持久设置,需要修改 /etc/security/limits.conf 文件。
1
2
3
4# /etc/security/limits.conf
root hard nofile 10032
root soft nofile 10032
# root 为进程用户,nofile 代表文件数量。Redis
maxclients
决定了最大客户端连接,默认 10000。如果同时打开文件数量小于这个数,会建议设置为 maxclient + 32,32 是 Redis 自身需要使用的数量。Redis 客户端文档 中提议设置
sysctl -w fs.file-max=100000
,根据Linux 内核参数文档,这里设置的是内核最大连接数。可以通过cat /proc/sys/fs/file-max
查看当前值,如果较小,可以增大。关于 ulimit 的设置,可以参考 how-to-set-ulimit-max-number-open-files。
增大 TCP 全连接队列长度。
Redis 默认的 tcp-backlog 值为 511,如果 Linux 系统的全连接队列长度小于 Redis 的值,启动时会警告。Linux 系统相关配置为
/proc/sys/net/core/somaxconn
,实际采用 max(somaxconn, backlog) 作为应用全连接队列长度。1
2
3
4# 查看系统默认值
cat /proc/sys/net/core/somaxconn
# 查看 6379 端口实际值,Send-Q 列
ss -lnt | grep 6379设置时将 somaxconn 增加到 Redis tcp-backlog 一致即可。高并发时可以统一为 65536。Redis 需设置 tcp-backlog 65536。
1
2
3
4
5
6# 临时设置
echo 65536 > /proc/sys/net/core/somaxconn
echo 65536 > /proc/sys/net/ipv4/tcp_max_syn_backlog
# 持久设置
echo "net.core.somaxconn=65536" >> sysctl.conf
echo "net.ipv4.tcp_max_syn_backlog=65536" >> sysctl.confDocker 在 run 命令添加相关配置。
1
docker run ... --sysctl net.core.somaxconn=511 ...
Docker compose
1
2sysctls:
net.core.somaxconn: '511'
安全
配置网络防火墙。
仅限内网访问,禁止外网流量。
如果是云主机,设置 IP 白名单仅限指定主机访问。
使用非 root 用户启动。
比较简单的做法是用一个非 root 用户安装并配置 Redis。高级一点的做法是专门创建一个用户,nologin 选项禁止登录,并赋予 Redis 相关权限。Docker 默认是以非 root 用户启动。
(可选)定时备份数据。
取决于 Redis 数据是否重要。如果是纯粹的缓存服务器,且加载成本不高,可以不备份。
Redis 服务配置
调整配置
设置最大内存。
设置为 70%~80% 物理内存。
1
2
3# redis.conf
# 2GB
maxmemory 2147483648开启持久化。
对于数据记录场景,数据持久化是基本要求。
对于缓存场景,开启 Redis 持久化可以加快重启速度。
建议采取混合持久化方案。
1
2# redis.conf
aof-use-rdb-preamble yes设置客户端连接超时。
超时客户端连接会被自动释放。使用连接池时没设置 idle 检测,以此兜底。
1
2
3# redis.conf
# 秒
timeout 300设置慢日志阈值。
放宽慢日志记录条件,增加慢日志队列长度。
1
2
3
4# 命令执行耗时超过 1 毫秒,记录慢日志
CONFIG SET slowlog-log-slower-than 1000
# 保留最近 1000 条慢日志
CONFIG SET slowlog-max-len 1000限制高危命令防止误操作。
部分 Redis 命令的影响很大,比如
flushdb/flushall
,误操作会导致数据丢失,需要限制使用。高危命令:
- flushall/flushdb
- shutdown
- config
- debug
可能导致性能问题的命令:
- keys
- save
Redis 6.0 之前可以通过
rename-command
重命名来隐藏命令。1
2# 将 flushall 重命名为指定的随机字符串
rename-command flusall {random_str}重命名命令也有缺陷:
- 客户端不支持,如果要在客户端使用重命名过的命令,需要修改代码。
- AOF、RDB 兼容问题。
- Redis 源码内部调用的命令还是原来的名称,主要是
config
命令。 - 主从复制时需要手动配置从节点,不支持自动设置。
Redis 6.0 开始,支持 ACL 功能,能更完善地控制权限。ACL 功能支持创建不同用户,并从命令和键名两个维护控制用户权限。
ACL 功能文档
ACL 命令文档ACL 为所有命令添加了不同标签(虽然叫 category 但允许重叠更像是 tag),@dangerous 标签下包含了所有的高危命令,可以通过
ACL CAT dangerous
查看。1
2
3
4# 创建一个叫 application 的用户,密码 42a979...
# 允许 @dangerous 外的所有命令
# 支持访问所有的键
ACL SETUSER application on +@all -@dangerous >42a979... ~*修改默认端口防止扫描。
1
2# redis.conf
port 9736(可选)调整 hash、zset 等数据类型的 listpack (ziplist)使用条件。
listpack 是紧凑结构,内存占用比 hashtable 和 skiplist 更低。如果瓶颈在内存,可以提高使用 listpack 的阈值。缺点是命令执行性能可能降低。配置如
hash-max-listpack-entries
hash-max-listpack-value
。
增加监控
监控 bigkey。
降低来自 bigkey 的危害。一般是在开发阶段就要注意。
可以通过
redis-cli —bigkeys
统计分布。也可以通过scan
+debug object key
查询实际大小。还可以 Redis RDB Tools 工具,离线分析 rdb 文件。重点是建立自己的 bigkey 标准。
可以在从节点上执行分析,降低对业务的影响。
删除时,通过 unlink 异步删除,对性能影响小,缺点是内存回收不及时。
监控热点 Key。
可以从客户端角度监控。
使用代理集群方案时,可以再代理端监控热点。
可以在 Redis Server 上利用 monitor 命令统计热点。
在网络层抓包分析。
启用延迟监控功能。
设置一个延迟阈值,超过阈值的事件被记录为延迟峰值。Redis 服务会监控记录命令、fork、rdb、aof、过期、淘汰等多种事件的耗时。
Redis 官方称延迟监控的实际成本接近于零,可以放心使用。
1
2
3
4# 阈值设置为 100 毫秒
CONFIG SET latency-monitor-threshold 100
# 使用 latency 命令查询
LATENCY latest(可选)监控内存碎片化。
used_memory_rss
代表操作系统分配给 Redis 进程的内存。used_memory
代表 Redis 分配出去的内存,包括 swap 内存。mem_fragmentation_ratio
代表内存碎片率,used_memory_rss
/used_memory
。> 1 表示内存碎片严重。< 1 代表存在 swap。建议以 used_memory ≥ 500M && mem_fragmentation_ratio > 1.5 作为一个监控指标。
追求高并发性能
增加最大连接客户端。
1
2# redis.conf
maxclients 20000增加 tcp-backlog。
1
2# redis.conf
tcp-backlog 65536读写分离。
需要在客户端配置。
1
2
3
4# 从节点与主节点断开连接后也能对外提供服务,保证基本可用。
slave-serve-stale-data yes
# 从节点仅处理读请求
slave-read-only yes(可选)增加 I/O 子线程提升网络性能。
Redis 默认关闭。官方建议确实遇到网络瓶颈再开启,且至少 4 核 CPU 时才开启,注意为主线程预留 1~2 核心。
1
2
3
4# 开启多线程处理接口写入
io-threads-do-reads yes
# 配置 I/O 线程数
io-threads 2(可选)Redis 绑定 CPU 核心,降低上下文切换开销,提高缓存命中率。
缺点是 fork 子进程会与主进程共用一个 CPU,造成竞争,因此不建议在主节点使用。
1
2
3
4# 在 CPU 0-1 上启用 RPS
echo '3' > /sys/class/net/eth1/queues/rx-0/rps_cpus
# redis 的 CPU 亲和性设置为 CPU 2-8
taskset -pc 2-8 `cat /var/run/redis.pid`
高延迟问题排查
判断是否是 Redis 问题
- 运行基准测试,得到基准性能数据。与正常实例比较。
确认了是 Redis 性能问题后
- 检查慢日志,slowlog get {n},排查慢速命令。应该从命令时间复杂度和数据量两方面分析。
- 排查 bigkey,redis-cli —bigkeys。
- 检查持久化方案,AOF + always fsync 很慢。
- 检查持久化状态,fork 执行时间过长会导致主线程阻塞。info state 查看 latest_fork_usec 指标。
- 检查内存用量,查看 Redis 进程用量和 swap 情况。
- 检查系统 THP 是否禁用。
- 检查 CPU 负载,top 命令查看 Redis 进程 CPU 使用率。
- 检查服务器网络环境,查看 ulimit fd 数量、backlog 队列长度。
- 测试网络延迟,在另一台服务器执行
redis-cli --latency -h host -p port
。 - 检查 Redis 连接数是否超过 maxclients。
Redis 基准测试命令
测试固有延迟,必须在 Redis 同台机器上测试。
1 | # 100s 内的最大响应延迟 |
水平有限,欢迎指正。