Redis从入门到实战:核心数据结构、缓存治理与高可用全解析
2026/9/14 18:17:40 网站建设 项目流程

先从一个查了很久的真实故障说起。当时我们的后端服务遇到一次大促流量,接口平均响应时间从 80ms 一路涨到 2 秒多,数据库连接池被打满,慢查询日志里反复出现同一条 SQL——查某个秒杀商品的详情。同一个 key 在短时间内被几万个请求同时查询,数据库当然扛不住。后来我们引入 Redis 做缓存,把热点数据挡在数据库前面,接口耗时直接掉到 5ms 左右。Redis 就是在这个背景下,成为后端开发绕不开的“第一个中间件”。

这篇文章想做的,不是给你罗列一堆命令,而是把 Redis 从安装部署、五种核心数据结构、持久化、缓存治理、分布式锁、内存淘汰,到主从哨兵高可用,整个入门链路串起来。每部分都会结合真实业务场景讲“为什么这么做”,也会把我实际踩过的坑直接标出来,省得你再走一遍弯路。无论你是刚接触 Redis 的在校学生,还是准备系统补基础的后端工程师,都可以照着这篇文章从零把它跑起来,并理解每个操作背后的设计缘由。

1. 先用业务场景搞懂 Redis 到底解决了什么问题

1.1 缓存层不是数据库的替代品,而是“挡箭牌”

很多人第一次听说 Redis,记住的标签就是“缓存”。这个说法没错,但要理解到位,得先搞清楚它到底在系统里扮演什么角色。数据库把数据存在磁盘上,靠索引和预读来提速,但磁盘 IO 的速度再快,跟内存相比也差着几个数量级。当同一批数据被高频访问时,每次都去打数据库,就是在拿系统的瓶颈点硬扛流量。

Redis 做的事情,是把高频访问的数据从磁盘搬到内存,让请求在内存里直接被“接住”。用大白话说,它就是一层挡在数据库前面的挡箭牌,把业务对数据库的读压力卸下来。你访问一次数据库可能要走 1~5ms,还得承担连接池、事务日志、磁盘随机读的连锁开销;而 Redis 的读写通常是在 0.1ms 这个量级,纯内存操作加高效网络模型,差距就是这么拉开的。

所以判断一个场景要不要用 Redis,先问自己:这个数据是不是被高频读?读多写少?对一致性要求是不是可以做到最终一致?如果三个问题都符合,那就非常适合用 Redis 做缓存。反过来,如果数据本来就很少有人读,或者写入非常频繁且要求强一致,那 Redis 不是万能的,硬上只会引入新的复杂度。

1.2 Redis 和 Memcached 的选型对比:为什么它不是简单的“升级版”

在 Redis 之前,Memcached 才是“缓存”领域的代名词。不少老项目里至今还有 Memcached 的身影,所以面试也爱问两者区别。直接说结论:Memcached 能做的事 Redis 基本都能做,而且 Redis 做得更多,但多出来的能力也意味着你必须更懂它。

对比维度RedisMemcached
数据类型String、Hash、List、Set、ZSet 等丰富类型仅支持 key-value 字符串
持久化RDB、AOF 支持数据落盘纯内存,重启数据即丢
主从复制原生支持,配合哨兵可实现自动故障转移不具备内置主从复制能力
线程模型单线程执行命令,6.0 起网络层多线程多线程处理网络请求
适用定位缓存、计数器、分布式锁、排行榜、队列等简单的分布式缓存

这个表不是让你背下来,而是帮你理解设计上的取舍。Memcached 把简单做到了极致,所以它的性能天花板也很高;但 Redis 选择用更丰富的数据结构和持久化能力,去覆盖更多的业务场景。实际项目里,我现在的默认选择基本就是 Redis,只有在纯粹追求极致缓存性能、完全能接受数据丢失、且只存简单字符串的老系统里,才会考虑 Memcached 保留不动。

1.3 Redis 的适用边界:哪些场景不适合硬上

讲完“Redis 能做什么”,必须泼一盆冷水说说“它做不了什么”。首先是复杂查询,比如多表关联、条件聚合、模糊搜索,这些是数据库和搜索引擎的强项,放在 Redis 里强行实现只会是一场灾难。其次是强事务场景,Redis 的事务模型和关系型数据库的 ACID 完全不同,它不保证回滚,也没有隔离级别,订单扣款这类场景不能靠 Redis 事务来保证资金安全。再次是超大数据量场景,Redis 是内存数据库,数据量大到内存装不下时,成本会非常夸张,这时候需要考虑冷热分层或引入其他存储方案。

