💡 去年我给一台 Redis 改配置,为了搞清 protected-mode 到底管什么,把官方那份 redis.conf 从头到尾读了一遍。读完最大的感受不是"学到几个新参数",而是这份文件其实是一张作者亲手写的功能地图:二十多个分节把 Redis 的能力边界划得很清楚,而真正生效的那几十行,才是运维现场天天碰的东西。
比较有意思的是我手上那份 conf。它长得跟官方样例一模一样,2739 行、注释一行没少,但服务起来的行为完全不同------因为它只有 4 行被人动过,而这 4 行连在一起,刚好把"这台实例能不能被公网摸到"和"数据会不会掉"两件事一起改掉了。
很多人读 redis.conf 的方式是 Ctrl+F 搜参数名,读到的都是孤立的一行。可是 Redis 的配置项之间是有合取关系的:bind 要和 protected-mode、requirepass 一起看,save 要和其他几行一起看,appendonly 关掉之后下面十几行 append* 全部空转。
还有一种更常见的误会:看见 # save 3600 1 ... 是注释状态,就以为这台 Redis 不落盘了;看见 appendonly no 又以为快照也没了。这两条判断都是错的,而且错的方向相反。
所以这一篇就拿着这份真实的 redis.conf 逐段走一遍:先确认它相对官方样例改了哪几行,再把 NETWORK、SNAPSHOTTING、AOF、内存阈值、客户端缓冲这几段真正生效的配置摊开,讲清每一条默认值背后的取舍,以及如果是我在生产上会怎么落笔。

1. 先把这份 conf 的"身份"确认清楚
读配置文件的第一步不是读参数,是确认自己在读哪个版本、它被改过多少。
这份文件的几个物理特征:
| 项目 | 实测值 | 说明 |
|---|---|---|
| 总行数 | 2739 行 | 去掉空行和注释后只剩 85 行有效 |
| 文件大小 | 131 KB | 注释占掉九成以上体积 |
| 分节数 | 27 个 | ### NETWORK ### 这种横幅分隔 |
| 生效配置项 | 85 条 | 其中相当一部分是"默认值写出来给你看" |
| 相对官方样例的改动 | 4 处 | 用 diff 对同一个 tag 比出来的 |
版本这件事我一开始判断错了。redis.conf 里不写自己的版本号,所以只能靠配置项集合反推。这份文件里出现了 backupdirname、backup-sealed-ttl、array-slice-size、hash-min-template-entries 这一批条目,我把官方仓库里几个 tag 的样例文件拉下来逐条对,8.0.0 和 8.2.0 都没有这批项,8.10.2 全都有,而且行数(2738)和这份只差一行------那一行就是多出来的 bind 那处改动。
所以:这是一份 Redis 8.10.2 的官方样例配置,本地只动了 4 行。
判"被改过哪几行"的方法我现在固定在做的就一件事------不要跟内存里的默认值比,要跟对应 tag 的样例文件比:
bash
diff <(grep -vE '^\s*$' upstream-8.10.2/redis.conf) <(grep -vE '^\s*$' mine/redis.conf)
比出来的结果一共 9 行,归成 4 处改动:
| # | 官方样例 | 这份 conf | 一眼看去的意图 |
|---|---|---|---|
| 1 | bind 127.0.0.1 -::1 |
注释掉,改成 bind * -::* |
让容器外面能连进来 |
| 2 | protected-mode yes |
protected-mode no |
别拦非本地连接 |
| 3 | pidfile /var/run/redis_6379.pid |
pidfile ./redis_6379.pid |
不想碰系统目录权限 |
| 4 | # requirepass foobared |
requirepass <六位数字弱口令> |
至少要有点认证 |
这张表是整篇文章的入口。第 1、2、4 行必须放在一段里读,它们不是三个独立开关,而是同一个判定式的三个变量------这也是这份 conf 最值得警惕的地方,下一段展开。
还有一点要先说清楚:不要把 redis.conf 的注释当权威文档。 注释里写的默认值会随版本漂,比如 hash 的 listpack 阈值在中途从 128 调到了 512,一份 6.x 的样例注释里就还写着旧值。注释负责讲道理,具体数值一律以 CONFIG GET 的现值为准。
2. NETWORK 与 SECURITY:三行连起来是一个危险三角
这三条不能分开读,因为 protected-mode 的判定式里同时用到另外两条。
先看这份 conf 的实际取值:
conf
bind * -::*
protected-mode no
port 6379
requirepass <已设置,弱口令>
protected-mode 的注释讲得很直白:当保护模式开着、且默认用户没有密码时,服务端只接受来自 127.0.0.1、::1 和 Unix domain socket 的连接。也就是说它是给"裸奔实例"兜底的一道保险丝,判据是两个变量:有没有密码 、连接来自哪里。
于是这四行的组合语义是这样的:

顺序读完这张图会得到一个结论:bind * -::* 把所有网卡都听了,protected-mode no 把唯一的兜底保险拆了,此时决定性的就只有密码那一行。而这一行填的是六位数字。六位数字加一个默认存在的 default 用户,等于这道门是虚掩的------Redis 历史上被扫到并直接写文件、加载模块的实例,绝大多数就停在"公网可达 + 弱口令 or 无口令"这个形态上。
这里顺便把 bind 的一个语法细节说清楚,因为它经常出现在容器编排的配置里:
| 写法 | 含义 |
|---|---|
bind 127.0.0.1 -::1 |
IPv4 绑回环;IPv6 带 - 前缀,本机没有这个地址也能起 |
bind * -::* |
所有可用接口;-::* 表示 IPv6 全部接口不可用时不报错 |
bind 192.168.1.100 10.0.0.1 |
绑两个指定 IPv4 |
- 前缀只豁免"这个地址不属于任何网卡"这一种情况。地址已经被别的进程占用时照样启动失败 ,不受 - 影响;不支持的协议族则会被静默跳过。
Q1:容器里要外部可访问,是不是必须关 protected-mode?
不是。关掉它只是省事的那条路。正确的组合是:protected-mode 保持 yes,同时把 requirepass 设成强随机值(或者用 ACL 建用户),保护模式看到"有密码"就不会再拦外部连接。生产上再加一层:安全组只放行应用子网,不要把 6379 暴露到公网地址段。
Q2:requirepass 和 ACL 该用哪个?
| 维度 | requirepass | ACL |
|---|---|---|
| 粒度 | 全实例一个口令 | 按用户分,可限 key 模式与命令 |
| 审计 | 无 | acllog-max-len(这份 conf 是 128 条)留拒绝记录 |
| 改密影响面 | 所有客户端一起断 | 只影响对应用户 |
| 适合 | 单机自用、内网隔离 | 多人多应用共用一套实例 |
这份 conf 的 SECURITY 段里还有一组容易被忽略的条目,它们不在生效行里,但决定了运维动作的边界:几个控制 Redis 写哪些文件的指令是不可变 的(比如 dir、dbfilename),一批危险命令默认被屏蔽,要让 CONFIG SET 能改这些敏感项得显式打开 enable-protected-configs(样例里注释状态,值 no)。这套收紧是 Redis 后来给自己加的护甲,读 conf 时值得知道有这一段。
剩下几行网络参数:
| 配置 | 值 | 说明 |
|---|---|---|
tcp-backlog |
511 | 全连接队列长度,实际生效值受内核 somaxconn 截断 |
timeout |
0 | 不主动踢空闲客户端;线上一般是应用自己管连接池 |
tcp-keepalive |
300 | 5 分钟探一次,对端掉线不至于一直占着 fd |
maxclients |
未设置(默认 10000) | 起不来时会被压到文件描述符上限减 32 |
maxclients 注释里有一条容易踩的提醒:集群模式下这个额度要和集群总线共用,每个节点各占一进一出两条,大规模集群必须提前把它调大,否则客户端连不上而报错长得像"连接泄漏"。
Q3:为什么 timeout 0 通常不动?
说白了,踢谁这件事应用比 Redis 更清楚。真出现连接数只涨不降,八成是应用侧连接池没设上限或没做归还,在 Redis 上配 timeout 只是把症状压住,还会顺带误伤低频但长驻的客户端。
3. GENERAL 与 SNAPSHOTTING:注释掉 save 不等于关掉 RDB
这一段是误会密度最高的地方,两条相反的错都出在这里。
GENERAL 段的生效行:
| 配置 | 值 | 实际含义 |
|---|---|---|
daemonize |
no | 前台运行;日志才能被容器运行时收走 |
pidfile |
./redis_6379.pid |
相对路径,落在启动时的当前工作目录 |
loglevel |
notice | 默认档 |
logfile |
"" |
空串即打到标准输出,不是"日志关掉" |
databases |
16 | SELECT 0..15 |
always-show-logo |
no | 不刷屏 |
set-proc-title |
yes | ps 里带上监听地址和运行模式 |
proc-title-template |
{title} {listen-addr} {server-mode} |
多实例排障时很好用 |
locale-collate |
"" |
空串走默认 |
logfile "" 是最常被读错的一行:空串代表输出到 stdout,而不是关闭日志。这份 conf 的"前台 + stdout"组合,明显是照着容器形态配的,和上面把 bind 打开、pidfile 挪进当前目录是同一个意图。
SNAPSHOTTING 段的关键在这里:
conf
# save 3600 1 300 100 60 10000
# save ""
stop-writes-on-bgsave-error yes
rdbcompression yes
rdbchecksum yes
dbfilename dump.rdb
rdb-del-sync-files no
dir ./
样例里 save 那一行是注释状态,紧挨着上面还有几行说明:不特别指定的话,Redis 默认会在一小时内改动 1 次、五分钟内改动 100 次、六十秒内改动 10000 次这三个点上落盘。所以注释掉 save 是"没显式覆盖",不是"关闭" ;真正把快照调度清空要写 save "",样例里那条 # save "" 才是关闭的写法。这两行注释只差一个引号,很多人记住的是后者、执行的是前者。
反过来的误会也一样常见:看见 appendonly no(下一节)就断言这台实例完全没有持久化。实际上 RDB 快照在默认状态下是开着的,只是没人显式写下那三个点。
dir ./ 我要单独拎出来,因为它是我见过最容易造成"重启数据没了"的一行。RDB 和 AOF 都写在 dir 下,而相对路径的解析基准是进程的当前工作目录 。 systemd 起的服务、手动在 /root 下敲起来的进程、容器里 WORKDIR 不同的镜像,三者会把同一个 ./ 落到三个不同目录。样例文件写 dir ./ 是文档性质,落到生产上应该换成绝对路径,并且确认挂载点真的映射出来了。
stop-writes-on-bgsave-error yes 的逻辑值得知道边界:它的生效前提是"至少配置了一个 save 点",快照调度被清空时它不起作用。开着的时候,只要最近一次后台保存失败,实例就硬拒写入------磁盘满、权限错、fork 失败都会走到这里。这是故意的难看:宁可让监控立刻炸,也不要"数据其实已经三天没落盘、所有人都很平静"。
rdbcompression yes 是对字符串值做 LZF 压缩;rdbchecksum yes 在文件尾部加 CRC64,代价很小,样例里两条都建议保持开启。rdb-del-sync-files no 属于复制侧的开关,下一节说。

