拓十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Redis配置与日志全解析:从logfile到慢查询的运维实战

Redis配置与日志全解析:从logfile到慢查询的运维实战

干Redis这几年,我最大的感受是:很多人把配置和日志当成两码事。配置文件改完就扔一边,日志只有线上出故障才想起来翻两眼。其实Redis的日志恰恰就是配置的“回声”——你的每一项配置怎么写的,日志就会用具体的事件、报错、耗时反馈给你。Redis配置日志这件事,往小了说是logfile路径和loglevel级别,往大了说,它直接关系到慢查询发现、主从复制健康度、缓存雪崩前的排查,甚至生产事故的根因定位。这篇就结合我实际部署和排障的经验,把Redis配置与日志里里外外拆一遍,该给的配置示例、轮转方案、慢日志分析方法、坑点清单都会列出来。适合刚接触Redis的后端同学,也适合正在折腾Redis运维和性能优化的老手。

1. Redis日志体系就是配置的一面镜子

1.1 日志不是附属品,而是配置表态的结果

很多人以为日志只是“出了问题才看的东西”,这个认知我觉得得先纠正。Redis的日志从哪来?全是从配置项里长出来的。比如你没配置logfile,Redis启动后日志就打到标准输出,你如果又是以daemonize yes方式后台启动,日志会直接被系统丢进/dev/null,等于出了一个哑巴Redis,出了事啥也看不见。再比如loglevel设为debug,日志能刷到你看不完;设为warning,平时看起来干净得跟没干活一样。这些选择都不是日志本身的“脾气”,而是配置的态度。

更直接的是,日志里的很多关键条目其实是配置问题的“举报信”。举个例子,主从复制链路里经常看到MASTER <-> REPLICA sync started,这条日志的背后取决于replicaof配没配、repl-backlog-size设多大、网络缓冲区够不够。再比如日志里出现Can't save in background: fork: Cannot allocate memory,大概率是系统的overcommit_memory设置有问题,而这属于典型的Linux内核参数,不属于redis.conf,但恰恰是配置问题。所以我的结论是:日志管理这件事,本质上在配置阶段就已经开始了。先理解配置,才看得懂日志。

1.2 Redis日志的四件套

我把Redis的日志体系分成四类,每一类的记录方式、存储位置、保留策略都不一样,千万别混为一谈:

日志类型核心配置默认行为主要用途
运行日志logfile、loglevel输出到stdout启动、关闭、错误、复制同步、持久化事件
慢查询日志slowlog-log-slower-than、slowlog-max-len内存环形队列,不落盘定位慢命令和性能隐患
持久化日志save、appendonly、appendfsync写日志文件与数据文件RDB快照、AOF重写与持久化异常
复制/集群日志replicaof、cluster-enabled等运行日志中的子类别主从同步、故障转移、集群状态变化

这里有个常见认知误区:很多人以为“慢查询日志”是一个文件。不是的,它默认只存在内存里,就是一个环形缓冲队列,用slowlog get才能看,一旦重启进程,慢日志清零。所以如果你指望靠文件去回溯昨天的慢查询,纯属想多了。想要长期保留,你得周期性用slowlog get把它捞出来写进自己的采集系统,这一点后文我会给具体方案。

还有一点要提醒:持久化日志和运行日志是两个概念。Redis的AOF文件(appendonly.aof)是数据恢复用的,RDB文件是快照,它们不承担“给人看”的职责,但运行日志里会记录“什么时候开始RDB保存”“什么时候完成AOF重写”,这些线索对排查持久化故障非常关键。所以看日志别只盯着error级别,info级别里的Background saving started、Background AOF rewrite finished successfully这类信息,同样藏着重要的配置反馈。

2. Redis日志相关配置项逐个拆解

2.1 logfile、loglevel与输出目标

先看最基础的三个配置:logfile、loglevel、daemonize。它们决定了日志去哪、记得有多细、以及进程后台化之后日志是否还能存活。

logfile的默认值是空字符串,表示输出到标准输出。如果你用systemd托管Redis,日志会被journald接走,这个时候你执行journalctl -u redis能看到日志,这没问题。但如果你是裸机手动启动,并且设了daemonize yes,那么Redis在fork出子进程后,父进程退出,stdout没人接管,日志就直接丢了。这个坑我踩过不止一次——节点挂了,翻遍/var/log什么也没留下。所以裸机部署时,务必把logfile指定到一个真实路径,比如/var/log/redis/redis-server.log。