一句话总结:Redis 是“高频小数据”的加速器,不是“全量大数据”的仓库。搞清楚了边界,再看后面的安装和配置,你会知道每一项参数背后是在为什么场景服务。

2. 安装部署三路线:Windows、Linux、Docker 里把 Redis 跑起来

2.1 Windows 本地安装的两条路径

虽然 Redis 官方并没有提供非常完整的 Windows 原生支持,但企业级项目里确实有很多开发同学日常用的是 Windows 电脑。第一条路径是直接下载 Windows 移植版本。GitHub 上 tporadowski 维护的 Redis Windows 版本流传最广,支持到 5.0.14 左右。解压后打开一个命令窗口,先启动服务端:

redis-server.exe

再打开另一个窗口验证:

redis-cli.exe ping

看到 PONG 就表示服务已经起来了。这种方式的优点是快,缺点是版本通常落后于 Linux 官方版本,所以它只适合本地学习验证,不建议部署到生产。

第二条路径是 Windows 上装个 WSL(Windows Subsystem for Linux),然后在 WSL 里按 Linux 的方式装。这样你既能体验 Linux 环境,又可以直接执行 Redis 官方最新版本的命令,本地开发和线上环境更接近。我个人更推荐这条路径做环境模拟,因为入门阶段最容易遇到“本地能跑、线上起不来”的坑,早点适应 Linux 环境能少踩很多雷。

2.2 Linux 编译安装与基础配置

Linux 服务器上安装 Redis,最稳妥的方式还是下载源码自己编译。以 7.x 版本为例,整体步骤大约四分钟:

wget https://download.redis.io/releases/redis-7.4.1.tar.gz tar xzf redis-7.4.1.tar.gz cd redis-7.4.1 make make install

前提是系统装了 gcc、make 等编译工具链。编译结束直接执行 redis-server 就能启动,但我一般会加--daemonize yes让它后台运行:

redis-server --daemonize yes redis-cli ping

这里有一个新手常见的疑问:为什么不像 Windows 一样直接下个安装包?原因很简单,生产服务器环境千差万别,源码编译能让你在编译过程中提前暴露环境依赖问题,而且编译出的二进制文件与系统兼容性更好。后面你如果需要定制某些编译参数,比如打开 TLS 支持,也必须在源码编译阶段完成。

2.3 用 Docker Compose 跑一个开发环境

现在很多团队已经默认用 Docker 来管理中间件了,Redis 也不例外。我建议你哪怕本地不装 Redis,也要把 Docker 这条路线跑通,因为后面学主从、哨兵时,Docker Compose 能让你用很低的成本搭出一整套模拟环境。开发环境我通常就用一个最简配置:

services: redis: image: redis:7.4 container_name: redis-dev restart: always ports: - "6379:6379" command: redis-server --requirepass 123456 --appendonly yes --maxmemory 512mb --maxmemory-policy allkeys-lru volumes: - redis-data:/data volumes: redis-data:

把上面内容保存为 docker-compose.yml,在同目录执行docker compose up -d,Redis 就起来了。这里故意加了密码、持久化和内存淘汰参数三项配置,后面章节里会逐一展开。用 Docker 的好处是干净,玩坏了随时docker compose down再重建;坏处是如果你完全不懂容器网络,后面做主从时会困惑为什么容器之间能通、宿主机连不上,这个我在第八部分专门讲。

2.4 安装完最容易踩的三个坑

第一个坑是protected-mode。Redis 默认开启保护模式,如果你没有设置密码,它只接受本机回环地址 127.0.0.1 的连接。很多人在 Linux 上启动完说外部连不上,以为是防火墙问题,其实改一下 bind 配置和密码就解决了。我建议本地学习时不要图省事直接关掉保护模式,而是设一个密码,这样既安全又能复现生产环境的认证流程。

第二个坑是daemonize yes之后日志去哪了。后台运行后,启动信息不会打印在当前屏幕上,默认输出到配置文件里的 logfile。如果没配 logfile,你甚至看不到日志,遇到启动失败会一脸懵。排查技巧是先不带 daemonize 参数前台启动,让错误直接打出来,看到具体报错再改配置。

第三个坑是 Windows 下端口被占用。Windows 上的 Hyper-V、WSL 网络组件经常占用 6379 端口,启动 redis-server 时可能报 bind 失败。用netstat -ano | findstr 6379找到占用进程,或者干脆在本地配置里把端口改成 6380,先跑起来再说。

