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

资讯详情

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

Ubuntu上Redis常见故障排查:从安装到运维的全链路指南

Ubuntu上Redis常见故障排查:从安装到运维的全链路指南

刚帮两个同事排查完Ubuntu上Redis的故障,一个是阿里云ECS,一个是自己电脑上的VMware虚拟机。问题一个比一个典型:源码编译make报错,日志里什么都没有服务就停了,远程死活连不上,重启机器Redis直接“失踪”。单独搜这些问题,答案散落在各个帖子,每次都要重新拼一遍。这篇我把这些年踩过的坑完整梳理出来——不针对单个报错,而是沿着Ubuntu上Redis从安装、启动、连接、运行到维护这条完整链路,把最常见的故障节点逐个拆开,给出可复现的排查方法和解决步骤。

核心关键词就三个:Ubuntu、Redis、常见故障及解决方法。适合刚接触Redis部署的人,也适合那些已经跑起来但隔三差五出点小毛病的运维朋友。

1. 安装阶段:make失败和装完缺胳膊少腿

1.1 make报错多半不是Redis的锅

很多人一搜“redis安装教程”,上来就让你源码编译,然后就在make这一步卡住。报错五花八门:

test.c:1:10: fatal error: stdio.h: No such file or directory

或者:

zmalloc.h:50:31: fatal error: jemalloc/jemalloc.h: No such file or directory

第一反应是Redis源码有问题?其实不是。Ubuntu最小化安装或云镜像不带完整编译工具链,stdio.h找不到代表连基础C库头文件都没有。解决方法很简单,先把基础依赖补齐:

sudo apt update sudo apt install -y build-essential pkg-config

build-essential包含了gcc、g++、make这些核心工具。装完再执行make,大部分情况能过。jemalloc那个报错,常见于某些旧版本Redis或特定架构下,可以绕开:

make MALLOC=libc

用libc自带的内存分配器替代jemalloc。性能上差异在日常场景基本感知不到,Redis在benchmark里用jemalloc略好,但开发环境、内部系统完全够用。官方文档也允许这个参数,不必有心理负担。

1.2 make成功但redis-server不存在的目录乌龙

还有个高频问题:make成功了,输入redis-server却提示command not found。原因多数是源码目录下它真的存在,只是你没在PATH里:

./src/redis-server

或者你make install没执行。make install才是把二进制复制到/usr/local/bin,这一步经常被人漏掉。如果执行了make install还找不到命令,检查/usr/local/bin是否在PATH里,或者干脆用绝对路径。

我遇到过更离谱的:有人下载了源码包,解压后直接在deps/hiredis子目录里乱敲make,然后说“Redis编译不过”。Redis的源码必须从根目录执行make,不要进子目录。这也算经验教训,遇到安装问题先确认你站在哪个目录。

1.3 用apt装还是源码装,想清楚再选

我个人的建议是分场景:

场景推荐方式理由
生产/正式环境apt或官方二进制包便于升级、依赖干净
需要特殊编译参数源码编译可自定义jemalloc、debug等
临时测试学习apt一分钟搞定

apt安装简单,但Ubuntu仓库里的Redis版本通常偏保守,2024年时主流版本在6.0.x左右。如果你需要新版本特性(比如Redis 7的functions、acl改进),就得走源码或官方redis.io提供的二进制。源码编译不是洪水猛兽,装好build-essential之后,也就是三分钟的事。

2. 服务起不来:前台正常、后台秒挂的诡异现象

2.1 明明启动成功,进程却消失了

这个故障特别迷惑人:在终端执行redis-server,一堆启动日志打出来,显示Ready to accept connections。然后你关掉终端或者用redis-server &放到后台,过一会儿进程就没了。

根本原因写在Redis自己的日志里。默认配置下Redis以非守护进程方式运行,前台模式下Ctrl+C或者关闭终端,SIGINT/SIGTERM直接把进程带走。你用&放后台,终端一关,会话结束,后台任务也跟着收尸。这不是Redis独有,是所有Unix进程的会话机制问题。