loglevel有四个级别:debug、verbose、notice、warning。生产环境我一般用notice,因为它能保留启动信息、主从同步信息、持久化事件,但又不会被大量调试信息刷屏。warning太极端,只把错误和严重告警记下来,会丢掉很多有价值的上下文。如果你正在排查某个诡异问题,可以临时调到debug复现一把,但要注意两点:一是debug日志量可能是notice的几十倍,磁盘很快会被写满;二是不要忘了改回来。

这里还涉及一个细节:很多发行版通过/etc/redis/redis.conf启动时,pid文件和日志目录的权限需要手工确认。Redis进程通常以redis用户运行,如果/var/log/redis目录不存在或者属主是root,Redis启动时不会自动帮你建目录,结果就是日志文件压根创建不了,进程起不来或者起来之后一直往stderr打错误。这时候去翻系统日志才能看到Failed to open the log file的报错,非常误导人。

2.2 慢查询日志的阈值与队列长度

慢查询日志相关的配置项只有两个,但这两个参数的设计逻辑值得好好讲。

slowlog-log-slower-than单位是微秒。默认值10000,也就是10毫秒。Redis只在命令执行完之后才判断它是不是慢查询,所以它统计的是命令自身的执行时间,不包括网络IO和排队时间。需要注意一点:如果你把阈值设成0,表示记录所有命令,这在测试环境方便,生产环境会把整个Redis拖出一个巨量的内存日志,不建议。如果你设成负数,表示关闭慢日志,也不建议。

slowlog-max-len表示内存里最多保留多少条慢日志,默认128。这个队列是先进先出的环形结构,超过长度后最老的记录会被淘汰。阈值设多少合适?我个人的经验公式是:先拿到业务P99延迟,再结合Redis单命令耗时毛估。比如业务要求99%请求在20ms内返回,那命令执行时间超过5ms就应该开始关注,慢日志阈值先设为5000微秒;观察一段时间发现误报太多(比如批量命令本来就需要几十ms,但属于正常业务),再往上调到10000;如果有大key隐患,甚至可以压到2000微秒。核心思路是:阈值宁可先紧一点,让慢日志帮你建立基线,再慢慢放宽,而不是一开始就设得很宽松,结果啥都抓不到。

慢日志的命令行操作有SLOWLOG GET、SLOWLOG RESET、SLOWLOG LEN。注意SLOWLOG GET默认返回最近10条,你可以指定条数,比如SLOWLOG GET 100。另外,慢日志不会永久保存这个事,再强调一遍:Redis重启即丢。所以我后文会给一个“定时捞日志”的方案,把慢日志落盘到你的采集系统。

2.3 持久化与复制里的日志线索

RDB和AOF相关的配置也会直接决定你会看到什么样的日志。比如save配置若为空(save ""),表示关闭RDB自动快照,那么运行日志里永远不会出现Saving DB相关的记录。你在排查“为什么Redis启动后数据少了一截”时,一旦发现日志里根本没有RDB保存记录,就要怀疑是不是快照配置本身就没生效。

AOF方面,appendonly yes后,appendfsync的取值对日志和性能影响很大:always每次写入都刷盘,慢但安全;everysec每秒刷一次,折中;no交给操作系统决定,快但有丢失风险。日志上,AOF重写时会打印Background AOF rewrite started,结束后会有Background AOF rewrite finished successfully。如果你看到AOF rewrite in progress长时间不结束,多半是磁盘IO瓶颈,或者某个大key导致重写进程过快增长内存。

再顺带提一下主从复制的日志线索。复制相关配置比如replicaof、repl-backlog-size、replica-read-only等,一旦配错或网络波动,运行日志会立刻给出信号。全量同步时常见日志是Full resync requested、MASTER <-> REPLICA sync started;增量同步出现问题时会看到Partial resynchronization not possible。如果你在配置里把repl-backlog-size设得太小,高并发写入时从库的offset很快超过主库backlog范围,就会被迫全量重同步,日志里会看到反复出现的Full resync。这种“配置引发的日志风暴”,往往比业务问题更值得先解决。

3. 一套可直接抄走的Redis日志配置方案

3.1 redis.conf日志配置片段

先给一套我在生产环境验证过的redis.conf日志相关配置,细节我都加了注释,可以直接抄:

