做后端这些年,Redis 几乎是每个项目都绕不开的组件。缓存、分布式锁、排行榜、计数统计、消息队列,凡是追求性能的地方基本都有它的身影。网上讲 Redis 的帖子很多,但大多比较零散,有些只讲命令,有些只讲部署,真正能直接拿回来解决实际问题的却不多。这篇文章就是我自己的“Redis 相关操作大全”:从下载安装、数据类型选用,到可视化客户端、缓存治理、分布式锁、集群部署,再到面试里常被追问的底层原理,一次性整理成一份可查、可用的清单。如果你刚接触 Redis,照着做就能跑起来;如果已经在生产环境用过一段时间,里面整理的那些坑和排查思路,应该也能给你一些参考。
1. Redis 安装:Windows、Linux、Docker 环境一次配齐
很多人第一步就卡在安装上,因为 Redis 官方其实只对 Linux 做了最佳支持,并不提供官方的 Windows 安装包。这导致网上搜出来的下载方式五花八门,版本也参差不齐。我建议根据你的实际场景选一种方式:本地开发图方便用 Windows 移植版,正式服务器老老实实编译安装或用包管理器,环境隔离要求高就上 Docker。
1.1 Windows 上如何安装 Redis:别在“官方不支持”上卡住
在 Windows 上跑 Redis,常见有三条路:
- 使用社区维护的 Redis for Windows 项目,解压即可用
- 使用 WSL2 运行真实的 Linux Redis
- 使用 Docker Desktop 跑 Redis 容器
最省事的还是直接用 Windows 移植版。到项目的 Releases 页面下载 zip 压缩包,解压后就能看到 redis-server.exe、redis-cli.exe、redis.conf 等文件。想注册成 Windows 服务,以管理员身份打开命令行,进入解压目录执行:
bash复制redis-server.exe --service-install redis.conf --service-name Redis
redis-server.exe --service-start --service-name Redis
这里建议在 redis.conf 里先把密码配好,否则默认没有认证,局域网里谁都能连上来:
conf复制requirepass yourstrongpassword
我踩过的一个坑是:Windows 移植版通常停留在某个 Redis 大版本上,对 Redis 6 之后引入的 ACL、多线程等新特性支持不及时。本地做基础调试没问题,但如果要验证生产环境特性,最好还是用 WSL2 或 Docker,避免“本地正常、线上行为不一致”的情况。另外 Windows 下 fork 能力较弱,BGSAVE、BGREWRITEAOF 这类需要创建子进程的操作表现不如 Linux,所以生产环境完全没必要考虑在 Windows 上跑 Redis。
1.2 Linux 编译安装:从源码到 systemd 服务的完整过程
Linux 上编译安装其实不难,关键是依赖要装全。以 Ubuntu/Debian 为例:
bash复制sudo apt update
sudo apt install -y gcc make libsystemd-dev pkg-config tcl
wget https://download.redis.io/releases/redis-7.0.14.tar.gz
tar -xzf redis-7.0.14.tar.gz
cd redis-7.0.14
make -j$(nproc)
make install
编译完成后,redis-server 和 redis-cli 会被安装到 /usr/local/bin。然后复制配置模板到 /etc:
bash复制mkdir -p /etc/redis
cp redis.conf /etc/redis/redis.conf
配置文件里我通常会改这几个关键项:
conf复制bind 127.0.0.1
protected-mode yes
port 6379
daemonize no
requirepass yourstrongpassword
appendonly yes
maxmemory 512mb
在生产环境,bind 不建议直接绑 0.0.0.0。如果非要远程访问,建议绑内网 IP,同时把 protected-mode 保持为 yes,再用 requirepass 设置强密码。Appendonly 打开是为了至少保证崩溃时可以恢复大部分数据。
把 Redis 托管给 systemd 管理会省心很多,写一个 /etc/systemd/system/redis.service:
ini复制[Unit]
Description=Redis Server
After=network.target
[Service]
ExecStart=/usr/local/bin/redis-server /etc/redis/redis.conf
ExecReload=/bin/kill -s HUP $MAINPID
ExecStop=/bin/kill -s TERM $MAINPID
User=redis
Group=redis
Restart=always
RestartSec=5
[Install]
WantedBy=multi-user.target
启动并验证:
bash复制useradd -s /sbin/nologin redis
chown -R redis:redis /var/lib/redis
systemctl daemon-reload
systemctl enable redis
systemctl start redis
redis-cli -a yourstrongpassword ping
如果返回 PONG,说明服务已经正常跑起来了。
1.3 Docker 安装与主从快速部署
Docker 方式最接近生产环境,也是我现在最常用的方式。单机跑一个实例非常简单:
bash复制docker run -d --name redis \
-p 6379:6379 \
-v /data/redis:/data \
redis:7 \
redis-server --appendonly yes --requirepass yourstrongpassword
要跑主从,我习惯用 docker-compose 管理。先建一个目录,里面放 compose 文件:
yaml复制version: '3.8'
services:
redis-master:
image: redis:7
container_name: redis-master
command: redis-server --appendonly yes --requirepass masterpass
ports:
- "6379:6379"
volumes:
- master-data:/data
redis-slave:
image: redis:7
container_name: redis-slave
depends_on:
- redis-master
command: redis-server --replicaof redis-master 6379 --masterauth masterpass --requirepass slavepass
ports:
- "6380:6379"
volumes:
- slave-data:/data
volumes:
master-data:
slave-data:
启动后进入从节点验证:
bash复制docker exec -it redis-slave redis-cli -a slavepass info replication
看到 role:slave 和 master_link_status:up 就说明主从同步成功。从节点默认是只读的,这对读写分离来说正好,写操作全部走主节点。
关于 Redis Cluster 集群,最小规模是三主三从。如果你只是本地测试,可以用 docker 起 6 个容器,每个节点映射不同端口,再通过 redis-cli --cluster create 命令创建集群。运维上要注意 Redis Cluster 除了 6379 这类业务端口外,还需要使用总线端口(默认 6379+10000=16379),安全组和防火墙都要把总线端口放通,否则集群节点之间无法通信,会一直提示“ Waiting for the cluster to join ”。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 数据类型与命令实战:从 String 到 Stream 全覆盖
Redis 能用的远不止“缓存 String”这一种形态。很多人一开始就是把所有内容塞进 set/get,等遇到排行榜、去重统计、高并发抽奖这类需求时才发现,选对数据类型和命令,能省掉大量业务代码和下游数据库压力。
2.1 基础类型的正确使用姿势
String 是最基础的类型,使用率也最高。我常用的命令包括 SET、GET、DEL、EXPIRE、SETNX、INCR、DECR。典型的场景有三个:缓存接口结果、实现计数器、用 SETNX 做简单的资源占用。
bash复制SET user:login:token:123 "abc123" EX 7200 NX
INCR video:play:10001
GET video:play:10001
EX 7200 表示 2 小时过期,NX 表示 key 不存在时才设置成功,这在后面做分布式锁时也是核心原语。计数器场景要注意 INCR 是原子操作,高并发下不会出现重复计数。
Hash 适合存一个对象的多个字段,例如用户信息、商品信息:
bash复制HSET product:1001 name "机械键盘" price 399 stock 200
HGETALL product:1001
HINCRBY product:1001 stock -1
Hash 的直观好处是只改一个字段,不用像 String 那样把整个 JSON 读出来再反序列化。要注意 Redis 的 Hash 没有字段级过期时间,如果想给“某个字段”设置过期,只能在业务层面增加一个辅助的带过期时间的 key,或者把数据拆分成多个 String。
List 最常见的两个用途是:最新消息列表和轻量级任务队列。Redis 的 List 是双向链表,LPUSH 往队首插、RPUSH 往队尾插,需要阻塞读取时用 BRPOP。一个简单的生产者消费者模型:
bash复制LPUSH task:queue {job_id: 123}
BRPOP task:queue 0
这里 0 是超时时间,表示一直等待。如果需求里要求消息至少消费一次,List 其实没有原生的 ACK 机制,消费失败消息就丢了,所以生产级的任务队列还是建议后面提到的 Stream。
Set 的特点是自动去重,适合做抽奖、点赞去重、共同好友:
bash复制SADD lottery:20250101 user1 user2 user3
SPOP lottery:20250101
SCARD lottery:20250101
SINTER user:1:follow user:2:follow
ZSet 即有序集合,每个元素带一个分数,按分数排序。排行榜是它的经典场景:
bash复制ZADD game:rank 1000 "player_a"
ZINCRBY game:rank 100 "player_a"
ZREVRANGE game:rank 0 9 WITHSCORES
延时队列也可以靠把“执行时间戳”作为分数来实现:生产者往 ZSet 里塞任务,消费者轮询 ZRANGEBYSCORE 取出到点执行的任务,然后 ZREM 删除。
2.2 高级类型:Bitmap、HyperLogLog、Stream 与 Redis 7 新特性
Bitmap 在 Redis 底层是字符串,按位操作,通常用来做用户签到、在线状态这类“大量布尔值”场景。
bash复制SETBIT sign:202501:user123 5 1
GETBIT sign:202501:user123 5
BITCOUNT sign:202501:user123
上面的例子表示用户 123 在 1 月的第 5 天签到了。一亿用户每人一年的签到记录,用 Bitmap 也只要不到 1.2GB,这在传统 MySQL 里是不可想象的。
HyperLogLog 适合做海量数据的 UV 统计。它有个非常重要的特性:标准误差约 0.81%,内存占用却恒定很小。
bash复制PFADD today:uv user1 user2 user3
PFCOUNT today:uv
如果允许一定误差,用 HyperLogLog 统计日活是性价比极高的方案。比如每天独立访客量 1000 万,结果多几万人或少几万人,业务上完全可以接受。
Stream 是 Redis 5.0 引入的消息队列数据结构,它比 List 更接近一个纯正的 MQ。基本操作:
bash复制XADD order:event * order_id "1001" status "created"
XLEN order:event
XGROUP CREATE order:event group1 0
XREADGROUP GROUP group1 consumer1 COUNT 10 STREAMS order:event >
Stream 支持消费者组和消息 ACK,可以用 XACK 确认消费成功。相比 List,它至少能解决“消息被谁消费了”的问题。但如果你的项目里已经有 RabbitMQ 或 Kafka,也没必要因为 Redis 有 Stream 就把消息队列切过来——Redis Stream 缺少很多 MQ 的治理能力(比如复杂的死信路由、延迟队列、多协议接入),它更适合“不想再引入一套新中间件”的轻量场景。
Redis 7.x 的新特性里,我比较关注 Redis Functions,它用来替代 Lua 脚本,可以集中管理脚本,并解决 Lua 脚本在集群模式下分发困难的问题。另外 7.x 还把 listpack 默认引入到了小 Hash 和小 ZSet 中,内存占用进一步降低,集群模式下 Pub/Sub 也支持了分片,功能上越来越完整。
2.3 命令速查表与 Key 命名规范
我这里整理了一张高频命令速查表,适合贴在手边。展开说明前先说一个重要习惯:key 一定要按规范命名,我常用的格式是“业务名:模块名:主体ID:属性”,例如 mall:order:1001:status。这样在排查问题时能直接按前缀定位。
| 数据类型 | 命令 | 说明 |
|---|---|---|
| String | SET/GET/DEL/EXPIRE/SETNX/INCR | 缓存、计数、分布式锁基础 |
| Hash | HSET/HGET/HGETALL/HINCRBY/HDEL | 对象字段存储 |
| List | LPUSH/RPUSH/LPOP/BRPOP/LLEN | 列表、简单队列 |
| Set | SADD/SREM/SISMEMBER/SINTER/SPOP/SCARD | 去重、抽奖、交集计算 |
| ZSet | ZADD/ZINCRBY/ZREVRANGE/ZRANGEBYSCORE/ZREM | 排行榜、延时队列 |
| Bitmap | SETBIT/GETBIT/BITCOUNT | 签到、在线状态 |
| HyperLogLog | PFADD/PFCOUNT/PFMERGE | UV 统计 |
| Stream | XADD/XREAD/XGROUP/XACK/XLEN | 轻量消息队列 |
命令记不全时,直接 redis-cli --help 或 help @string 在命令行里查,比翻文档快得多。
关于 key 命名还有几个实操经验:key 不要设计得过长,过长的 key 会持续占用内存;不要用空格和换行,尽量只保留字母、数字、冒号、下划线;同一业务线的 key 要加统一前缀,方便用 SCAN 做统计和清理。还有一个坑我必须强调:生产环境严禁使用 KEYS ekypic_* 这类通配符匹配来查 key。KEYS 命令会全库扫描,在 key 数量较大时直接阻塞 Redis,导致所有请求排队。正确做法是用 SCAN:
bash复制redis-cli --scan --pattern "mall:order:*" --count 1000
SCAN 是游标式迭代,每次返回游标和一批 key,不会阻塞服务。但注意 SCAN 一次可能返回重复 key,客户端要做去重。
3. 可视化客户端与生产连接实践
命令行适合调试,但日常排查和数据分析还是得有可视化工具。这个领域的软件其实不少,选错很容易影响效率。
3.1 Redis Desktop Manager、Another Redis Desktop Manager、Redis Insight 怎么选
很多人一搜“Redis 可视化工具”就跳出来 Redis Desktop Manager(RDM)。老牌的 RDM 功能确实全,但 OSS 版本已经停止更新,新版本是商业授权,对于个人开发者不算便宜。我的建议是:优先考虑官方推出的 Redis Insight,或者开源的 Another Redis Desktop Manager(ARDM)。
| 工具 | 开源/免费 | 支持平台 | 特点 |
|---|---|---|---|
| Redis Desktop Manager | 老版本开源,新版本商业 | Win/macOS/Linux | 功能全面,支持集群、SSH 隧道 |
| Another Redis Desktop Manager | 开源免费 | Win/macOS/Linux | 界面现代,支持多开、内存分析 |
| Redis Insight | 官方出品,免费 | Win/macOS/Linux | 新特性支持最快,自带 Redis 命令分析 |
个人开发场景我用 ARDM 比较多,它比 RDM 轻,界面响应也快。但如果你要连接 Redis 7 并使用 ACL 用户登录,最好选 Redis Insight 或最新版 ARDM,老版本的 RDM 对 ACL 支持不完整,会出现用户名密码正确但登录失败的怪问题。
另一个常见需求是连接生产环境的 Redis。默认 Redis 只允许本机访问,可视化工具连不上并不一定是密码问题,多半是 bind 和 protected-mode 配置导致的。你可以在服务器本地验证:
bash复制redis-cli -h 127.0.0.1 -p 6379 -a password ping
本地通、远程不通,就检查 bind 是否绑定了内网地址、云安全组和防火墙是否放行端口、requirepass 是否设置。
3.2 连接远程 Redis 的常用姿势
生产环境我基本不会直接把 Redis 端口暴露到公网,最稳妥的方式是 SSH 隧道。本地机器上执行:
bash复制ssh -L 6379:127.0.0.1:6379 user@你的服务器IP
然后可视化工具直接连接本机的 127.0.0.1:6379,数据就通过 SSH 加密隧道转发到服务器。这样做的好处是:不用额外配置 TLS,Redis 本身可以保持只监听 127.0.0.1,安全风险小很多。
如果你需要更规范一点,可以在 Redis 配置里启用 TLS 或让客户端走 Stunnel 之类的加密隧道,但这会让运维复杂度陡增。对于大多数中小团队,SSH 隧道加强密码已经够用。
还有一个常见坑:连接时提示 MISCONF Redis is configured to save RDB snapshots。这是 Redis 在 BGSAVE 失败时为了保护数据默认拒绝写入的机制。排查方向是磁盘空间、内存不足或持久化目录权限不对。临时可以先执行 CONFIG SET stop-writes-on-bgsave-error no 恢复写入,但务必尽快解决根因,否则数据丢失风险会增加。
4. 缓存治理与分布式锁:生产环境真正的硬仗
把 Redis 部署起来、会敲几个命令,只是第一步。真正让团队头疼的往往是缓存穿透、缓存击穿、缓存雪崩,以及分布式锁和缓存一致性问题。我在多个项目里都经历过这些问题,下面把它们的成因和应对策略完整展开。
4.1 缓存穿透、击穿、雪崩:三板斧怎么打
这三个概念经常被放在一起问,但成因完全不同。
缓存穿透是指查询一个根本不存在的数据,请求打不到缓存,直接穿透到数据库。如果有人恶意循环查询不存在的 ID,数据库压力瞬间会被拉满。常见对策有两个:
- 对空结果也做短暂缓存,比如
SET key "null" EX 60,让同样查询短时间内不再打到 DB - 用布隆过滤器在查询前判断“这个 key 是否可能存在”,不存在直接返回
布隆过滤器本质是一个 Bitmap 数组加多个哈希函数,它的缺点是存在误判概率,但不会漏判。Redis 本身没有内置布隆过滤器,通常通过 RedisBloom 模块实现,或者在业务代码里用 Guava 的 BloomFilter 挡一层。
缓存击穿是指一个热点 key 在过期瞬间,大量请求同时打到数据库。由于并发量很高,数据库瞬间就被压垮。最常用的解法是互斥锁重建缓存:当缓存过期时,只允许一个线程去查数据库并回填缓存,其他线程短暂等待后重新读缓存。也可以用“逻辑过期”方案,不真正设置物理过期时间,而是在 value 里存一个过期时间戳,异步线程发现逻辑过期后去刷新缓存,这样请求不会阻塞。
缓存雪崩是指大量 key 在同一时间段集中过期,导致请求全部压到数据库。最常见的原因是没有给过期时间加随机因子。解决办法是:
text复制过期时间 = 业务基础过期时间 + [0, 300] 秒随机值
例如原来是 3600 秒,改成 3600 + random(0, 300),让 key 的过期时间错开。除此之外,Redis 本身要做到高可用(主从或集群),即便单台宕机也有其他节点顶上,同时加一层本地缓存(如 Caffeine、Guava)兜底,能挡住相当比例的流量。
4.2 分布式锁:从 SET NX 到 Redisson 的完整演进
分布式锁是 Redis 在分布式系统里的典型应用。很多新手直接写一个 SETNX 就以为锁写对了,实际上坑非常多。一个标准实现至少要考虑三个点:加锁原子性、锁超时自动释放、锁误删问题。
加锁的正确命令:
bash复制SET order:lock:1001 "uuid-001" NX EX 10
NX 保证不存在才设置,EX 10 设置过期时间。必须放在同一个命令里,因为“先 SETNX 再 EXPIRE”两步执行时,如果第二步失败,锁就会变成永不过时的死锁。
释放锁不能直接 DEL。因为可能出现这种情况:线程 A 拿着锁执行时间较长,锁自动过期了;线程 B 拿到同一把锁,此时 A 执行完去 DEL,把 B 的锁误删了。所以释放锁时要先比较 value 再删除,用 Lua 脚本保证原子性:
lua复制if redis.call("get", KEYS[1]) == ARGV[1] then
return redis.call("del", KEYS[1])
else
return 0
end
value 必须用全局唯一的字符串,比如 UUID。这样才能保证“只有持有锁的人才能释放锁”。
但上面这套自己实现的锁有一个痛点:如果业务代码执行时间超过锁的过期时间,锁自动释放,其他线程就会进入临界区,造成并发问题。生产上我建议直接用 Redisson。Redis 的 Java 客户端里,Redisson 封装好了一套分布式锁,默认启动一个“看门狗”线程,每 10 秒检查一次锁是否还持有,如果持有就把锁的过期时间续期到 30 秒,业务没执行完锁就不会丢。
还要注意:单节点的 Redis 分布式锁存在一个极端盲区。主节点拿到锁之后,数据还没同步到从节点时主节点挂了,从节点升级为主节点,锁就丢了,另一个线程就能拿到同一把锁。Redis 官方提出的 Redlock 算法试图解决这个问题,但它的复杂度很高,尤其是对时钟跳跃很敏感。我的建议是:如果你的业务允许偶尔重复操作(比如幂等校验在下一层兜底),单节点锁 + Redisson 完全够用;如果必须严格互斥,最好引入 ZooKeeper 或 etcd 这类带强一致性的锁组件,不要过度依赖 Redis 的 Redlock。
4.3 缓存一致性:先更新 DB 还是先删缓存
缓存和数据库的一致性,是每个后端都要面对的经典问题。主流方案是 Cache Aside 模式:
- 读请求:先读缓存,命中直接返回;未命中查 DB,回填缓存
- 写请求:先写 DB,然后删除缓存
为什么写之后是“删缓存”而不是“更新缓存”?因为更新缓存存在并发问题:线程 A 先更新 DB,线程 B 后更新 DB,但网络回包顺序不确定,Redis 里可能出现“后写 DB 却先更新缓存”的脏数据。而删除缓存之后,下一次读缓存会 miss,重新查一次 DB 并回填,最终一致性有保障。
缓存删除失败是另一个常见问题。如果 DB 写完,删除缓存时网络抖动失败,旧缓存就会一直存在。常用的补偿手段是:
- 把删除失败的 key 丢进消息队列,由专门消费者重试删除
- 使用订阅数据库 binlog 的技术(如 Canal)感知数据变更后主动清理缓存
延迟双删是一种相对简单但有效的方案。写 DB 后先删除一次缓存,等几百毫秒再删除一次,目的是解决“读请求在删除瞬间把旧数据回填到缓存”的窗口期问题。要注意延迟时间要根据实际业务调整,太短没用,太长影响实时性。
事务场景下的顺序也容易踩坑:必须等数据库事务提交成功后再删缓存。如果在事务还没提交时就删了缓存,另一个线程查询到的是未提交的旧数据并回填缓存,最终缓存和数据库就不一致了。
4.4 用 Stream 做消息队列:适合什么场景
基于 Redis 的 List 做消息队列是很多新手的选择,但 List 模式缺少消费者组和 ACK,遇到消费失败难以重试。Stream 是更好的替代方案。
Stream 的基本消费模型:先创建消费者组,多个消费者组可以独立消费同一份消息;组内多个消费者竞争消费,类似 Kafka 的 partition 消费模型。一条消息被消费者读取后不会被立即删除,只有收到 XACK 确认后,Pending 列表里才会移除这条消息。
bash复制XGROUP CREATE order:event group1 0
XREADGROUP GROUP group1 consumer1 COUNT 10 STREAMS order:event >
其中 > 表示只读之前未被投递给当前消费者的消息。如果消费者读取消息后进程崩溃,没有 ACK,消息会一直留在 Pending 列表里,之后可以重新用 XCLAIM 将其分配给其他消费者。
关于 Stream 是否适合做核心消息队列,我的看法是:你需要先明确对可靠性的要求。如果业务场景允许极端情况下丢 1 秒数据,且消息量级不大,用 Redis Stream 能少维护一套 MQ 集群。但如果涉及订单、支付这类核心链路,我还是建议用 RabbitMQ 或 Kafka,它们针对消息堆积、消费重试、死信队列、持久化都有更成熟的方案。Redis 的优点在于它是内存操作、延迟极低,缺点也在于内存——如果消费端长期离线,消息不断堆积,Redis 内存压力会非常大。
5. 集群、持久化与性能排查实录
单机 Redis 无论如何优化,都不可能永远满足线上需求。主从复制、哨兵、Cluster 集群、持久化选型,这些内容不搞清楚,上线后遇到高负载或者宕机事故,处置起来会非常被动。
5.1 主从复制、哨兵与 Cluster 集群
主从复制是 Redis 高可用的基础。主节点负责写,从节点负责读和备份,默认从节点只读。首次连接会做一次全量同步:主节点生成 RDB 快照发给从节点,之后增量命令通过 backlog 缓冲区继续同步。如果网络断开时间过长导致 backlog 被覆盖,从节点只能重新做全量同步,所以在网络不稳定的环境里,repl-backlog-size 可以适当调大,避免频繁全量同步带来性能抖动。
主从复制本身不具备自动故障转移能力。主节点挂掉后,需要哨兵 Sentinel 介入。哨兵会不停监控主节点状态,当主节点客观下线后,从候选从节点中选举出一个提升为新主节点,并把客户端拉到新主节点上。生产环境至少部署 3 个哨兵节点,原因是为了避免“脑裂”:哨兵之间需要多数派决策,2 个节点一旦一个挂掉就失去了决策能力,3 个节点允许挂 1 个。
Cluster 集群则是把数据打散到多个主节点上,每个主节点可以带一个或多个从节点。Redis Cluster 默认把 key 空间划分成 16384 个 slot,通过 CRC16(key) % 16384 决定这个 key 属于哪个 slot。客户端连接任意节点,如果定位到的 slot 不在当前节点,会返回 MOVED 错误并指向正确节点,所以生产客户端一般都要开启集群模式(例如 redis-cli -c、Lettuce 的集群模式配置)。
如果你遇到“集群 1 数据要迁移到集群 2”的需求,最稳妥的方式有三步:先在集群 2 搭建好对应数据结构的空库,然后从集群 1 主节点生成 RDB 文件,迁移到集群 2 后通过临时单机实例导入,或者用支持跨集群同步的工具复制增量命令。但最省心的其实是业务层双写:并行一段时间、校验数据一致后再切流,这样风险是可控的。直接对在线集群做跨集群全量复制,很容易造成连接风暴和数据不一致。
5.2 RDB、AOF 与混合持久化的取舍
Redis 虽然叫“内存数据库”,但如果不配置持久化,重启后数据会全部丢失。持久化有 RDB 和 AOF 两种方式,各有利弊。
RDB 是快照机制,按配置的时间点(如 900 秒内至少 1 次写操作)把内存数据全量写入磁盘。优点是文件体积小、恢复速度快,适合做备份和灾难恢复。缺点是最多会丢失上次快照后写入的数据,而且生成快照时 fork 子进程如果数据量很大,可能短暂影响主进程响应。
AOF 以日志形式追加记录每条写命令。可以配置三种 fsync 策略:always(每条命令都刷盘,最安全也最慢)、everysec(每秒刷盘,最多丢 1 秒数据)、no(交给操作系统决定)。我一般用 everysec,兼顾安全和性能。AOF 的缺点就是文件会不断变大,需要定期执行 BGREWRITEAOF 重写,压缩成最小命令集。
Redis 4.0 以后支持混合持久化:AOF 重写时直接把当前数据以 RDB 格式写入 AOF 文件头部,后续增量命令再用二进制追加。这样重启时既有 RDB 的快速加载优势,又保留了 AOF 断点恢复能力。除非你明确只需要缓存且能接受重启丢全部数据,否则我建议都开启 aof-use-rdb-preamble yes。
选型上可以这样判断:如果 Redis 只做纯缓存,关掉持久化能获得最大性能;如果 Redis 里存了用户资料、限流计数等不能全部丢失的数据,至少开启 AOF everysec;如果 Redis 还承担了消息队列角色,建议 AOF everysec 加主从同步,并且定期 RDB 备份,双保险。
5.3 慢查询、大 Key、内存碎片排查
线上 Redis 性能出问题,一半以上是大 Key 和慢命令引起的。大 Key 指的是单个 key 里的 value 很大,比如一个 Hash 里塞了几百万字段,或者一个 String 存了几 MB 的序列化对象。大 Key 会导致删除、迁移、复制时阻塞 Redis,甚至拖慢主从同步。
排查大 Key 用内置命令:
bash复制redis-cli --bigkeys
它会扫描整个实例,分别统计每种类型里最大的 key。注意这个命令本身也会遍历实例,建议在业务低峰期执行。单独看某个 key 的大小可以用:
bash复制redis-cli --bigkeys --scan --pattern 'mall:*'
更精确的是:
bash复制redis-cli memory usage mall:product:1001
慢命令可以通过慢查询日志查看。在 redis.conf 里设置:
conf复制slowlog-log-slower-than 10000
slowlog-max-len 128
表示记录超过 10ms 的命令,最多保留 128 条。然后用:
bash复制redis-cli slowlog get 20
重点看有没有 KEYS、HGETALL、LRANGE 这种一次性返回大量数据的命令。比如对一个很大的 List 执行 LRANGE 0 -1,等于把所有数据全量拉到客户端,网络和内存都会瞬间被吃满,这是典型的生产事故源。
内存碎片问题可以通过 info memory 里的 mem_fragmentation_ratio 看。正常情况下在 1 到 1.5 之间,如果长期大于 1.5,说明内存碎片太多。原因是大量 key 频繁增删导致内存段不连续。轻量情况可以执行 memory purge,严重的话需要主从切换或重启,重启前要确保数据已持久化,否则会丢失数据。
5.4 日志怎么看:常见故障排查速查表
Redis 的日志默认输出到标准输出,用 systemd 管理时可以用 journalctl 查看,Docker 容器用 docker logs 查看。如果手工启动并用 daemonize no,日志会直接打印在终端。生产环境建议配置 logfile 指定日志路径,并且单独做日志轮转。
| 故障现象 | 可能原因 | 排查思路 |
|---|---|---|
| 客户端连接超时 | 并发连接数超过 maxclients | info clients 查看连接数,调大 maxclients 或加连接池 |
| key 大量消失 | 过期时间策略或内存淘汰策略 | info stats 查看 expired_keys 和 evicted_keys |
| 写入报 MISCONF | BGSAVE 失败 | 查磁盘空间、持久化目录权限,stop-writes-on-bgsave-error |
| 主从同步延迟大 | 网络带宽不足、从节点服务能力弱 | 查看 repl_backlog、info replication |
| 命令执行耗时高 | 大 Key 操作、慢命令 | slowlog get 定位,拆分或优化 key |
| 内存增长过快 | value 过大、key 过多、内存碎片 | memory usage 定位大 key,对象编码优化 |
| 拒绝远程连接 | bind、protected-mode、安全组 | 确认 bind 和 requirepass 配置 |
日志里有一个高价值信息:当出现 WARNING overcommit_memory is set to 0 时,说明系统内存分配策略可能影响 BGSAVE,建议在 /etc/sysctl.conf 中设置 vm.overcommit_memory = 1 并执行 sysctl -p 生效。这类系统级参数如果不改,高负载下 RDB 持久化会出现偶发失败。
6. 面试高频题与底层原理一次讲清
我把面试和团队评审里经常出现的 Redis 原理问题集中过一遍。很多人能答出“Redis 快是因为内存”,但再深一层就说不清楚了。下面这些内容不仅是应付面试,也是你在排查问题时的底层知识储备。
6.1 Redis 为什么快:原理层面的答案
Redis 快的原因要从四个层面看。
第一,纯内存操作。所有读写都在内存中完成,内存随机读写的速度和磁盘完全不在一个量级。这是最直接的原因。
第二,核心命令执行是单线程。Redis 6.0 之前整个服务基本是单线程,6.0 之后引入了多线程,但多线程只用于网络数据的读写和异步删除等部分,命令执行这一段仍然是单线程。单线程的好处是不需要加锁,也不存在线程切换开销,自然避免了大量的并发竞争问题。
第三,I/O 多路复用。Redis 使用基于 epoll(Linux)、kqueue(BSD)的事件驱动模型,一个线程可以同时监听成千上万个客户端 socket,而不会像“一个连接一个线程”那样消耗大量资源。这个概念可以用生活里的餐厅例子理解:传统模式是每个顾客配一个服务员,人一多服务员就不够用;Redis 的模式是一个服务员同时照看多桌,只在有请求产生时过去处理,空闲时间不需要占用资源。
第四,底层数据结构非常高效。Redis 没有直接用 C 语言的字符串和链表,而是设计了简单动态字符串(SDS)、压缩列表(listpack)、跳表(skiplist)等结构。比如 ZSet 用跳表实现有序查询,时间复杂度为 O(logN),比普通数组的 O(N) 好得多。
6.2 高频面试题精简答案
结合我担任面试官时的常见提问,下面列几道高频题的关键点,既要答出定义,也要答出工程上的取舍。
Redis 过期删除策略是什么?
两种策略结合:惰性删除,请求访问 key 时才检查是否过期,过期就删;定期删除,后台定期抽样扫描一部分过期 key 并删除。它不会对所有 key 实时检查,因为那样太耗资源。
内存淘汰策略有哪些?
Redis 8 种策略,常用的包括:volatile-lru(从已设置过期时间的 key 中淘汰最近最少使用)、allkeys-lru(所有 key 中淘汰最近最少使用)、volatile-ttl(优先淘汰剩余过期时间最短的)、noeviction(默认,不淘汰,内存满时拒绝写命令)。缓存场景建议 allkeys-lru,有做会话等强需求时用 volatile-lru 并给 key 设置过期时间。
大 Key 有哪些危害?
删除大 Key 会产生阻塞,因为释放内存需要时间;主从复制时大 Key 会导致全量同步变慢;写操作时如果频繁修改大 Key,还可能引起内存碎片。解决办法是拆分大 Key、压缩 value,删除时用 unlink 异步删除代替 del。
Sentinel 和 Cluster 有什么区别?
Sentinel 解决的是高可用问题,负责监控和故障转移,数据只有一份主从。Cluster 解决的是扩展性问题,数据分片到多个主节点,天然包含主从和高可用设计。两者解决的核心问题不同,可以独立使用,也可以结合。
缓存一致性怎么保证?
主流 Cache Aside 模式,写 DB 后删除缓存,再用延迟双删、消息队列重试、binlog 订阅等方式补偿删除失败场景。没有绝对的强一致方案,工程上做到最终一致即可。
分布式锁哨兵模式下安全吗?
单节点锁在故障切换时可能丢失,Redlock 算法能降低风险但复杂度高。对一致性要求严格时,考虑 ZooKeeper 或 etcd。我在项目中一般会把 Redis 锁用于幂等控制,比如“同一用户同一秒内不能重复提交”,而不是用于资金类强互斥操作。
7. 写在最后:十个实操心得
把 Redis 从“会用”到“用得稳”,靠的是这些平时踩坑换来的经验。我最后整理十条,希望能帮你少走弯路。
- key 命名规范要提前定好。采用“业务:模块:ID:属性”格式,上线后统一排查和清理有效 key 都会方便很多。
- 不管什么场景,都要给 key 设置过期时间。没有过期时间的 key 就像没设闹钟一样,等你发现内存暴涨时已经晚了。
- 生产环境禁止 KEYS、禁止频繁 MONITOR。这两个命令都会阻塞或拖垮主线程,排查问题也尽量用 SCAN 和 slowlog。
- 连接池一定要配置合理。maxIdle、maxTotal、minIdle 让连接可复用,用完归还,否则高并发下会积累大量 TIME_WAIT。
- 宁可多花几个字节存短 key,也不要把 key 设计成几百字节的长串。key 是要常驻内存的,越短越省。
- 大 value 一定要拆。超过几百 KB 的 String、超过几十万字段的 Hash,都可能成为慢命令和大内存隐患。
- 监控一定要做。至少盯着 maxmemory、expired_keys、evicted_keys、慢查询日志这几项,出了问题能快速定位。
- 不要把 Redis 当唯一的数据存储。它更适合做加速层和状态层,关键数据必须落库或持久化,缓存丢失时要有回源方案。
- 集群或主从上线前,先做一次故障演练。手动 kill 主节点看哨兵会不会自动切换,比等真出事时再临时抱佛脚好得多。
- 关注 Redis 版本升级。新版通常会给性能和安全带来优化,但升级前先在测试环境验证持久化兼容性,别在生产环境直接大版本跳跃。
我在实际项目里最深的体会是:Redis 本身是个简单可靠的工具,绝大多数问题都出在使用姿势上——命名乱、不设过期、大 key 不拆、配置裸奔。把基础规范执行到位,后面会省下很多半夜被报警电话叫醒的时间。希望这份操作整理对你也有同样的价值。