正确做法有三个:

# 方式一:改配置 sudo vim /etc/redis/redis.conf # daemonize yes # Redis 7.x之后改为 supervised,后面讲 # 方式二:启动时指定 redis-server --daemonize yes # 方式三:最推荐,用systemd sudo systemctl start redis

2.2 supervised参数和systemd单元文件的坑

Redis 6.0+默认配置里daemonize已经不是主流推荐方式,官方更希望你在systemd环境里用supervised指令:

supervised systemd daemonize no

配置了supervised systemd之后,Redis会通知systemd自己已经就绪,并让systemd管理进程生命周期。这样systemctl start redis和systemctl enable redis能够正常工作。

但是很多人在Ubuntu上遇到systemd服务起来后立刻失败的情况。用journalctl -u redis一看,报错往往是:

Can't open the log file: Permission denied

或者:

# Warning: /var/lib/redis is not writable

这是目录权限问题。apt安装的Redis会自动创建redis用户和/var/lib/redis目录,但源码编译后你手动创建目录,可能忘了chown:

sudo mkdir -p /var/lib/redis sudo chown redis:redis /var/lib/redis sudo mkdir -p /var/log/redis sudo chown redis:redis /var/log/redis

如果不想自己写systemd单元文件,优先用apt安装,它全给你配好了。源码装的话,官方已经提供utils/systemd-redis_server.service之类的模板,照着改路径即可。

2.3 日志没写对地方,等于没日志

不少“服务突然挂掉”的排查,卡在根本没日志。默认loglevel是 notice,输出到stdout,一旦你设置了daemonize yes,stdout被丢弃,日志去哪了?变成空。

建议把日志固定到文件,调试期直接调到debug或verbose:

loglevel notice logfile /var/log/redis/redis-server.log

注意目录要让运行用户可写。很多老手一上来就tail -f /var/log/redis-server.log,结果文件不存在——因为Redis压根没创建。用-作为logfile会把日志打到stdout,这个符号容易被忽略。

提示:排查任何Redis故障,第一步永远是从日志开始。日志不落盘的话,先修复日志配置再谈排查。

3. 连接被拒:本机能连、远程拒绝的联排问题

3.1 bind 127.0.0.1、protected-mode和密码三座山

从其他机器连不上Redis,是最经典的“常见故障”。绝大多数人卡在这几处:

bind 127.0.0.1 protected-mode yes # requirepass foobar

这三项是一套组合锁。bind 127.0.0.1表示只监听本机回环地址,外部网卡一律不服务。protected-mode yes是个安全兜底:如果你没设置密码,又没显式bind到内网地址,Redis只允许本机连接。requirepass没设置,等于大门敞开,但被保护模式挡住。

修改方法:

bind 0.0.0.0 protected-mode no requirepass your-strong-password

bind 0.0.0.0监听所有网卡。生产环境建议精确bind到内网IP,不要全开。每次改完配置,要重启Redis才能生效:

sudo systemctl restart redis

3.2 改了配置却不生效:你可能启动了一个“裸”Redis

这里有个巨坑,我见过好几次:配置文件改了bind和requirepass,systemctl restart redis,从远程用Redis Desktop Manager连接,还是超时或被拒绝。于是开始怀疑防火墙,查了半天没结果。

最后一看进程:

ps aux | grep redis-server

输出:

redis-server *:6379

注意这个*:6379意味着Redis没有加载你的配置文件。运行的是默认配置实例。systemd服务配置的ExecStart里写的是redis-server /etc/redis/redis.conf,但你可能曾经手动跑过redis-server,它占用了6379端口,systemd服务起了但端口被占,你连的其实是那个没有密码、没有配置的裸实例。

排查方法:

redis-cli -p 6379 info server | grep config_file

如果显示config_file:为空,说明当前运行的进程确实没加载任何配置文件。杀掉它,再用正确方式启动。