# 日志级别:notice能保留启动、复制、持久化等关键事件 loglevel notice # 日志文件路径,一定要设置,别让日志裸奔在stdout logfile /var/log/redis/redis-server.log # 以守护进程方式后台运行 daemonize yes # 进程文件,注意目录权限 pidfile /var/run/redis/redis-server.pid # 慢查询阈值:超过5毫秒的命令记入慢日志 slowlog-log-slower-than 5000 # 慢日志最多保留200条 slowlog-max-len 200 # RDB快照策略,老生长谈的三个策略 save 900 1 save 300 10 save 60 10000 # AOF持久化,生产环境建议开启 appendonly yes appendfilename "appendonly.aof" appendfsync everysec

这里有几个细节需要注意。第一,logfile路径的目录/var/log/redis必须提前创建,并把属主改成redis用户,否则Redis启动时没权限建文件。第二,如果你同时用systemd管理Redis,日志的流向你得想清楚:是让Redis直接写文件,还是让日志进journald再统一采集。我建议直接写文件,因为journald默认会做大小限制,日志一多容易丢。第三,loglevel notice只是常规选择,如果你在某次重大变更后想看得更细,临时改成debug并配合CONFIG SET热加载就行,不需要重启,但记得改回notice。

说一下我为什么把slowlog-log-slower-than压到5000微秒而不是默认的10000微秒:因为我日常处理的业务对Redis响应时间很敏感,P99超过20ms就要告警,命令本身执行超过5ms已经是明显的性能隐患。如果你服务的Redis只是做简单KV缓存、流量不大,用默认的10000也能接受。总之这个阈值不是拍脑袋定的,它得跟你的业务延迟目标对齐。

3.2 日志轮转:别等磁盘爆了才处理

Redis的日志如果不做轮转,运行个把月就能把磁盘吃干净。我见过最夸张的一个实例,loglevel debug忘改回production,两天写了80GB日志,直接把系统盘干满。轮转方案我推荐用Linux自带的logrotate,简单可靠。

新建/etc/logrotate.d/redis,内容如下:

/var/log/redis/redis-server.log { daily rotate 30 compress delaycompress missingok notifempty copytruncate create 0640 redis redis }

逐项说一下:daily表示每天轮转一次;rotate 30保留30份,也就是一个月;compress把旧日志压缩;delaycompress延迟一天再压缩,避免Redis还没来得及写完就被压坏;copytruncate是Redis场景下的关键参数,它先复制日志内容到新文件,再清空原文件,这样Redis持有的文件描述符不会因为文件被rename而失效,也就不需要重启Redis进程。如果你用create而不是copytruncate,日志文件被rename后Redis会继续往旧文件写,你会在日志轮转后突然发现新日志“消失了”,这个坑特别经典。

另外,如果Redis日志里塞了大量慢查询条目或者错误堆栈,日志体积增长会非常猛。建议logrotate之外再挂一个监控:日志文件超过1GB就告警。有些团队图省事关掉Redis日志,我不推荐,因为主从同步、持久化、过期键清理这些事件是你排查故障的基础。

3.3 日志采集:用Filebeat把Redis日志接入统一平台

日志只在本地躺着价值有限,我习惯把Redis日志接入ELK或者类似平台,统一检索、统一告警。采集端我用得最多的是Filebeat,配置相当轻量。核心配置如下:

filebeat.inputs: - type: log enabled: true paths: - /var/log/redis/redis-server.log fields: service: redis server: ${HOSTNAME} fields_under_root: true tags: ["redis", "cache"] multiline.pattern: '^[0-9]+:[A-Z]' multiline.negate: true multiline.match: after output.elasticsearch: hosts: ["http://your-es:9200"] index: "redis-log-%{+yyyy.MM.dd}"

multiline那段配置是很多刚开始用Filebeat的人容易漏掉的。Redis日志通常每行以时间戳开头,比如1234:M 10 Jan 2025 10:00:00.123 * Background saving started,但异常堆栈可能跨多行,如果不做multiline合并,一条报错会被拆成好几条割裂的记录,检索起来极其痛苦。pattern: '^[0-9]+:[A-Z]'表示匹配“进程ID+级别字母”的行首,单次出现的行是新的日志事件,后面跟的堆栈行归并为同一条记录。

还有个细节:Redis日志里偶发的时间格式是“10 Jan 2025”这种英文月份,如果你的采集链路依赖时间格式做索引分片,建议在Filebeat里加processors把时间解析为规范格式,否则Kibana的时间轴会乱。这里我一般加一个timestampprocessor,把@timestamp替换成本地时间字段,具体视你的ELK版本而定。