3. 五种核心数据结构决定你的使用层次

3.1 String:别只会当缓存用,它还是计数器和高频操作的主角

String 是 Redis 里最基础、也最容易低估的类型。它的本质就是一个二进制安全的字符串,最大能存 512MB,可以存普通文本、JSON 序列化后的对象、数字,甚至图片的二进制内容。入门操作基本就是SETGET

SET user:1001:name "张三" GET user:1001:name

但 String 真正的威力在它的自增自减能力。INCR系列命令能在一个原子操作内对数值加一,不需要你先读出来再写回去。比如一个文章阅读量计数器,直接执行INCR article:read:1001,每次请求加一,不需要加锁也不会出现并发覆盖问题。这在数据库里用 UPDATE 也能实现,但要付出行锁和事务的开销,在高频场景下,Redis 的原子自增有着不可替代的性能优势。

使用 String 存储对象时,常用的做法是把对象序列化成 JSON 字符串再存,比如SET user:1001 '{"name":"张三","age":28}'。这种方式的优点是存取简单、兼容性好;缺点也非常明显——如果你想修改其中一个字段,必须把整个字符串取出来反序列化、修改、再序列化存回去。业务里对象字段比较多且经常只改部分字段时,就要考虑用 Hash 了。

3.2 Hash:对象存储的天然载体,可读性秒杀 String

Hash 在 Redis 里的结构相当于对象或 map。它允许你在一个 key 下面挂多个 field-value 对,非常适合存储用户、商品、订单这类结构化数据。示例:

HSET user:1001 name "张三" age 28 city "杭州" HGET user:1001 name HGETALL user:1001 HINCRBY user:1001 age 1

对比 String 存 JSON,Hash 的优势是字段级操作。我只想改用户的年龄,HINCRBY一下就完成了,不需要动其他字段。生产环境在做会话存储时,也经常用 Hash 保存用户登录态信息,比如session:{token}下面挂 userId、loginTime、expireTime,查询单字段方便,整体过期时间可以由 key 的 TTL 统一控制。

Hash 还有一个特别实用的上层用法:当你用 Redis 做配置中心或开关系统时,把一组配置放在一个 Hash 里,修改某个配置项时只影响其中一个 field,其他字段的缓存仍然可用,不会造成整个配置缓存失效。这个细节在缓存治理章节还会再提。

3.3 List:消息队列与分页列表的底层结构

List 在 Redis 里是一个双向链表结构,既可以从头部压入,也可以从尾部弹出。最经典的用法是LPUSH配合BRPOP实现一个简单的消息队列:生产者往列表左边塞消息,消费者从右边阻塞等待消息。

LPUSH msg:queue "task-001" BRPOP msg:queue 0

这里的 BRPOP 带阻塞参数,0 表示没有消息就一直等。这种方式比轮询更省资源,很多轻量级业务场景不引入 RabbitMQ、Kafka 时,就是靠这个组合顶上的。不过要提醒一句:Redis 列表队列没有消费者确认机制,消息被弹出后消费者如果崩溃,这条消息就丢了。所以它适合任务可以重试、即使丢失也能兜底的场景,核心业务队列还是交给专业消息中间件。

List 的另一个常见场景是分页查询,比如论坛的最新评论列表。LRANGE key 0 9取第一页,LRANGE key 10 19取第二页,相当于按插入顺序的倒序读取。加上LTRIM可以只保留最近 N 条,天然适合做“最近浏览记录”“操作日志”这种只关心最新几条数据的业务。

3.4 Set 和 ZSet:去重、标签与排行榜

Set 是无序集合,自动去重,支持多个集合之间的交并差运算。做好友列表、用户标签、抽奖去重都很顺手:

SADD user:1001:tags "Java" "Redis" "后端" SISMEMBER user:1001:tags "Java" SINTER user:1001:tags user:1002:tags

上面SINTER是求两个用户共同标签,用来做兴趣推荐或好友推荐。很多人学 Set 只记住去重,忽略了集合运算这个杀手级特性。在实际项目里,用户有没有某个权限、商品在不在某几个分类下,这类“包含关系”用 Set 的SISMEMBER判断,时间复杂度是 O(1),比数据库里 IN 查询快很多。

ZSet 是加权的 Set,每个元素带一个 score 分数,Redis 内部根据分数排序。排行榜功能基本就是为它量身定做的:

ZADD rank:2025 100 "user:1001" ZINCRBY rank:2025 10 "user:1001" ZREVRANGE rank:2025 0 9 WITHSCORES

ZADD初始分数,ZINCRBY给某个用户加分,ZREVRANGE取分数最高的前十名。除了排行榜,延时队列也能用 ZSet 实现:score 存执行时间戳,轮询时用ZRANGEBYSCORE key -inf now取出所有已到期的任务,处理完再ZREM删除。这种玩法面试也爱问,因为它能体现你对数据结构的理解层次。

4. 持久化选型:RDB、AOF 与混合持久化的取舍

4.1 RDB 快照的触发机制与丢失窗口

Redis 默认开启的是 RDB 快照持久化。它会定期把内存中的全量数据生成一个二进制快照文件,默认叫 dump.rdb。快照的触发条件是配置里的 save 规则,典型配置是:

save 900 1 save 300 10 save 60 10000

意思是:900 秒内至少 1 次写操作就做一次快照,300 秒内至少 10 次写操作做一次快照,60 秒内至少 10000 次做一次快照。这种“条件触发”的设计很务实,低频写入时不会频繁落盘,高频写入时又能保证数据不太旧。

但 RDB 的问题也很明确:它是一个时间点快照,两次快照之间如果 Redis 崩溃或断电,这期间写入的数据全部丢失。你可以回忆一下本地服务没保存就断电的后果,RDB 的开销机制与之类似。如果业务能接受最近几十秒甚至几分钟的数据丢失,RDB 够用;但凡是涉及订单、交易痕迹这种不能丢的场景,就必须开 AOF。

4.2 AOF 日志与刷盘策略:每隔一秒钟的妥协

AOF 持久化的思路是“写操作日志”。每次 Redis 执行写命令,都会把命令追加到 aof 文件末尾,重启时重新执行日志里的命令,从而恢复数据。相比 RDB 的全量快照,AOF 的恢复粒度更细,丢失数据的窗口更小。关键配置是 appendfsync 的三种策略:

策略行为数据安全性性能表现
always每次写命令都 fsync 到磁盘最安全,最多丢一个命令最慢
everysec每秒 fsync 一次最多丢 1 秒数据推荐
no由操作系统决定何时刷盘可能丢较多数据最快

生产环境我基本固定用 everysec,这是数据安全性和性能之间的一个合理折中。顺便补一个知识点:AOF 文件会随着写入不断变大,Redis 提供了重写机制,用BGREWRITEAOF或达到自动重写阈值时,把当前数据的最短命令集重新生成一份日志,避免文件无限膨胀。不要把重写想得多复杂,它就是压缩日志的“瘦身”操作。

4.3 混合持久化和备份恢复演练

Redis 4.0 之后提供了混合持久化,配置项是aof-use-rdb-preamble yes。它的做法很巧妙:AOF 文件头部用 RDB 格式记录全量数据,后面追加增量命令日志。这样重启恢复时先加载 RDB 快照,再执行增量日志,恢复速度快,数据丢失窗口又小。我现在的默认配置就是 AOF 开启 + 混合持久化开启。

持久化配置只是第一步,真正的“生存能力”来自恢复演练。我一个实际建议:每季度至少在测试环境干一次“删库恢复”实验。步骤很简单,先redis-cli shutdown,然后把 dump.rdb 或 appendonly.aof 从备份目录拷回来,重新启动 Redis,用DBSIZE核对数据量是否与预期一致。不要等到真出故障时才第一次试恢复流程,那种“看着配置妥当、恢复时才发现文件损坏”的教训,我见过不止一次。

5. 缓存治理:穿透、击穿、雪崩与数据一致性

5.1 缓存穿透:请求的是“不存在的数据”,Redis 根本拦不住

缓存穿透是指请求的数据在缓存和数据库中都不存在。比如一个用户传了个不存在的商品 ID,Redis 查不到,于是请求继续打到 MySQL,MySQL 也查不到。如果是恶意循环攻击,大量这样的请求会直接绕过缓存,把数据库击穿。

解决办法有三种层次。第一层是参数校验,比如 ID 必须为正整数、长度必须在合法范围内,这能从源头挡掉大部分非法请求。第二层是缓存空值:查不到数据时也在 Redis 里写一个空 value,设置较短的 TTL(比如 60 秒),后续请求直接返回空结果,不再穿透到数据库。第三层是布隆过滤器,在查询前先判断这个 key 是否可能存在,如果过滤器说不存在,直接返回,这个方案的缺点是布隆过滤器存在误判率,实现成本也更高。实际项目中不太厚重的接口用“空值缓存”就够了,高风险场景再上布隆过滤器。

