服务器之 Redis:从零搭建到生产级排障的完整实战笔记
Redis 在服务器端的重要性,根本不需要我再多吹。只要你的系统扛过一定的并发,Redis 基本就是那根绕不开的“救命稻草”。它是高性能键值存储服务器,能做缓存、分布式锁、消息队列、排行榜,甚至直接把数据库顶到前面扛读流量。这篇文章我会从零开始,把服务器上部署 Redis 的完整链路捋一遍:安装选型、配置避坑、数据类型怎么用、缓存治理怎么做、分布式锁怎么写、线上问题怎么查。适合刚接触 Redis 的运维,也适合正在把 Redis 从“能跑”升级成“生产可用”的开发者。所有内容都是我实际踩过坑之后的总结,不会给你抄那些官方文档。
在开始之前先说下我的环境参考:两台 CentOS 7.9 / Rocky Linux 8 的服务器(一台 4C8G,一台 8C16G),Redis 版本从 5.x 一直用到现在的 7.2,应用以 Java + Spring Boot 为主。后面所有操作都是在普通用户下执行,需要 root 权限时用 sudo,全程不涉及任何敏感操作。
1. 服务器上部署 Redis:为什么它总在缓存层的第一位
1.1 为什么你的服务器一定要有个 Redis
很多第一次接触服务器的人会问:我现在数据库都用了 MySQL,为什么还要在中间加一个 Redis?我通常用一个比喻解释:MySQL 就像一个大仓库,东西都放在仓库里,找起来慢、盘点也慢;Redis 就像仓库门口的一排货架,把最常用的货直接摆上去,伸手就能拿到。你每天有很多读请求,总不能每次都去仓库翻箱倒柜,那样仓库迟早被打爆。
实际场景里,我见过一台 MySQL 在 3000 左右的 QPS 下就开始出现慢查询、连接池堆积,而同样的服务前面挂一个 Redis,读请求全部打到 Redis 上,单实例轻松扛 5 万到 10 万 QPS,MySQL 的压力瞬间降到几百。所以从架构角度讲,Redis 不是可选项,而是解决“服务器撑不住高并发”的最廉价手段。
另外 Redis 不止做缓存。它的“原子性操作 + 过期机制 + 持久化 + 发布订阅”这些特性,让它可以承担分布式锁、限流计数器、会话共享、排行榜、消息队列等工作。也就是说,你在一台服务器上部署好 Redis,它就相当于给你的整个系统架构补上了好几个重要组件。
1.2 部署前的容量规划:内存、CPU 和网络
我见过太多人直接yum install redis装上就用,结果上线第二天内存不够了才来做优化。在装之前你一定要算清楚三件事。
第一是内存预算。Redis 的数据全部在内存里,这一点和 MySQL 不同,MySQL 可以把热数据留在 buffer pool,冷数据留在磁盘,但 Redis 没有这个层级。你要根据业务峰值估算数据量。我有一个简单的估算公式:最终常驻内存数据量 = 单条 key 的平均大小(bytes) × key 总数 × 1.5 到 2 的膨胀系数。膨胀系数是因为 Redis 自身的数据结构、dict entry 头部、过期信息都会显著增加内存占用。比如你计划存 1 亿个用户 token,每个平均 100 字节,那你至少需要100 * 100000000 * 1.5 ≈ 15GB的内存,那台服务器就不能只配 8GB,不然 redis 进程会在内存写满后触发淘汰策略,最坏情况下把刚写的缓存全部清掉,引发缓存雪崩。
第二是 CPU。Redis 是单线程模型,这不是它的缺点,反而让它没有锁竞争、上下文切换开销小。但单线程意味着单个 Redis 实例只能吃满一个 CPU 核心,如果你这台服务器是 32 核,开一个 Redis 实例只能用到其中一核。所以配置高一点的路由器建议直接部署多个实例(如每个端口 6379 / 6380 / 6381),或者用 Redis Cluster 自带的多分片机制。不过对大多数中小企业,一个主从架构或者哨兵架构就够用了,没必要上来就上 Cluster。
第三是网络。Redis 的吞吐量强,也意味着它对网络的消耗非常快。如果业务是跨机房访问,RTT 带来的延迟会直接主导服务性能。比如你从华东访问华南的 Redis,每次命令 30ms 延迟,就算 Redis 本身 0.1ms 也没用。部署时尽量和应用同机房、同 VPC 网络,跨机房的场景一定考虑内网域名 + 专线。
1.3 三种安装方式实测对比:包管理器、编译源码、Docker
装 Redis 有几种常见方式,我把实际体验给你们列出来,省得你们走弯路。
方式一:系统包管理器(apt / yum / dnf)
CentOS 默认源里的 redis 版本通常很老(3.2 菜),但如果用 EPEL 源能到 5.x。Ubuntu 20.04 的 apt 源自带 redis 5.0,22.04 自带 6.0。好处是安装简单、systemd 服务都帮你配好了,适合快速验证和简单的内部服务。坏处是版本不跟手,很多新特性(如 ACL、Redis Functions)用不了。如果只是为了做个测试环境,这方式足够。
方式二:编译源码安装
这是生产环境最推荐的方式。因为你可以自定义安装路径、指定编译参数,拿到最新稳定版源码后自己把关。我一般在/opt/redis部署,步骤是下载源码、解压、make && make install,然后自己写 systemd unit 文件。编译对机器的构建环境有要求,需要gcc、make、pkg-config等,Redis 源码本身不算大,编译也就几分钟。需要注意的一点是,编译前最好看一下README.md,有些版本需要不同的BUILD_TLS=yes参数才能启用 TLS 支持。
方式三:Docker 容器
如果你服务器上已经全面容器化管理,那 Docker 跑 Redis 非常合适。官方镜像redis和redis/redis-stack-server都是直接可用。但有一个关键点要记住:容器里的 redis 默认无法数据持久化(除非挂载 volume),你需要在启动命令里挂一个宿主机目录,并且加上--appendonly yes。我经常看到有人在容器里跑 Redis 重启后数据全丢,就是没挂数据卷。此外,Docker 网络对访问延迟有一定影响,如果是低延迟场景,裸机或宿主机部署更容易把控。
我这里给一个我比较满意的裸机编译安装过程(结合生产习惯)供参考:
# 下载解压 wget https://download.redis.io/releases/redis-7.2.4.tar.gz tar xzf redis-7.2.4.tar.gz cd redis-7.2.4 # 编译 make -j$(nproc) make install PREFIX=/opt/redis # 建立目录和配置 mkdir -p /opt/redis/etc /opt/redis/data /opt/redis/logs cp redis.conf /opt/redis/etc/redis.conf # 创建专用用户(不要用 root 跑 redis) sudo useradd --system --no-create-home redis sudo chown -R redis:redis /opt/redis之后用 systemd 管理,unit 文件精简如下:
[Unit] Description=Redis Server After=network-online.target Wants=network-online.target [Service] Type=simple User=redis Group=redis ExecStart=/opt/redis/bin/redis-server /opt/redis/etc/redis.conf ExecStop=/opt/redis/bin/redis-cli -p 6379 shutdown Restart=on-failure LimitNOFILE=65535 [Install] WantedBy=multi-user.target关于为什么不能直接 root 运行,我后面会专门说,服务器安全是个大话题,Redis 被攻击的例子太多了。
2. 核心配置:从默认参数到一套可用的生产级参数
2.1 内存策略与持久化怎么选:RDB、AOF 还是混合
Redis 默认配置的save 3600 1 300 100 60 10000是 RDB 快照策略,配合 AOF 关闭。这在开发环境没问题,但生产环境如果只开默认配置,故障恢复时可能会丢一段时间的数据。你需要想清楚你的业务到底能不能接受丢失最近几十秒到几分钟的数据。
RDB 是“全量快照”:它会 fork 一个子进程,把内存中的数据生成一个压缩的二进制文件 dump.rdb。优点是恢复快、文件小、对性能影响相对小。缺点是快照之间如果服务器宕机,中间写入的数据全部丢失。另外,如果数据集特别大,fork 子进程瞬间会消耗大量内存和 CPU,甚至导致主进程阻塞。我见过一台 64GB 内存的 Redis,开着大 RDB 配置,做快照的那几十毫秒内客户端超时,后来我调整了 save 策略才解决。
AOF 是“追加日志”:每次写命令都会追加到 aof 文件末尾。优点是最多丢 1 秒的数据(如果你设置appendfsync everysec),缺点是文件比 RDB 大很多,恢复速度也慢,极端情况下fsync会造成一定的 IO 压力。
生产上我现在更推荐混合持久化:aof-use-rdb-preamble yes,即 AOF 文件开头用 RDB 格式压缩已有数据,后续用 AOF 追加新命令。这样既有 AOF 的可靠性,又有 RDB 的快恢复速度。Redis 4.0 以后就支持这个特性,默认开着,非常推荐。
配置上面,我建议至少给以下参数留出位置:
appendonly yes appendfilename "appendonly.aof" appendfsync everysec no-appendfsync-on-rewrite yes auto-aof-rewrite-percentage 100 auto-aof-rewrite-min-size 64mb save 3600 1 300 100 60 10000 stop-writes-on-bgsave-error no rdbcompression yes rdbchecksum yes注意stop-writes-on-bgsave-error,默认配置是 yes,意思是如果 RDB 写盘失败,Redis 会拒绝所有写入请求,这在磁盘故障时能保护数据,但也可能阻塞业务。生产环境我一般改成 no,因为磁盘临时故障时,我宁愿让业务继续跑,也不愿直接停止写入。但这个决策需要结合你的具体场景,不要盲改。
2.2 网络与安全:绑定 IP、设置密码、ACL 权限控制
Redis 默认只绑定 127.0.0.1。如果你只是本机应用访问,这个配置最安全,完全不需要担心被外网扫描。但如果要跨服务器访问(比如应用服务器连独立的 Redis 服务器),必须修改bind配置,指定内网 IP 或 0.0.0.0(不建议)。
绑定方式我推荐用子网或内网 IP,而不是直接 0.0.0.0。比如:
bind 192.168.1.10 protected-mode yes port 6379如果同时设置protected-mode yes,Redis 只允许绑定地址来源的客户端访问。这样比单纯放防火墙更可靠。
密码方面,5.0 以上我基本不用requirepass这种全局密码,而是改用 ACL 细粒度权限:
user default off user app on >AppP@ssw0rd_2024 ~cache:* +@all -@admin上面创建了一个app用户,密码是AppP@ssw0rd_2024,只能操作cache:前缀的 key,且禁用了 admin 命令。ACL 真正好用,能避免某个客户端flushall把整个库清掉,这种事故我见过不止一次。
还有一个极其重要的安全设置:禁用危险命令。即使有密码,我依然建议在配置里把重命令重命名或直接禁用:
rename-command FLUSHALL "" rename-command FLUSHDB "" rename-command KEYS "" rename-command SHUTDOWN ""单独提一下KEYS,它是 Redis 性能杀手。生产环境禁止用 KEYS*去匹配大量 key,一旦 key 数量过百万,它会阻塞 Redis 单线程执行,线上直接“假死”。排查的时候用SCAN替代KEYS,这是基本功。
防火墙层面,我习惯配合 firewalld 或 ufw 只放行应用服务器 IP 到 Redis 端口。如果 Redis 端口被暴露在公网,再强的密码也可能扛不住暴力破解,恶意扫描工具专挑 6379 下手。
2.3 日志与监控:别等故障了才来看
Redis 默认把日志打到 stdout,在 systemd 环境下可以直接journalctl -u redis查看。但我更推荐把日志指向文件,配置里一行logfile "/opt/redis/logs/redis.log",然后把 logrotate 配上,避免日志无限膨胀。
监控方面,我不喜欢过度建设。如果你刚上手,用两招就够:
INFO stats、INFO memory手动看一眼,尤其注意used_memory、used_memory_rss和瞬时instantaneous_ops_per_sec。- 部署一个简单的采集脚本,把
INFO的字段推送给监控系统。如果要更高级的,可以用 Redis 自带的redis-cli --latency-monitor,或者用 Prometheus 的redis_exporter。
我这里分享一个实时查看 Redis 状态的常用命令:
redis-cli -h 127.0.0.1 -p 6379 -a 'AppP@ssw0rd_2024' --latency-history你会看到类似输出,用于判断网络延迟是否正常。如果 avg 稳定在 0.1ms~0.5ms 算正常,如果持续超过 5ms,就要检查服务器负载、网络抖动和 Redis 慢查询了。
3. Redis 核心数据类型:每个场景该用哪个,别再用错
3.1 五大数据结构的基础与应用映射
Redis 不是一个“大 Map”这么简单。它的五种核心数据结构有各自的使用姿势,很多人项目里不管什么都用 string,结果性能和容量都很吃亏。
String(字符串):最经典。适合存 session token、验证码、商品库存、接口缓存。内部可以存任意二进制字节。但请注意,它不是无限大,单个 value 最大 512MB,虽然实际不会有人这么存。做计数器时可以用INCR/DECR,能做到线程安全的自增,无需加锁。
Hash(哈希):适合存对象。比如用户信息、商品详情、配置项。每个 hash 里面可以有几十个 field。为什么不用 string 去拼 JSON?因为 Hash 支持单个 field 的增删改查,对比幂等来说省流量、省内存。如果你要更新用户头像,直接用HSET user:1001 avatar "新地址",不需要把整个 JSON 取出来反序列化再写进去。
List(列表):底层是链表或压缩列表,适合做任务队列、消息队列、粉丝列表、时间线。LPUSH+BRPOP是一个标准的阻塞消费模型,常用于生产者消费者场景。如果你把 List 当消息队列,生产端LPUSH,消费端BRPOP阻塞等待,能实现一个轻量可靠的队列。但要注意,List 不提供消息确认机制,消费者取出后崩溃,消息就丢了,这个和专业的 MQ 比还是有差距。
Set(集合):无序去重,适合做标签、黑白名单、共同好友,还能做交集、并集、差集计算。比如运营要做“给看过A活动但没看过B活动的人推送”,一个SDIFF就计算出结果。它的去重能力天然适合 UV 统计。
ZSet(有序集合):为每个元素附加一个 score,按 score 排序,适合排行榜。ZADD加分数,ZRANGEBYSCORE查区间,ZREVRANGE取排名。秒杀系统的限流、在线用户列表、延迟队列都可以用 ZSet 做。例如延迟队列:score 存执行时间戳,开启一个定时任务用ZRANGEBYSCORE获取到期任务执行。
实操中,我经常看到一个经验不足的人把所有业务数据都丢进 string,导致 keys 数量极多,然后内存和序列化效率双双变差。建议按数据领域的访问模式选择结构,比如一个“用户对象”用它自然应该用 Hash,一个“用户的好友列表”是 Set,一个“按时间排序的新闻列表”用 List,一个“按热度排序的热点列表”用 ZSet。选好结构以后,性能提升和内存节省是立竿见影的。
3.2 高级结构:Bitmap、HyperLogLog、GEO、Stream 的加分用法
除了五种基础类型,Redis 还有几个经常被忽略、但非常能打的特殊类型。
Bitmap(位图):用最小存储做统计。比如记录一个用户一年 365 天是否登录,用 string 类型配合 SETBIT / BITCOUNT,每天只占 1 bit,一年也就 46 字节/用户。一亿用户年登录状态约 46MB,比用 set 或 string 存省太多。这个常用于签到、在线状态、活跃用户统计。
HyperLogLog(基数统计):用固定 12KB 内存估算几亿不同值的数量,误差在 0.81%。如果要算独立访客 UV,不需要精确到个位数,用 PFADD / PFCOUNT 非常完美。比如要统计今天访问页面的用户数,你只需要PFADD today:uv userId1 userId2 ...,最后PFCOUNT就能得到近似 UV,内存占用固定,不随用户数量增长。但要注意它无法反查具体有哪些用户。
GEO(地理位置):Redis 3.2+,基于 ZSet 实现,可以存经纬度,计算两点距离,以及查找“附近的人”。GEOADD添加位置,GEOSEARCH按半径搜索。做附近门店、周边推荐非常实用。
Stream(流):Redis 5.0 加入的全功能消息队列,支持消费者组、确认、重试。如果你不想引入 Kafka / RabbitMQ,Stream 一定比 List 更可靠。它有XADD写消息,XREADGROUP消费,XACK确认,XPENDING查看未消费列表。我用它做过一套轻量任务分发系统,替代了原来基于 MySQL 的状态轮询机制。
对于大多数人来说,掌握基础和 Bitmap / HyperLogLog 已经能在服务器资源上省不少钱。Stream 则在你准备把 Redis 当 MQ 的时候再深入。
3.3 序列化方式与 key 设计的门道
Redis 本身存的是字节流,你怎么序列化对象,直接影响性能和兼容性。常见有 JDK 原生序列化、Jackson / Fastjson、Protobuf、Kryo 等。
如果你用 Spring Boot,默认 RedisTemplate 的序列化器是 JdkSerializationRedisSerializer。这个有一个巨大问题:序列化后的内容带了一堆类名信息,体积膨胀,并且在反序列化时容易出现类版本不一致。第一次用 Redis 时我就踩过这个坑——一台服务器的 JDK 版本变化,缓存里的旧对象就反序列化失败。更麻烦的是,JDK 序列化后二进制里面有大量自己的类结构信息,用redis-cli --scan看着全是乱码,也不方便调试。
我现在采用“String 一层通吃”的方式:Value 用 Jackson 或 Fastjson 序列化成 JSON 字符串;如果哈希结构,field 对应的 value 也是 JSON 字符串;如果追求极致性能,用 ProtoBuf 或 Kryo,但要考虑到后续跨语言消费问题。不要过度优化,先用方案简单、排查方便的方式,等到性能瓶颈再逐热点优化。
key 的命名规范我强烈建议提前统一。我一般用业务名:模块名:id[:属性]格式,比如:
user:profile:1001 order:detail:20240101123456 cache:hot:goods:2001这样好处很多:一是好排查,一个扫描或一次匹配就能看到一类 key;二是配合 Cluster 时哈希槽分布更合理;三是避免 key 冲突;四是 ACL 能按前缀精细化控制。我见过有人用无规则的 key,如abc123x、xxxx2024,这种项目维护到后期,没人知道这个数据是谁存进去的,也不敢删,纯属给自己埋雷。
4. 缓存治理与分布式锁:线上最容易翻车的两座山
4.1 缓存穿透、击穿、雪崩:对症下药
很多服务器扛不住大流量,不是 Redis 本身不行,而是使用姿势不对。常见的“缓存三兄弟”,你只要开过线上,大概率都遇到过。
缓存穿透:请求查询一个不存在的 key,缓存里没有,数据库里也没有,每次请求都透传到数据库。如果恶意用随机 key 打你,数据库会被打爆。解决方法有几种:一是把空值也缓存起来,设置短过期时间(比如 5 分钟),但注意如果大量空 key 会让 Redis 被垃圾数据占满;二是用布隆过滤器,先把所有可能存在的 id 过滤一遍,不存在直接返回;三是网关层做参数校验。我现在常见做法是空值缓存 + 限流兜底。
缓存击穿:某个热点 key 在过期瞬间,大量请求同时穿透到数据库。解决手段是互斥锁或逻辑过期。互斥锁就是在缓存未命中时,只让一个线程去查库并写缓存,其他线程等待。逻辑过期就是不给 key 设置物理过期,value 里存放过期时间,后台线程专门检查并更新缓存。前者适合对一致性要求高的场景,后者适合高并发读多写少的热点。
缓存雪崩:大量 key 同时过期,或者 Redis 实例挂掉,导致流量全部打到数据库,数据库跟着挂。解决方式有三层:过期时间加随机偏移;用高可用架构避免单点(主从 + 哨兵);降级策略,数据库暂时不可用时直接返回默认值或友好错误页,不让请求打到底层。
我做缓存治理时,会先画一张“读写链路图”:读请求走缓存,缓存未命中走数据库,写请求先更新数据库再删缓存。接下来关键难点是顺序:到底是先更新数据库再删缓存,还是先删缓存再更新数据库?我做过总结,稳妥方案是“先更新数据库,再删除缓存”,因为并发更新时,删除缓存后哪怕有一个老请求把旧值写回缓存,也会在下一次更新后删除,最终一致。如果反过来,先删缓存再更新数据库,中间很短的时间窗口其他线程会读到数据库旧值,容易不一致。
4.2 分布式锁:别自己造轮子,但原理必须懂
“Redis 分布式锁”是面试高频题,也是线上实际要用的东西。
最基本的版本是 SETNX + 过期时间:
Boolean locked = redisTemplate.opsForValue().setIfAbsent("lock:order:1001", "value", 30, TimeUnit.SECONDS);这个命令等于“只有在 key 不存在时才能设置成功并自动过期”,标准姿势。没有它的话,普通 SETNX 锁能获得但永远不会释放,需要手动 EXPIRE,异常情况下锁就死在那了。
但只靠原生命令还不够稳妥。比如锁的持有者是 A,当 A 还在执行业务时锁因为超时自动释放,B 拿到锁进入,此时 A 执行完释放锁,直接把 B 的锁释放了。这个 bug 只靠简单 SETNX 是防不住的。你必须保证释放的是自己的锁,常见的做法是 value 放一个唯一 ID,释放前比对确认,再用 Lua 脚本保证“读取 + 判断 + 删除”是原子的。
这段 Lua 脚本是标准答案:
if redis.call("get", KEYS[1]) == ARGV[1] then return redis.call("del", KEYS[1]) else return 0 end如果对可靠性有更高要求,就要考虑 Redisson 的看门狗自动续期机制。Redisson 默认会给锁加 30 秒的 leaseTime,然后起一个定时任务每隔 1/3 时间续期一次,防止业务没执行完锁就过期。它还能保证释放时只释放自己的。所以生产环境我建议直接用 Redisson 封装好的 RLock,而不是自己手写复杂逻辑。自己撸代码很容易漏掉续期、锁重入、主从切换后的锁丢失等问题。
4.3 缓存与数据库一致性:最终一致比强一致更能落地
有人一听到缓存不一致就慌,实际上很难做到绝对强一致。我的态度是:区分业务场景。如果这笔数据对账要求极高(比如账户余额),就不应该只依赖缓存,而是用数据库事务 + 可靠事件,缓存只作为加速读取;如果只是一般的商品库存展示,先更新数据库后删缓存,延迟在毫秒级,完全可以接受。
所谓“删缓存”,我在实战中发现一个细节:删除缓存前,最好把删除操作发到 MQ 里,由消费端统一删除。这样即使应用服务在删除缓存时崩溃,MQ 里的消息还能继续执行,提高最终一致的可靠性。另一个细节是缓存更新时加分布式锁,防止并发更新导致覆盖旧值。
我做过一个双删方案,这里分享给大家:更新数据库后,先延迟 200ms 删除缓存,再次延迟删除一次。原因是有种极端情况:如果请求 A 更新数据库,请求 B 读旧数据并写回缓存,此时 A 删除缓存已经执行完,缓存就永远保留旧值了。双删策略配合短延迟,能大幅降低这种概率。上生产之前先做压测,确认这个延迟不影响体验。
5. 运维操作与可视化管理:命令行不万能,但要熟练
5.1 高频命令与慢查询日志定位
不要小看命令行,很多问题你不登录服务器根本查不出来。我常用的几组命令列出来:查看配置CONFIG GET *,修改配置CONFIG SET,查看服务状态INFO,查看所有客户端连接CLIENT LIST,杀掉某个连接CLIENT KILL,查看慢查询SLOWLOG GET。高负载时第一件事就是CLIENT LIST,看看是否有异常数量的连接数。曾经有一次线上告警 Redis 连接数超过了 3000,我用CLIENT LIST一查,全是同一个应用服务器的连接没释放,马上定位到某个项目的 JedisPool 配置过小导致排队,而不是 Redis 本身有问题。
慢查询日志非常关键。Redis 的默认 slowlog 阈值是 10000 微秒(10ms),在生产上把阈值调到 5000 甚至 1000 微秒:
CONFIG SET slowlog-log-slower-than 5000 CONFIG SET slowlog-max-len 128然后SLOWLOG GET 20查看。如果发现大 key 操作(比如HGETALL一个超大 hash,或SMEMBERS一个大 set)频繁出现在慢查询中,你需要把它拆分成小批量操作,或者调整数据结构。这是 Redis 调优最直接的手段。
5.2 可视化管理工具:Redis Desktop Manager 与 Another Redis Desktop Manager
命令行虽好,但很多人更习惯用图形界面。网上最常用的 Redis 可视化管理工具是Redis Desktop Manager(简称 RDM),它的正版在较新版本转成商业收费模式,旧版开源。另一个社区分枝Another Redis Desktop Manager是免费开源的,支持 Windows/macOS/Linux,我自己是主力用这工具。它能看 key 列表、树形结构、查看 TTL、打开命令行终端、甚至看慢日志和 bulk 操作。
需要提醒的是,连接生产 Redis 建议先开一个只读账号(用 ACL 配置),再用图形工具连接,避免摸到生产环境直接删 key。我吃过亏:某次用 GUI 一键删除一个 pattern 里的 key,把一批正在使用的前缀 key 全删了,还好有 RDB 备份才恢复过来。可视化工具只是辅助,操作前先想清楚自己有没有权限、影响范围多大。
5.3 服务器资源监控:Redis 自己的瓶颈如何发现
服务器监控板块,我关心几个指标:内存使用率、used_memory_rss与used_memory的差距、evicted_keys、rejected_connections、blocked_clients、mem_fragmentation_ratio。
used_memory是 Redis 认为它占用的内存,used_memory_rss是操作系统视角分配的物理内存。如果mem_fragmentation_ratio超过 1.5,说明内存碎片严重,可能需要重启节点或调整activedefrag yes。如果evicted_keys持续增长,说明内存超过 maxmemory,正在淘汰 key,这会导致缓存命中率下降,数据库压力和雪崩风险上升。此时第一件事不是去加内存,而是检查业务里是否有大量无用的 key,或者 TTL 没设好,数据无限膨胀。
CLI 里我常用的快速健康检查:
redis-cli info stats | grep -E "instantaneous_ops_per_sec|evicted_keys|rejected_connections" redis-cli info memory | grep -E "used_memory|used_memory_rss|mem_fragmentation_ratio" redis-cli info clients | grep connected_clients5.4 主从、哨兵与集群:单台服务器不是终点
单实例 Redis 一旦挂掉,整个服务链都会雪崩。所以生产上至少要有主从结构:一台主库负责写,一台从库负责读或备份。Redis 主从复制配置很直接,在从库的 config 里写replicaof 主库IP 主库端口,或者通过命令SLAVEOF动态指定。我见过有人一台服务器上同时跑多个 redis 实例,主从复制就部署在同一台机器上——这样毫无意义,主库挂了从库也挂,必须要跨服务器部署才叫高可用。
再往上,可以使用 Sentinel(哨兵)监控主库,自动切换。哨兵至少部署三台,形成自己对主库主观下线与否的判断。如果你只想有一个稳定的缓存层,不需要分片,那“主从 + 哨兵”已经够用。Redis Cluster 则是把数据自动分片到多个节点,适合单实例内存无法容纳数据的情况。这个复杂度较高,新手不建议直接上,先搞明白主从和持久化再说。
6. 常见问题排查与避坑实录
6.1 “连接超时”和 “failed to fetch”类问题排查思路
我在服务器运维时经常遇到以下情况:应用报Redis command timed out或类似nested exception is io.lettuce.core.RedisCommandTimeoutException,偏偏这台 Redis 看着“好像活着”。出现这种问题,不能只看 Redis 进程有没有在,因为进程在也不代表它响应及时。
第一步用redis-cli ping从本地测;第二步从应用服务器上ping和telnetRedis 端口;第三步看 Redis 端慢查询和 CPU 负载。常见原因有:单线程执行了慢命令(比如大 key、KEYS)、AOF 重写瞬间阻塞、内存耗尽后触发淘汰风暴、网络带宽被打满,甚至 Redis 所在服务器负载过高,被其它进程抢占了 CPU。这些都可能造成“连接能建,但命令超时”。
我处理过一个案例:一台 Redis 部署在虚拟机上,线上时不时报超时,进一步排查发现宿主机上其它虚拟机占用大量网络 I/O,导致这台 Redis 的网络数据包延迟飙升。当时定位到核心指标是redis-cli --latency出现几百毫秒的尖刺。如果你排查完这些都正常,再看应用端连接池配置。Lettuce 的默认超时时间是 2 秒,太短也会造成误报,合理设置 3 到 5 秒之间更稳妥。
6.2 内存暴涨与 OOM:优先检查数据结构和过期策略
Redis 内存持续增长时,别急着FLUSHALL。先用redis-cli --bigkeys扫一遍,它会把最大的 key 找出来,通常是某个 Hash / Set / String 过于巨大,导致某次操作阻塞和内存暴涨。如果一个 key 中的数据量达到几十万甚至百万级别,建议拆分成多个小 key,或者改用 Stream / List 分批处理。
还有一个容易被忽略的是“大 key + 过期”:如果大 key 设置了过期时间,Redis 在惰性删除或定期删除时,一次性释放大量内存会导致服务器内存使用率瞬间冲高,甚至引起主线程卡顿。Redis 4.0 之后可以用unlink替代del异步释放内存。对于所有超大 key,能拆就拆,实在不能拆,至少要用UNLINK删除,千万别用DEL。
如果 OOM 是因为内存碎片高,还需要关注activedefrag yes配置。它可以在服务运行时整理碎片,但有一定的 CPU 消耗。你可以在低峰期开一段,观察效果再决定长期使用。
6.3 主从复制延迟与分裂问题
主从架构也不是铁板一块。复制延迟、脑裂问题仍然常见。如果主库写入量极大而网络又差,从库的repl_backlog被覆盖,可能导致从库需要FULLRESYNC重新全量复制,期间从库不可用。此时可以调大repl-backlog-size,比如从默认 1MB 改成 32MB 或更大,覆盖低峰期的同步积压,减少全量重同步次数。
另一个常见问题:主从切换后,旧主库重新上线变成从库,但它含有新主库没有的旧数据。Redis 会判断主从复制进度,如果冲突,通常以新主库为准。但如果你把 Redis 的 AOF 文件到处拷贝恢复,有可能引入旧数据覆盖新数据的问题。我在做容灾演练时,都会先强制SLAVEOF重新全量同步,再开放对旧库只读,避免脑裂后数据打架。
另外注意主从复制是异步的,如果主库刚写成功就宕机,这条数据可能没复制到从库。这种场景下分布式锁不要只依赖 Redis 主从,可以考虑 RedLock 或直接引入强一致组件,比如 etcd / ZooKeeper。但 RedLock 本身也有争议,这里不展开,我只想提醒一句:锁本身就是一秒级或毫秒级的需求,不要过度设计,除非业务真的无法接受锁丢失。
6.4 常见问题速查表
| 问题现象 | 排查方向 | 常用解决措施 |
|---|---|---|
| 连不上 Redis,端口不通 | 防火墙、bind 配置、网络策略 | 检查telnet ip port,放行安全组/入站规则 |
| 命令超时,ping 通但不响应 | 慢查询、大 key、CPU 负载、网络延迟 | 查SLOWLOG,恢复大 key,调大客户端超时 |
| 内存占用持续增长 | key 堆积、无 TTL、大 key | --bigkeys扫描,设置合理过期,拆分大 key |
| 缓存穿透导致数据库被打 | 查询不存在 key 的恶意流量 | 空值缓存 + 布隆过滤器 + 接口限流 |
| 缓存雪崩 | 大量 key 同时过期 | 过期时间加随机数,集群高可用,降级开关 |
| 主从切换后数据不一致 | 异步复制、脑裂 | 确保配置min-replicas-to-write,强制全量重同步 |
使用KEYS阻塞实例 | 误用高频命令 | 用SCAN替换,设置rename-command KEYS "" |
| 图形工具误删 key | 权限过大 | ACL 只读账号,删除前备份 |
| 慢查询频率高 | 命令设计不合理 | 分布式批量操作pipeline,拆分大数据结构 |
7. 一点私人体会
我在服务器上折腾 Redis 这么久,最大的感受是:它很容易上手,但极难“驾驭好”。很多人装上、用起来,觉得一切正常,直到线上遇到一次内存雪崩或者锁失效,才会意识到自己之前忽视了多少小细节。比如maxmemory从来不做规划,比如持久化策略随便选,比如可视化工具一把梭,这些都是我踩过的坑。
另外一个经验是,Redis 的版本升级别太保守。我从 3.x 一路用下来,4.0 的异步删除、5.0 的 Stream、6.0 的 SSL 与 ACL、7.0 的 Function,这些新特性实实在在地解决了很多老版本“能跑但不好用”的问题。只要你有经过测试的升级流程,尽量保持较新的稳定版。
最后再分享一个小技巧:在服务器上给 Redis 留一个“逃生舱”。我会准备一个独立的脚本,紧急时可以直接redis-cli shutdown nosave快速重启,并同时保留最近的 RDB 文件。真遇到内存卡死或锁问题,快速恢复永远比慢慢分析更重要。等你恢复完业务,再回头慢慢看日志。这个思路适用于任何线上中间件,Redis 尤甚。服务器是脆弱的,但服务不能跟着脆弱,合理的设计和谨慎的运维能让你睡个好觉。