Q1:那到底要不要显式写 save?
我的倾向是要写。不是为了改数值,是为了让配置文件自解释。默认调度生效这件事只有读过源码或读过注释的人知道,接手的人看见 # save ... 很容易判断成"快照没开",进而做出和实际不符的备份方案。写清楚 save 900 1 300 10 60 10000 或者干脆 save "" 配 appendonly yes,都比留一行注释强。
4. REPLICATION 与 AOF:一组"参数在、开关关"
AOF 段是这份文件里无效行密度最高的一段。
appendonly no 一关,下面这些行全部进入"备而不用":
| 配置 | 值 | 当前状态 |
|---|---|---|
appendfilename |
appendonly.aof | 不写 |
appenddirname |
appendonlydir | 目录不生成 |
appendfsync |
everysec | 不起作用 |
no-appendfsync-on-rewrite |
no | 不起作用 |
auto-aof-rewrite-percentage |
100 | 不起作用 |
auto-aof-rewrite-min-size |
64mb | 不起作用 |
aof-load-truncated |
yes | 不起作用 |
aof-use-rdb-preamble |
yes | 只在 rewrite 时有意义 |
aof-timestamp-enabled |
no | 显式关 |
appendfsync everysec 是绝大多数线上的落点:每条写命令不单独 fsync,一秒批量落一次。always 把持久性换成一倍以上的延迟抖动,no 交给操作系统决定,最多可能丢到秒级以上的量。no-appendfsync-on-rewrite yes 是另一种取舍:AOF 重写期间子进程在猛刷盘,这时父进程暂停 fsync,能避免重写带来的抖动,代价是这段窗口的数据风险变大。
auto-aof-rewrite-percentage 100 与 min-size 64mb 是一对:文件比起上次重写后长大一倍才重写,并且不小于 64 MB。这个 64 MB 的下限就是防止小实例反复重写的。
BACKUP 段是 8.10 才进来的新东西,这份 conf 里能看到它的两条生效行:
conf
backupdirname "backupdir"
backup-sealed-ttl 0
按注释口径:BACKUP 在工作目录下生成一套可恢复的 MP-AOF 文件集,目录名由 backupdirname 给出,而且只能是目录名,不能是绝对路径或嵌套路径 ;调用 BACKUP START 时这个目录必须是空的,BACKUP SEAL 之后里面就是不可变的 BASE、INCR 与 manifest,直到 BACKUP CLEANUP 清掉。backupdirname 是启动期专用配置,CONFIG SET 改不了。backup-sealed-ttl 0 表示封存后的备份不自动过期。
这里有个断链:备份目录已经配好了,但 appendonly no。这套能力挂在 MP-AOF 上,AOF 关着的形态下它用不上------所以这两行现在只是"把路径占好了",不是"备份已经在跑"。(这条依赖关系我只读了 conf 注释,没有实测过 appendonly no 时执行 BACKUP START 的具体报错,写在这里是为了别让人误以为备份已生效。)
复制段这份 conf 一条都没改,全在默认上,但每一条都影响副本同步的形态:
| 配置 | 值 | 说明 |
|---|---|---|
replica-serve-stale-data |
yes | 与主断开后仍继续应答,可能返回旧数据 |
replica-read-only |
yes | 副本只读 |
repl-diskless-sync |
yes | 同步时不落盘、直接从 socket 传给副本 |
repl-diskless-sync-delay |
5 | 等 5 秒攒更多副本一起开始 |
repl-diskless-sync-max-replicas |
0 | 0 即不限制并行副本数 |
repl-diskless-load |
disabled | 副本侧仍然先收 RDB 到磁盘再加载 |
repl-disable-tcp-nodelay |
no | 不关 Nagle,不攒包 |
replica-priority |
100 | 主备切换时的候选排序权重 |
rdb-del-sync-files |
no | 同步期间不产生临时删除文件 |
repl-diskless-load 这个四选一是这段里最需要想清楚的:disabled 是先把 RDB 落到磁盘再读;swapdb 边从 socket 解析边加载、同时保留当前数据集,同步期间还能对外服务,但内存要够,不然直接被 OOM 杀掉;flushdb 每次先清空整个库再装,失败就全丢;on-empty-db 只在库为空时才走 diskless,最稳。默认停在 disabled,就是不想让副本在同步期间赌内存。
Q1:diskless 同步什么时候真的划算?
主库内存很大、本地磁盘慢、而且副本集中同时发起同步的时候,不落盘能省掉一次几 GB 的写加一次读。反过来,如果磁盘不慢、或者网络抖动重,落盘的方式更好排查,出问题还能拿那个 RDB 文件手动装载。repl-diskless-sync-delay 5 那个"等 5 秒攒副本"就是为了避免每个副本各起一次完整传输,副本数量多的时候效果才明显。
5. 内存与编码阈值:真正影响延迟和内存的那一段
MEMORY MANAGEMENT 段整段都是注释状态,这件事本身就是最大的配置项。
这份 conf 里没有 maxmemory 这一行------样例里它是注释掉的 # maxmemory <bytes>。也就是说这台实例不设内存上限,配合同样保持注释默认的 maxmemory-policy noeviction,行为是:内存一路涨,涨到操作系统不允许为止。noeviction 的含义是"不淘汰任何 key",内存到达上限时写命令直接报错,而不像 allkeys-lru 那样悄悄丢数据。
这条链路我画出来,因为它决定了一次故障是"报错可诊断"还是"进程被内核干掉且无日志":