5.2 缓存击穿:热点 key 过期的那一瞬间,谁能扛住

缓存击穿和穿透只差一个字,问题却完全不同。击穿是缓存里明明有这个 key,但它正好在某一刻过期了,此时大量并发请求同时涌进来,全部没有命中缓存,同时打到数据库。典型场景就是微博热搜、秒杀商品详情这类超高热点数据。

最常见的解决办法是互斥锁加缓存重建。请求发现缓存没命中时,先尝试获取一个 Redis 分布式锁(后面章节会讲),拿到锁的请求去数据库查询并重建缓存,其他请求短暂等待后再次查询缓存就能命中。缺点是一旦缓存重建时间较长,请求会产生一定的等待延迟。另一种思路是“逻辑过期”:在 value 里存一个过期时间字段,读时发现逻辑上过期不影响返回旧值,但后台异步线程去重建缓存并更新 value。这种方式对耗时重建非常友好,但对代码的侵入性更高一些。

5.3 缓存雪崩:大面积 key 一起过期引发的连锁反应

雪崩是击穿的扩大版。击穿是一个热点 key 过期,雪崩是大量 key 在同一时间段集中过期,或者 Redis 实例直接宕机,导致所有请求都落到数据库,数据库瞬间被压垮。最容易出问题的一种做法,是给缓存数据设置一个统一的固定过期时间,比如全部 30 分钟,那么每 30 分钟就会迎来一次雪崩峰值。

基础解法是在过期时间上加上随机因子,比如 30 分钟加上 0~300 秒的随机值,让过期时间错开。进阶解法是采用多级缓存,Redis 之上再放一层本地缓存(比如 Caffeine),本地缓存可以设计成“永不主动过期”,而是由后台任务定时刷新,这样即使 Redis 整层不可用,本地缓存也能扛住一部分流量。最后一定要加限流降级兜底,当数据库压力超过阈值时,放弃非核心数据的实时性,直接返回旧缓存或提示稍后再试。

5.4 缓存与数据库的双写一致性:Cache Aside 模式的细节

缓存治理绕不开一致性问题。现在的主流模式还是 Cache Aside(旁路缓存):读的时候先读缓存,不命中则查数据库并回填缓存;写的时候先更新数据库,再删除缓存。

问题恰恰出在“先更新库还是先删缓存”的顺序上。如果先删缓存再更新数据库,在删完缓存到更新数据库之间有一个时间窗口,另一个线程读到旧数据并回填缓存,就会把旧数据重新写进缓存。所以实践上更推荐“先更新数据库,再删除缓存”。为什么删除比更新好?因为缓存重建成本高,而且更新缓存与数据库操作叠加容易出现并发覆盖。

有些场景容不下“先更新库再删缓存”的窗口,可以加“延迟双删”:更新完数据库后删除一次缓存,等几百毫秒后再删除一次。第二次删除的目的是清掉那几百毫秒窗口里被并发请求回填的旧缓存。要注意,这个方案仍然不是强一致,它只是把不一致窗口压缩到很小。真正需要严格一致的场景,应该考虑用订阅数据库 binlog 的方式来同步缓存,而不是在业务代码里去手动维护。

6. 事务、Lua 与分布式锁:把原子性用在刀刃上

6.1 Redis 事务能做到什么程度,做不到什么程度

Redis 事务用MULTIEXECDISCARD三个命令组合。开启 MULTI 之后,后续命令不会立刻执行,而是进入一个队列,EXEC 时再一并发送执行。这种批量执行可以保证队列里的命令按顺序、原子地执行,中间不会插入其他客户端的命令。

但必须明确:Redis 事务不支持回滚。如果执行过程中某条命令语法有误,它是不会执行前面命令再撤回的,而是直接报错,已执行的结果保留。它也不提供关系型数据库那种隔离级别,只是“打包执行一批命令”而已。事务的实用性其实是有限的,很多初学者会误以为它像 MySQL 事务一样安全,这个认知必须纠正。在实际业务代码里,如果只是希望多条命令原子执行,我更推荐用 Lua 脚本。

6.2 Lua 脚本:真正的原子操作利器