3.3 Ubuntu防火墙和云平台安全组的叠加效应

Ubuntu默认没开ufw防火墙,但很多人会开。检查一下:

sudo ufw status

如果状态active,需要放行6379端口:

sudo ufw allow 6379/tcp

但更隐蔽的是云服务器的安全组。阿里云、腾讯云这些平台,安全组规则没放行6379,即使服务器内部的ufw全开也没用。请求在云网络层就被拦了。这个排查顺序建议:先看云控制台安全组,再看服务器ufw,最后看Redis自身bind和protected-mode。按照这个顺序走,99%的“连不上”问题十分钟内能定位。

4. 运行期故障:内存、持久化和卡顿排查

4.1 设置了maxmemory,Redis还是被系统OOM杀掉

这属于“看似配置了却依然出事”的典型。很多人设置了:

maxmemory 512mb

以为内存封顶512MB,结果过几天Redis进程直接消失,dmesg一看OOM killer把redis杀了。

原因在于,maxmemory控制的是Redis自身数据占用的内存,不包括进程开销、碎片和复制缓冲区。比如主从复制场景下,master需要为每个slave维护输出缓冲区;还有客户端输入缓冲区,当有慢消费客户端时,这边数据堆积起来也能吃掉几百MB。这些内存统统不受maxmemory约束。所以512MB的maxmemory配合高写入流量,总内存轻松涨到1GB以上,触发系统OOM。

解决方案分两层:

  • 设置系统层面overcommit,降低被杀概率:
sudo sysctl vm.overcommit_memory=1

写入/etc/sysctl.conf持久化。Redis官网也建议这个值设为1,因为RDB持久化时fork子进程需要写时复制内存,overcommit_memory=0时可能fork失败。

  • 给保留内存余量。maxmemory建议设置为物理内存的50%-60%,同时监控RSS:
redis-cli info memory | grep used_memory_rss

used_memory_rss才是实际占用的物理内存,如果它逼近物理内存上限,说明maxmemory设置得太高,需要降。一般让used_memory_rss保持在物理内存的70%以下比较稳。

4.2 磁盘空间没满,RDB持久化却一直失败

另一个常见故障:日志里频繁出现:

Background saving terminated by signal 2

或者:

Failed opening the RDB file temp-xxx.rdb (in directory ...) for saving: No space left on device

但是df -h一看,磁盘还有几十GB空闲。这就有意思了。问题通常出在磁盘.inode耗尽。Ubuntu的临时目录或Redis数据目录所在分区,inode用完了。查看方法:

df -i /var/lib/redis

如果IUse%到100%,即使有剩余空间也无法创建新文件。清理一下临时文件,或者把Redis的dir换到inode充足的分区。

还有一种情况:Redis配置的dir目录不存在。比如你手动指定:

dir /data/redis

但/data/redis从来没创建过,Redis启动时不会报错(它只在持久化时才去创建临时文件),到了持久化周期才疯狂报错。提前确认:

mkdir -p /data/redis chown redis:redis /data/redis

4.3 慢查询和延迟排查:不是所有卡顿都是慢命令

线上Redis偶发卡顿,客户端报超时,比如热词里那个redis command timed out; nested exception is io.lettuce.core.rediscommandtim...,英文都给你截断了。用慢日志看:

redis-cli slowlog get 50

显示耗时最长的命令。但这里有个关键点:slowlog只记录命令执行时间,不包括网络排队和客户端等待。如果slowlog里没有明显慢命令,客户端却感觉卡顿,问题可能在别处:

  • big key的序列化传输。一个几MB的string,GET命令本身可能只花几毫秒,但网络传输占满带宽,拖垮整个客户端连接。用redis-cli --bigkeys扫描。
  • fork造成的停顿。RDB持久化触发fork时,如果内存占用大,fork会阻塞主进程几毫秒到几百毫秒。日志里能看到fork耗时。
  • swap。如果Redis内存被交换到磁盘,任何操作都可能卡顿。检查:
cat /proc/$(pgrep redis-server)/status | grep Swap

Swap数值大说明内存不够用了,加内存或者降maxmemory。

排查卡顿的正确思路:先slowlog排除慢命令,再检查big key,再确认swap和fork,最后看系统网络。一步一步来,不要一卡就往“Redis性能不行”上想。

4.4 数据结构误用:Redis变慢的隐性杀手

有些故障不报错,只是越来越慢。常见案例:把大集合进行O(n)操作,或者一个list存了上百万字符,执行LRANGE全量拉取。这些命令执行期间会阻塞Redis单线程,其他请求全部排队。

排查技巧:

redis-cli --bigkeys

它会统计各类型key的max size。另一个命令看运行时状态:

redis-cli info stats | grep instantaneous

配合客户端超时时间,可以判断阻塞持续周期。

还有一个容易忽略的:过期key的惰性删除。大量key在同一秒过期,Redis会在主线程尝试集中清理,造成瞬间卡顿。解决办法是过期时间加随机抖动:

expire_time = base_ttl + random.randint(0, 300)

Redis 6.0还提供了lazyfree-lazy-expire yes,把过期key回收放到后台线程,能在一定程度上缓解。

5. 运维工具和日常维护中的高频坑

5.1 redis-cli中文乱码和--raw参数

用redis-cli查一个value是中文的key,返回结果是"\xe6\xb5\x8b\xe8\xaf\x95"这种转义序列,初学者以为数据存坏了。其实Redis返回的是二进制安全内容,redis-cli默认按转义字符串显示。加--raw:

redis-cli --raw get test-key

就能看到原始中文。如果是批量导入导出,这个参数也常用。redis-cli --raw配合管道处理数据,是Shell脚本操作Redis的必备姿势。

5.2 可视化客户端连接失败的隐蔽原因

热词里出现了好几个Redis可视化工具。Another Redis Desktop Manager、Redis Desktop Manager连接不上,除了前面说的bind、密码、防火墙问题,还有两类隐蔽原因:

第一类是SSH隧道配置。很多云服务器只开了22端口,用户通过SSH隧道连Redis。工具里配置SSH Tunnel时,填了SSH密码,却忘了工具连接的是隧道的local port,或者误把SSH的host填成数据库host,导致连接失败。这类问题从命令行验证最直接:

ssh -L 6379:localhost:6379 user@server

然后本地连接127.0.0.1:6379,能连上说明工具配置问题,连不上说明Redis本身问题。

第二类是版本兼容性。一些旧版可视化工具对Redis 6的ACL和新密码机制支持不好。Redis配置了ACL用户,但工具只支持requirepass方式连接。碰到这类情况,升级工具版本,或者临时用默认用户测试。

5.3 Ubuntu重启后Redis没自动启动的真相

这个问题在“ubuntu系统重装”和“ubuntu的html编辑器”这些热搜旁边,看着不起眼,但实际很常见。很多人配置了Redis,也用了systemd,但重启后Redis不见了。检查:

sudo systemctl is-enabled redis

如果输出disabled,设置开机自启:

sudo systemctl enable redis

还要注意:源码编译安装的人,自己写的systemd服务文件可能因为路径不对导致enable失败。而且Ubuntu某些云镜像默认/tmp是tmpfs,如果你把Redis的dir或logfile放在/tmp下,重启后数据全部消失。Redis的数据目录、日志目录绝不要放/tmp。

另外,用docker安装redis主从这类方案(热搜词里有),重启后容器不自动启动,需要设置restart策略:

docker run -d --restart unless-stopped --name redis -p 6379:6379 redis:7

unless-stopped在宿主机重启后会自动拉起容器,除非你手动stop过它。

6. 进阶场景:Docker、主从和分布式锁背后的隐患

6.1 Docker跑Redis:目录权限和网络模式别踩雷