内核侧的两个相关条目这份 conf 是生效的:
conf
oom-score-adj no
oom-score-adj-values 0 200 800
disable-thp yes
oom-score-adj no 表示 Redis 不去调整自己的 oom_score_adj。这个能力的三个取值(no / yes(即 relative)/ absolute)配合那三个数字,分别控制主进程、副本进程、后台子进程的"被 OOM 杀掉的倾向"。这里有一个具体风险:后台保存或 AOF 重写 fork 出来的子进程如果不降低倾向,内核很可能先杀子进程,于是用户观察到的是"BGSAVE 老失败",而主进程看起来完全健康。注释里还提醒,非 root 且没有 CAP_SYS_RESOURCE 的进程只能往上加、不能往下减,所以 relative 配正数一定成功,配负数一定失败。
disable-thp yes 是 Redis 明确建议的一项:关掉透明大页。THP 会让 fork 之后的内存复制代价明显变大,对 BGSAVE 和 AOF 重写都是伤害,这一条保持默认开着是对的。
编码阈值在 ADVANCED CONFIG 段,这份 conf 全部显式写出了值(也就是"默认值抄给你看"):
| 类型 | 条目阈值 | 值阈值 | 说明 |
|---|---|---|---|
| hash | 512 | 64 | listpack 编码边界 |
| set | 128 | 64 | listpack;纯整数时另走 intset,上限 512 |
| zset | 128 | 64 | listpack |
| list | list-max-listpack-size -2 |
--- | 按字节控制:-2 即每节点 8 KB |
| hll | --- | hll-sparse-max-bytes 3000 |
稀疏编码上限 |
| stream | stream-node-max-entries 100 |
4096 | 单节点上限 |
-2 这种负数取值是这份文件里比较容易被漏掉的设计:负数表示按节点字节数限制(-5 到 -1 依次是 64/32/16/8/4 KB),正数表示严格按元素个数限制。注释直接把 -2 标成 good,-5 到 -3 标成不推荐。
这份 conf 里还有一批 8.10 新条目,属于我读注释才搞明白的类型:
conf
hash-min-template-entries 0
hash-max-template-entries 0
hash-rdb-load-min-template-entries 0
array-slice-size 4096
array-sparse-kmax 10
array-sparse-kmin 5
hash 的 template 编码思路是:当大量 key 共享同一套字段名时,把字段名集合抽成一份共享模板,字段名就不用在每个 key 里重复存一遍。字段数达到 hash-min-template-entries 的 hash 在写入时自动转成模板编码;使用了字段过期(HEXIPE 那一类)的 key 永远不转。默认 0 表示关闭自动转换。
注释里有两条约束我想特意转述一下,因为它们会影响"要不要开"的判断:
- 转换是单向 的。一旦某个 key 用了模板编码,即便后来
HDEL把字段数减到阈值以下,这个 key 也一路保持模板编码。 - 改配置是惰性生效的,只在该 key 下次写入时才应用,不会回头去转已存在的模板。
array-slice-size 4096 属于新的 Array 类型,必须是 256 到 65536 之间的 2 的幂。小 slice(1024 到 2048)适合索引稀疏、大量小数组;大 slice(8192 到 16384)适合连续密集数据。注释说 4096 适配混合负载,并且通过 CONFIG SET 改的话,已存在的数组保持原 slice 大小。array-sparse-kmax / kmin 那两个是稀疏 slice 的判定区间------元素很少时 slice 不会真的物化成一段连续内存。
list-compress-depth 0 保持关闭(对两端之外的中间节点做 LZF 压缩,读中间节点会变慢),activerehashing yes 保持开启(每次事件循环处理一点哈希表再平衡,把代价摊平,避免一次性卡顿)。
LAZY FREEING 段这份 conf 六条全是 no:
conf
lazyfree-lazy-eviction no
lazyfree-lazy-expire no
lazyfree-lazy-server-del no
replica-lazy-flush no
lazyfree-lazy-user-del no
lazyfree-lazy-user-flush no
说白了这就是"释放大 key 时选择阻塞主线程,而不是交给后台线程"。全 no 意味着 DEL 一个大 hash 或者 FLUSHALL 会卡住所有客户端。内存富裕、且不接受"一条命令卡几百毫秒"的服务,我会把 lazyfree-lazy-user-del 和 lazyfree-lazy-expire 打开;副本全量同步时清空主存的 replica-lazy-flush 也建议开,它能明显缩短副本不可用的窗口。代价是内存释放变慢,可能短期双份占用。
ACTIVE DEFRAGMENTATION 整段是注释状态,也就是 activedefrag 没开。这是 Redis 4.0 加的在线碎片整理,靠 jemalloc 把值拷到连续内存区再释放旧副本。用 jemalloc 且碎片不严重的话不开也行,我一般先看 mem_fragmentation_ratio 再决定。
THREADED I/O 段同样是整段注释,# io-threads 4 没生效。这段的适用边界比较窄:只有网络 I/O(读写 socket、协议解析)是瓶颈、而且 CPU 核绑得开的时候才值得动,多路复用主线程依然是单线程处理命令,别指望它让命令执行变并行。
6. 客户端、日志与被关掉的可观测性
这一段的默认值偏保守,可观测性三件套在这份 conf 里全部是关的。
client-output-buffer-limit 三行是复制与订阅断连的头号来源,值得逐行读:
conf
client-output-buffer-limit normal 0 0 0
client-output-buffer-limit replica 256mb 64mb 60
client-output-buffer-limit pubsub 32mb 8mb 60
三列语义是"硬性上限、软性上限 + 持续秒数"。普通客户端不限;副本缓冲一旦瞬时冲到 256 MB 立刻断链,持续在 64 MB 以上满 60 秒也断;pubsub 是 32 MB / 8 MB 60 秒。这份 conf 的三行都是默认值。
一个常见现象能直接由它解释:主库内存正常、CPU 正常,副本反复 full resync。十有八九是某个消费者读得慢、输出缓冲堆到 256 MB 被踢,回到全量同步又是一轮压力。这时候先查消费速率,改缓冲上限只是把断链换成堆积。
同一区的 maxmemory-clients 保持注释(样例里给了 1g 和 5% 两种写法),也就是没有全局的客户端内存配额。客户端很多又不太管连接数时,我建议显式设一个百分比,它比 client-output-buffer-limit 更整体------所有客户端的缓冲与开销一起算,超过配额按 idle 顺序回收。
KEYS TRACKING 段(客户端缓存失效通知)与 EVENT NOTIFICATION 段都是注释状态,这份 conf 的生效行里能看到结果:
conf
notify-keyspace-events ""
latency-monitor-threshold 0
| 能力 | 当前 | 打开的代价 |
|---|---|---|
| key 空间事件通知 | 关闭 | 每个事件都要发布;必须至少指定 K 或 E 才有事件 |
| 延迟监控 | 阈值 0,关闭 | 采样与统计常驻开销 |
| 慢日志 | 10000 微秒、128 条 | 开销很小 |
| LATENCY TRACKING | 独立一节,默认未启用 | 按事件类型累计 |
slowlog-log-slower-than 10000 的单位是微秒 ,也就是 10 毫秒,slowlog-max-len 128。这一项我几乎不动,但每次带新人都会强调一次单位,因为把它读成 10 秒的人会以为慢日志是空的。
hz 10 与 dynamic-hz yes 是后台任务的时间片密度(过期检查、定时任务);dynamic-hz 让 Redis 按 key 数量自适应提高活性。这一对保持默认即可。
7. 如果是我,这份 conf 会怎么落笔
把前面几段收拢成一张可执行的表,按"必改 / 按用途改 / 保持默认"三档。
先说这份 conf 本身的四处改动我会怎么处理:
| 现值 | 我的替换 | 理由 |
|---|---|---|
bind * -::* |
回环或指定内网地址 | 暴露面先收,认证不该是唯一防线 |
protected-mode no |
yes + 强口令或 ACL 用户 |
保险丝留着,外部访问由密码授权 |
pidfile ./redis_6379.pid |
绝对路径;容器形态干脆不要 | 相对路径跟着 cwd 走 |
requirepass <六位数字> |
强随机口令,或改走 ACL | 六位数字在公网等于没有 |
再补上这份 conf 里"没写但应该写"的几行:
| 建议加的配置 | 取值方向 | 属于哪一类问题 |
|---|---|---|
dir |
明确的绝对路径 + 已映射的挂载点 | 重启后数据找不到 |
save |
显式写出三档,或 save "" 交给 AOF |
配置意图只有读注释的人才知道 |
appendonly |
yes + everysec + aof-use-rdb-preamble yes |
只靠 RDB 时崩溃丢一整段快照窗口 |
maxmemory |
留足 fork 余量,别贴着物理内存 | 内核 OOM 而不是 Redis 报错 |
maxmemory-policy |
缓存用途 allkeys-lru;业务数据保持 noeviction |
悄悄丢 key 或写命令报错,二选一 |
lazyfree-lazy-user-del / -expire / replica-lazy-flush |
yes |
一条大 key 删除卡住整个实例 |
oom-score-adj |
yes,主进程负值、子进程正值 |
先杀子进程,表现成"保存老失败" |
maxmemory-clients |
百分比一档 | 慢消费者把主进程内存吃掉 |
logfile |
容器里留空走 stdout;裸机给真路径 | "空串"被误读成关日志 |
关于改配置这件事本身,还有两条实操层面的约束,是这份文件的 INCLUDES 段讲到的:
conf
# include /path/to/local.conf
# include /path/to/fragments/*.conf
Redis 永远以最后处理到的那一行为配置项的最终值 ,所以 include 放文件开头还是结尾,语义完全相反:放在开头是让主文件覆盖片段,放在结尾是用片段覆盖主文件。通配符按字母序展开;某个通配路径一条都没匹配上时,这条 include 会被忽略而不报错------这一点在容器里很容易变成"我的 override 文件根本没生效,但服务起得好好的"。
另一个坑是 CONFIG REWRITE 不会重写 include 这一条。运维用 CONFIG SET 热改完再 CONFIG REWRITE 落盘,include 的语义和位置不会被修正,但主文件里的其他行会被服务端现值刷写。
最后一件事:CONFIG GET 才是权威。我读任何 conf 文件时都会拿现值对一遍,尤其是版本升级之后------样例注释里写的默认值和二进制里编译进去的默认值,历史上确实出现过不同步的窗口。
最后总结
- 读一份 redis.conf 的正确顺序:先确认它对应哪个 tag(这份对不上 8.0/8.2,只对上 8.10.2),再 diff 出本地改动,最后才逐段读注释。跳过 diff 直接读,会把"作者举的例子"当成"这台实例的配置"。
bind、protected-mode、requirepass是一件事的三个变量,必须一起判。保护模式的保险丝只在"无密码"时生效,公网可达加六位口令等于没锁。- 注释掉
# save 3600 1 ...不是关闭快照 ,内置默认调度照样跑;真要关得写save ""。反过来appendonly no才是真的关掉 AOF,它一关,下面十几行append*与那段 BACKUP 配置全部空转。 dir ./、pidfile ./这类相对路径在 systemd、手动启动、容器三种形态下会落到三个不同目录,是"重启数据没了"的头号来源。- 没有
maxmemory加上默认的noeviction,故障形态是从"Redis 报错"变成"内核把进程杀掉";oom-score-adj no还会让内核优先杀掉 BGSAVE 的子进程,表现成"保存老是失败"。 - 8.10 那批新条目(
backupdirname、hash-*-template-entries、array-slice-size)默认都是关闭或 0,属于"知道有这件事、按需再开",不要一上来就调。 - 可观测性三件套
notify-keyspace-events、latency-monitor-threshold、activedefrag在这份 conf 里全是关的,slowlog反而一直开着;单位记得是微秒。 - 对后端和架构岗来说,我最关注的其实只有四条:暴露面(bind + 认证)、持久化路径(save / appendonly / dir)、内存上限与淘汰(maxmemory + policy + lazyfree)、缓冲与配额(output-buffer-limit + maxmemory-clients)。这四组决定了 Redis 在生产上是"可诊断的报错"还是"离奇的进程消失"。