Lua 脚本在 Redis 里的定位,就是“把多条命令打包成一个整体,由 Redis 单线程执行,中间不会被插队”。它天然具备原子性,也天然避免了很多竞态问题。比如之前分布式锁里判断锁值并删除的经典脚本:

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

这个脚本用一段逻辑完成了“判断+删除”两步操作,第二步不会因为服务崩溃而漏执行。如果你在处理限流、库存扣减、排行榜更新这类需要“先读后写且中间不能被打断”的逻辑,第一反应应该是能不能用 Lua 解决,而不是去加分布式锁。

6.3 分布式锁的正确实现与 Redisson 的价值

分布式锁是 Redis 在分布式系统里最经典的应用之一。最朴素的实现是SETNX,但只写一行SETNX lock:order 1是一个标准反模式:如果客户端拿到锁后崩溃了,这个锁永远不会释放,后面的请求全部卡死。补救办法是给它加过期时间,但SETNXEXPIRE是两条命令,不是原子的,中间又可能出事。

正确写法是 Redis 2.8 之后提供的原子命令:

SET lock:order <requestId> NX PX 30000

NX 表示 key 不存在才设置,PX 设置过期时间,requestId 用来标识当前持有锁的客户端。释放锁时不能直接 del,因为可能你的锁已经因为执行业务太久自动过期,其他客户端拿到了同一把锁,你一个 del 把别人的锁删掉了。所以释放必须用前面那段 Lua 脚本:先比对 value 是自己,才执行删除。

这些逻辑其实都很机械,生产环境不建议手写,直接使用 Redisson 这种封装好的客户端。Redisson 的锁自带“看门狗”机制:默认加锁 30 秒,如果持锁线程还在执行,它会自动续期;执行完主动释放。这样就不用手动估算业务耗时,也避免了业务没跑完锁就过期的问题。另外提一句 RedLock,它的思路是让锁在多个独立 Redis 节点上分别加锁,用来降低单点风险,但在业界争议很大,普通项目用单节点 Redis + Redisson 就足够稳定了。

7. 内存淘汰与过期策略:Redis 内存被打满时的存活之道

7.1 过期删除的两种机制:想了想,还是定期加惰性

Redis 里的 key 可以设置 TTL,到期后会被删除。删除并不是“时间一到立刻删”,而是靠两种机制配合:惰性删除和定期删除。惰性删除的意思是,当某个请求访问一个 key 时,Redis 会先检查它是否过期,过期了就先删除再返回空。这种方式的优点是省资源,缺点是过期了没人访问就一直在内存里占地方。定期删除则是 Redis 每隔一段时间随机抽出一批设置了过期时间的 key,检查并删除其中的过期 key。

为什么不用定时器逐个精确删除?因为那需要一个额外的线程不断扫描所有 key,代价太高。反之,如果只靠惰性删除,大量过期 key 会堆积成“内存垃圾”。所以 Redis 用这两种机制配合,希望达到一种平衡:大多数过期 key 在访问时被惰性清掉,没被访问到的,定期任务也会慢慢扫出来。也正因为这种设计,你设置相同 TTL 的 key,删除时间不会是分秒不差的。

7.2 八种内存淘汰策略:maxmemory-policy 怎么选

当 Redis 内存达到maxmemory上限时,会触发内存淘汰策略。这个策略不是随便设的,它直接决定了哪些数据会被保留、哪些会被清理。常用策略如下:

maxmemory-policy行为适用场景
noeviction不淘汰数据,写命令直接返回错误不能容忍缓存丢失的核心数据
allkeys-lru从所有 key 中淘汰最近最少使用的通用缓存,最常用
volatile-lru只从设置了 TTL 的 key 中淘汰最近最少使用的缓存与永久 key 混合存储
allkeys-lfu从所有 key 中淘汰访问频率最低的冷热数据访问频率差异明显的场景
volatile-lfu只从设置了 TTL 的 key 中淘汰访问频率最低的访问频率模型清晰的数据
allkeys-random从所有 key 中随机淘汰所有 key 访问概率均等
volatile-random只从设置了 TTL 的 key 中随机淘汰操作均匀且带 TTL
volatile-ttl从设置了 TTL 的 key 中淘汰剩余时间最短的优先保留新鲜数据

我的默认习惯是 allkeys-lru,因为绝大多数缓存业务都符合“热点数据频繁访问,冷数据长时间无人问津”的规律。需要注意:如果业务中有一些 key 是长期存在的配置项,不希望被淘汰,那应该把它们归到一种单独的存储逻辑里,比如用固定前缀区分,并在淘汰策略上选择 volatile-lru,保证永久 key 不会被清掉。