Docker安装Redis看似简单,实际坑不少。最常见的是数据卷权限问题:

docker run -d -v /data/redis:/data redis:7

host上的/data/redis权限不对,容器内redis用户无法写入,持久化直接失败。解决:

mkdir -p /data/redis chown 999:999 /data/redis

Redis官方镜像里redis用户UID是999。这一步不做好,日志里会出现“Can't open the appendonly dir: Permission denied”。

网络模式也要注意。很多人用-p 6379:6379映射端口,然后从外部连接失败。如果Redis配置文件里设置了bind 127.0.0.1,容器内的Redis只监听自己的回环地址,外部流量进不来。Docker映射端口时,Redis配置需要监听0.0.0.0。也就是容器内要能接受外部连接,这和你本地跑Redis的逻辑不同,容易方向搞反。

6.2 主从复制延迟和脑裂场景

Docker安装redis主从,玩的是:

docker run -d --name redis-slave redis:7 redis-server --slaveof master-ip 6379

看似起来了,但slave同步滞后,或者发生切换后数据不一致。这里要分清复制延迟和主从切换两个维度。

复制延迟排查:

redis-cli info replication

关注master_repl_offset(master写入偏移量)和slave_repl_offset(slave复制偏移量),两者差距大说明网络带宽不足或同步命令太大。第二种常见原因是slave开启了AOF,且appendfsync everysec,磁盘写速跟不上。

脑裂问题更隐蔽:主从架构里,master由于网络分区被隔离,客户端还在向它写入,新选举的slave变主,分区恢复后旧主恢复,但它的数据和新主不一致。Redis Sentinel本身不会解决数据冲突,它只保证可用性。要缓解这个问题,需要设置最小写入节点数:

min-replicas-to-write 1 min-replicas-max-lag 10

这意味着master至少要有1个健康从节点才接受写入。分区后master失去所有从节点,拒绝写入,避免脑裂期间产生新数据。当然,牺牲了一点可用性换一致性,业务要能接受。

6.3 Redis分布式锁在故障下会怎么失效

热搜词里有“redis分布式锁”,这话题跟故障排查强相关。很多人用SETNX实现分布式锁,时间到了自动过期。常见故障场景:

SET lock_key owner_token NX PX 30000

如果锁的持有者处理时间超过30秒,锁过期,另一个线程拿到锁,两个线程同时执行临界区。这不算Redis故障,是业务设计问题。更隐蔽的是主从切换导致锁丢失:线程A在master上拿到锁,master还没同步到slave就宕机了,slave提升为master,线程B在旧slave(新master)上拿到同一个key的锁,两个线程都认为自己持有锁。

唯一可靠的规避方案是Redlock算法,或者用Etcd、ZooKeeper这类强一致组件做分布式锁。Redis官方对Redlock的适用场景本身有争议,简单理解:Redis分布式锁适合能容忍极小概率失效的业务,不能容忍的双写场景不要用它。

另外有个实操建议:value里存一个唯一随机token,释放锁时用Lua脚本校验token再删除,避免误删别人的锁:

if redis.call("get", KEYS[1]) == ARGV[1] then return redis.call("del", KEYS[1]) else return 0 end

这是很多人忽略的细节,不写token的分布式锁,在延迟抖动的环境下很容易出事故。

排查Redis故障这么多年,最大的体会是:Redis自身代码带来的故障概率极低,绝大多数问题出在部署方式、配置细节和外部环境这三层。安装阶段先把依赖和目录搞清楚,运行阶段先看日志再看指标,连接问题按防火墙、Redis监听、客户端的顺序排查。只要不凭感觉瞎改配置,按链路一步步验证,大部分问题都能在半小时内定位。我建议每台服务器上都预先写一个checklist:日志路径、配置文件路径、systemd状态、端口监听情况、内存RSS、磁盘inode。故障来的时候照着查一遍,比临时百度高效太多。

返回列表