如果你用Docker环境跑Redis,采集方式可以更简单:容器内Redis日志直接打到stdout,配合Docker的json-file驱动,Filebeat直接采集宿主机的/var/lib/docker/containers/*/*-json.log就行,不需要在容器里折腾Filebeat。但要注意,docker json-file日志默认不轮转,必须提前配置max-size和max-file,否则长期跑下来又是一个磁盘炸弹。

4. 慢查询日志分析与性能调优实战

4.1 读懂一条慢日志

慢日志用SLOWLOG GET查看,输出格式是这样的:

1) 1) (integer) 14 2) (integer) 1706176630 3) (integer) 12003 4) 1) "KEYS" 2) "*user*"

四个字段分别代表:慢日志的递增序号、Unix时间戳、命令执行耗时(单位微秒)、命令的具体参数数组。序号14说明这是第14条慢日志;1706176630换算成北京时间是2024年1月25日左右;耗时12003微秒也就是12毫秒;命令是KEYS *user*。这个命令就是明显的高危操作,我们下面详说。

慢日志里经常出现的一个问题是:你以为慢的是某个命令本身,但其实慢的是O(N)命令扫过了一个大Key。比如HGETALL一个包含几十万字段的hash,再比如SMEMBERS一个百万成员的set。日志里命令只显示名字和参数,不能直接告诉你Key有多大,所以你需要结合DEBUG OBJECT或者MEMORY USAGE去确认。这是慢日志分析最核心的一步:从“哪个命令慢”推到底层“哪个Key大”。

4.2 三类典型慢命令的处置方案

第一类是KEYS和SCAN的误用。KEYS在线上Redis绝对是禁区,它会阻塞整个实例。如果你在慢日志里看到KEYS出现,说明代码里有严重问题,必须改成SCAN游标遍历,分批次取。SCAN虽然是O(N)但它是增量式的,不会一次性阻塞太久,代价是多几次网络往返。

第二类是大Key上的集合操作。像HGETALL、SMEMBERS、LRANGE key 0 -1,这些命令的时间复杂度与集合大小成正比,数据量一上去就完蛋。慢日志里出现这类命令,我的处理流程是:先定位Key,评估它是不是热点;然后跟业务确认能否拆分,比如把大hash拆成多个小hash,或者把大list改成stream;如果暂时没法拆,至少要把这类操作用Lua脚本降级到异步,别让请求线程硬扛。

第三类是范围查询和排序。ZRANGEBYSCORE、SORT这类命令涉及排序和权重计算,在成员数量大时也是慢查询大户。优化思路通常是给数据结构瘦身、把冷数据下沉到其他存储,或者限制LIMIT范围,尽量减少扫描区间。

4.3 把慢日志跟业务指标对上

慢日志的另一个用途是验证你的缓存治理效果。我做过一个项目,缓存Key设计不合理,每次服务重启后大量缓存穿透,回源打到数据库,Redis里存的全是大集合。那时候慢日志几乎每秒钟都能看到几个大Key读操作。后来团队做了缓存治理:Key拆分、热点Key本地缓存、淘汰策略从allkeys-lru调整为volatile-lru,慢日志数量肉眼可见地下降。所以我建议每个团队都做一件事:每天定时抓慢日志,统计Top20命令和涉及Key,形成一份“Redis慢日志日报”。这比盯着监控大盘有用多了,因为它直接告诉你问题命令是什么。

采集的实现我写过一个小脚本,通过redis-cli定时连接Redis去取慢日志,再通过HTTP往日志平台推送。注意重试和去重,比如记录已处理的慢日志序号,只同步增量。脚本本身很简单,但核心思想是:慢日志是内存数据,不捞走就会丢,捞了他就是你做性能调优的素材库。

5. 常见问题与排查技巧实录

5.1 日志文件不存在或为空怎么办

这个问题我在群里被问过无数次。排查思路按顺序来:先确认logfile配置是否生效,执行CONFIG GET logfile;再检查日志目录是否存在且Redis用户有写权限,用ls -ld /var/log/redis看属主;然后确认Redis进程是否真的用了这个配置文件启动,ps -ef | grep redis看启动参数里的-c或--config指向哪个文件。如果以上都没问题但日志还是空的,还要看一下是不是logrotate的copytruncate把文件截断了还没写入新内容。

一个容易忽略的场景:如果你用Docker启动Redis且没有挂载volume,容器销毁后日志也没了。docker logs只保留当前容器生命周期的stdout,容器一旦删除,历史日志回不来。所以我建议容器化部署时要么把redis.conf的logfile指向挂载卷,要么就干脆不设logfile、全靠stdout + docker日志驱动采集。两者选一,别搞成“日志写进了容器里的文件,但容器一删就消失”的尴尬局面。

5.2 主从切换与复制故障里的关键日志

主从架构下,Redis日志是你判断复制健康的唯一现场。从库第一次连接主库,你会看到:

MASTER <-> REPLICA sync started Full resync requested MASTER <-> REPLICA sync started: Replica is trying to psync

全量同步期间主库会做RDB快照,日志里会跟着出现Saving a background fork。如果网络不稳定,你会看到反复的Master disconnection、Reconnecting。这个时候别光盯着业务层,先确认是不是repl-backlog-size配得不够,导致增量同步backlog被写穿,只能一次次全量重同步。我的调优经验是:把repl-backlog-size从默认1MB调到64MB甚至128MB,应对短时间的网络抖动和大量写。

还有一种情况:主从切换后新主库没有开启AOF或者RDB快照周期太长,运行日志里静悄悄,等到宕机才发现数据只恢复到n小时前。日志不会主动告警你“持久化配置不合理”,所以你要定期看日志里最近一次Saving DB的时间,如果超过你的预期RDB周期,赶紧查配置。

5.3 Redis日志问题速查表

日志现象常见原因处理方向
日志文件一直为空logfile未配置、目录无权限、stdout被丢弃查CONFIG GET、目录权限、systemd日志
日志疯狂刷Saving DBRDB周期过密或写入量过大调整save策略、错峰持久化、检查子进程内存
fork: Cannot allocate memory操作系统vm.overcommit_memory设置不当设置vm.overcommit_memory=1
AOF rewrite in progress久不结束磁盘IO瓶颈或大Key导致排查慢磁盘、拆分大Key
慢日志里有大量KEYS线上误用KEYS改为SCAN,代码层面禁止
主从反复Full resyncrepl-backlog-size过小调大backlog、查网络抖动
日志时间与本地时间不一致Redis没配时区或系统时间异常校准系统时间、统一日志时区
Possible SECURITY ATTACK detected收到可疑命令特征检查口令、ACL、防火墙,别慌着忽略

我特别想提醒一下最后一条。日志里如果出现Possible SECURITY ATTACK detected,很多人直接当成误报。这个日志背后的逻辑是:命令参数中出现了类似配置文件路径、.so后缀、eval脚本等敏感特征,可能是有人在尝试利用Redis写文件或者加载模块。收到这类日志,优先检查是否开启了protected-mode、是否设置了requirepass、ACL是否收紧。安全日志比性能日志更应该及时处理。

5.4 排查时的一条独家经验

最后分享一个我常用的排查技巧:别只盯着Redis日志本身,把它跟操作系统层面的dmesg和/var/log/messages对照着看。比如Redis日志里出现fork failed,原因可能是内存不足,也可能是系统有内存碎片压力,单看Redis日志根本看不出来。我遇到过一台机器Redis频繁卡顿,日志里没有任何error,最后是dmesg里看到了oom-kill的痕迹,原来是同一台机器上另一个进程把内存吃爆了。日志排查要交叉验证,Redis的日志只是现场的一部分。

6. 日志管理的一点个人体会

就在上个月,我还因为一套Redis哨兵模式的日志差点背锅。当时从库切换主库非常慢,应用侧一直在报连接超时。我盯着Redis日志看了半天,看到Sentinel new config epoch、Failover event,似乎一切正常。直到我顺手看了一眼maxmemory配置,才发现主库设置了allkeys-lru但内存已经到了上限,切换后新主库上线就要应付大量缓存驱逐和过期键清理,自然慢。这个教训让我养成了一个习惯:每次看Redis日志发现问题,都顺手把CONFIG GET maxmemory、CONFIG GET save、INFO replication一起拉出来对照,日志和配置从来就是同一件事的两面。

日志管理的本质,其实是给Redis配置做“审计”。你把配置改得再复杂,最终全靠日志来判断它健不健康。所以别再把日志当成事后补救的工具,从第一次部署Redis就把logfile、loglevel、慢日志阈值、轮转策略、采集链路配好,后续能帮你省下大量半夜查故障的时间。如果你现在正在处理一套日志裸奔的Redis,今天就动手把logfile和轮转补上,这个动作比任何优化都值钱。

返回列表