7.3 内存监控与调优:用 info 命令看门道

配置完成不代表一劳永逸,上线后还要会看指标。最常用的命令是redis-cli info memory,它会输出当前 Redis 已用内存、峰值内存、碎片率等关键数据。used_memory_human直接看当前占用;mem_fragmentation_ratio是碎片率,正常情况下在 1~1.5 之间,如果远大于 1.5,说明内存碎片化严重,可能需要重启或执行内存整理。

还有一个常见问题:为什么内存明明没到 maxmemory,却感觉响应变慢了?这时要看info stats里的evicted_keys,如果这个值在持续增长,说明淘汰策略已经在高频工作了,说明 maxmemory 设置不合理或数据过多,需要扩容或清理无用缓存。我踩过的坑是给 Redis 设了一个很小的 maxmemory,结果高峰流量一来,evicted_keys 暴涨,大量缓存被过期淘汰,缓存命中率直线下降,数据库压力反而更大了。调参前一定要先观察真实内存增长曲线。

8. 主从复制与哨兵模式:Redis 高可用的最小完整方案

8.1 主从复制的同步原理:先全量,再增量

单点 Redis 最怕的是宕机,一旦挂了,所有依赖缓存的请求都会压到数据库。主从复制是解决单点问题的第一步:一台主节点负责写,多台从节点负责读和冗余备份。从节点通过REPLICAOF指令指定主节点:

REPLICAOF 192.168.1.10 6379

第一次连接时,从节点会请求全量同步,主节点把当前数据生成 RDB 快照发给从节点,从节点加载后,再持续接收主节点随后的写命令并执行。此后进入增量同步阶段,主节点把写命令发送到复制积压缓冲区,从节点持续拉取执行。

这个机制里有几个关键点:全量同步比较耗时,如果主节点数据量很大,从节点的首次同步可能持续几秒甚至更久;复制是异步的,主节点写成功不等待从节点确认,所以从节点数据有轻微延迟。生产环境里,从节点通常设置为只读,防止客户端误写从节点导致数据不一致。如果你观察到主从数据差异很大,优先检查网络延迟和从节点的处理能力,而不是怀疑 Redis 复制协议有问题。

8.2 哨兵模式:监控、通知与自动故障转移

主从复制本身只能做冗余和读写分离,不能自动处理故障。如果主节点宕机了,从节点不会自动升级为新的主节点,整个写服务就断掉了。哨兵(Sentinel)就是来解决这个问题的,它会不断监控主节点和从节点的健康状态,发现主节点下线后,会从从节点中推举一个成为新的主节点,并把其他从节点重新指向新主节点,随后通知客户端新主节点的地址。这个动作叫故障转移。

一个细节值得注意:哨兵通常要部署奇数个节点(至少三个),因为它依赖“多数派”来决定主节点是否真的下线。如果只有两个哨兵,网络分区时可能无法达成共识。很多人入门时直接在单机上跑一个哨兵做实验,看效果是可以的,但生产环境必须按奇数节点部署。

8.3 Docker Compose 搭建主从哨兵的完整配置

用 Docker Compose 搭一套“一主一从一哨兵”的环境,是练习高可用最好的方式。先准备 sentinel.conf:

sentinel monitor mymaster redis-master 6379 1 sentinel auth-pass mymaster 123456 sentinel down-after-milliseconds mymaster 5000 sentinel failover-timeout mymaster 10000

注意这里mymaster是自定义的监控名称,redis-master是 Docker 网络里的容器主机名,不是本机 IP。sentinel monitor ... 1最后的 1 表示一个哨兵认定主节点下线,就触发故障转移。

再写 docker-compose.yml:

services: redis-master: image: redis:7.4 container_name: redis-master command: redis-server --appendonly yes --requirepass 123456 ports: - "6379:6379" networks: - redis-net redis-slave: image: redis:7.4 container_name: redis-slave depends_on: - redis-master command: redis-server --slaveof redis-master 6379 --masterauth 123456 --requirepass 123456 networks: - redis-net redis-sentinel: image: redis:7.4 container_name: redis-sentinel depends_on: - redis-master - redis-slave volumes: - ./sentinel.conf:/etc/redis/sentinel.conf command: redis-sentinel /etc/redis/sentinel.conf networks: - redis-net networks: redis-net:

启动之后,在哨兵容器里执行redis-cli -p 26379 info sentinel,如果看到Sentinels=1Slaves=1,说明哨兵已经正确感知整个拓扑。这里有一个很容易困惑的点:如果 start 日志里没有出现+monitor master mymaster这条消息,大概率是 sentinel.conf 里的容器名解析不了,或者mymaster名称不一致。另外,sentinel 运行时会尝试把状态写回配置文件,如果文件权限是只读,启动会有异常或更新失败,给 sentinel.conf 加上写权限可以避免这个坑。验证故障转移最简单的办法是docker stop redis-master,过一会儿再查看哨兵日志,你会看到它自动完成了从节点推举和切换。

9. 可视化客户端、序列化注意点与面试高频自测

9.1 趁手的可视化客户端怎么选

命令行玩得再溜,日常开发时也需要一个可视化客户端来查看 key 和数据的分布。Redis 官方推出了 RedisInsight,界面现代,支持数据浏览、命令分析、慢查询查看,而且对 Redis 系列版本支持得很全。如果你更习惯开源免费的工具,还有个 Another Redis Desktop Manager(ARDM),它同时支持 Windows、macOS、Linux,连接管理做得不错,适合本地日常调试。热搜词里也经常能看到 Redis Desktop Manager,一些版本在旧系统上反而更稳定,选哪个不强求,关键是别忽略加密连接和密码认证,生产环境连接一定要走 TLS,别为了省事在公网裸连 Redis。

连接不上时,按这个顺序排查:先redis-cli ping确认服务本身正常,再检查 bind 配置、端口监听、密码认证,最后看云安全组和本地防火墙。这个排查顺序能覆盖 90% 的“可视化工具连不上 Redis”问题。

9.2 序列化与日志:两个容易被忽略的细节

“Redis 序列化”在很多 Java 项目里是个会莫名从缓存取不到数据的问题。比如使用 Spring Data Redis 时,如果不自定义序列化器,默认会用 JDK 序列化,key 前面会带一堆类型字节,value 是一长串不可读的二进制内容,存进去之后在 Redis 里长得完全不像你预期的数据。更麻烦的是,不同版本之间 JDK 序列化的兼容性很差,一旦对象的类结构变化,从缓存里反序列化就可能直接报错。我的建议是:key 一律用 String 序列化并加上业务前缀,value 根据团队技术栈选 JSON、protobuf 或 msgpack,统一封装在客户端配置里,不要在业务代码里散落着各自不同的序列化方式。

日志方面,新手往往盯住持久化日志,忽略了 Redis 自身的慢查询日志。配置slowlog-log-slower-than 10000(单位微秒,这里表示 10ms)和slowlog-max-len 128,然后通过SLOWLOG GET查看哪些命令耗时过长。很多“Redis 变慢”的排查,第一步就是看慢查询,排除掉大 key 和复杂命令,再往下分析网络和内存碎片。

9.3 面试高频考点:把这些点串成一个自测清单

Redis 是面试里出现频率极高的中间件,围绕入门知识问到的问题基本是这几类:为什么 Redis 那么快、单线程为什么还能高效、数据类型的底层实现、缓存三大难题、分布式锁、持久化、淘汰策略、主从哨兵。我整理了一份自己面试别人时也会用的检查清单:

  • 能解释 Redis 的高性能来自内存存储、单线程避免锁竞争、IO 多路复用、高效数据结构四者叠加。
  • 能说清 6.0 多线程到底多线程在哪里:网络读写使用多线程,命令执行仍是单线程。
  • 能说出 String、Hash、List、Set、ZSet 各至少两个典型业务场景。
  • 能画出主从全量同步的步骤,说出复制积压缓冲区的作用。
  • 能说明哨兵故障转移的大致流程,以及为什么要奇数个哨兵。
  • 能写出正确的分布式锁命令和释放锁的 Lua 脚本。
  • 能说出 RDB、AOF、混合持久化的区别和适用场景。

这些点本质上都是这篇文章每个章节压缩出来的核心。能把它们完整讲清楚,Redis 入门这一关就算真正迈过去了。

最后再说一个从实际项目里得到的体会:别急着追新版本和新功能,先把五种数据结构用熟、把持久化和内存淘汰这两种“保命配置”弄懂,再逐步掌握主从哨兵和分布式锁。很多线上故障,不是 Redis 不够强,而是使用者对它的工作机制理解存在盲区。希望这篇文章能帮你把这些盲区一个个补齐。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询