Redis 是什么?从我线上一次事故说起。
老规矩,先聊点实际的。前几年我负责一个电商类的后端服务,当时数据库用的是 MySQL,日均请求量上来之后,最典型的场景就是首页的商品信息、用户购物车、热点活动配置这些数据被反复查询。单条查询也就几毫秒,问题是并发一高,数据库连接池直接被打满,紧接着就是慢查询堆积、CPU 飙高,最后整个服务雪崩。那天晚上我一边盯监控一边想一个问题:为什么同一份热点数据,要每次都去磁盘里"翻"一遍?
后来引入 Redis 做缓存层,把热点数据提前放内存里,问题迎刃而解。Redis,全称 Remote Dictionary Server,一个开源的内存键值数据库,它最核心的价值就是"快"——因为数据主要存在内存里,单线程处理简单命令时,读写性能可以达到每秒十万次以上。它是当前后端架构里不可或缺的中间件,也是面试必考、生产必用的基础组件。这篇内容我不打算念文档,而是从实际使用的角度,把 Redis 到底是什么、核心数据类型怎么用、持久化怎么做、高可用架构怎么搭、生产环境有哪些坑,一次性讲透。
1. Redis 为什么这么快:先从一条命令的生命周期看起
很多初学者听到"Redis 快"这个结论,就直接背下来了,但问到"为什么快"就支支吾吾。其实这个问题理解透了,你后面看性能排查、看调优方案都会轻松很多。
1.1 内存存储与高效数据结构:快的第一层底座
Redis 的所有数据都存在内存里。这一点看起来简单,实际上决定了它和传统关系型数据库最本质的区别。MySQL 的 InnoDB 引擎为了性能也设计了 Buffer Pool 内存缓冲,但最终数据还是要落到磁盘的 B+ 树索引文件里。而 Redis 的核心数据操作完全在内存层面完成,没有磁盘寻址、没有随机 I/O 的物理开销,这就好比一个人直接把东西放在手边和跑到仓库里去翻找,速度差别是量级的。
除了内存,Redis 内部针对不同数据类型设计了对应的底层编码结构。比如 String 类型除了常规的 SDS(简单动态字符串),还可能用 int 编码来存整数,用 embstr 编码来存短字符串;Hash 在字段少、值小的时候用 ziplist 紧凑存储,字段多了就升级为 hashtable。目的只有一个:在节省内存的同时保证操作高效。很多调优文档里讲的"ziplist 阈值"就是这个层面的东西。
1.2 单线程模型与 I/O 多路复用:为什么没有锁竞争反而更快
Redis 的网络请求处理和核心命令执行是在单线程里完成的。这个设计经常被误解,有人觉得单线程是"短板",实际上它避开了多线程编程里最头疼的锁竞争和线程切换开销。
那单线程怎么承受高并发?答案是 I/O 多路复用机制。Redis 基于 epoll(Linux 上)这类事件驱动模型,用一个线程同时监听成千上万个客户端连接的读写事件。当没有任何事件发生时,线程阻塞在系统调用上;一旦某个连接有请求进来,系统会立即通知 Redis 处理。这就像一个"服务生"同时照看很多桌客人,哪桌有人招呼就过去服务,而不是每桌配一个服务生傻等。
这里要强调一点:Redis 6.0 之后引入了多线程 I/O,主要用于网络读写层面的并发处理,但命令执行核心依然是单线程。所以你在写 Lua 脚本、使用事务时,依然可以基于"命令是顺序执行的"这个前提来设计,不用担心并发交错的问题。
1.3 单线程模型下的性能边界与误区
理解了单线程模型,也就理解了一个常见的性能铁律:Redis 的慢操作会阻塞所有后续请求。比如在生产环境里执行 KEYS 命令,它会遍历整个键空间,数据量大时可能导致 Redis 阻塞数秒,期间所有读写都卡住。正确做法是使用 SCAN 命令分批迭代,或者干脆在业务层维护一个索引集合。
再比如一次写入超大的 value(几 MB 甚至几十 MB),序列化和内存拷贝都会消耗较长时间;一次删除一个包含几百万元素的大集合,也可能阻塞主线程。这些都是"大 Key"问题,后面我会专门展开讲。总而言之,Redis 快是事实,但快是有条件的,快不等于你可以乱用。理解它的底层模型,是避免线上事故的第一步。
2. 五种基础数据类型:很多人只会用 String,其实亏大了
Redis 被称作"数据结构服务器"是有原因的。它的五种基础类型——String、Hash、List、Set、ZSet——每一种都对应着一类经典业务场景。我用实际案例逐个拆解。
2.1 String:不只是字符串,计数器、分布式锁、限流都要靠它
String 是 Redis 里最基础的类型,value 最大能存 512 MB。除了缓存 JSON 字符串,它最常见的用法是计数器,比如商品的浏览量、用户积分、库存数。
这里要注意的是 INCR 和 DECR 命令是原子操作。很多人在 Java 里用 RedisTemplate 调用increment()方法时踩过坑。比如热词里提到的那个报错:"ERR value is not an integer or out of range"。这个错什么意思?就是当前 key 存的值不是整数类型。常见原因有两个:一是同一个 key 之前被 SET 过一个非数字字符串,比如set stock abc,再执行 INCR 就会报错;二是这个 key 已经被 set 成浮点数了,比如 1.5,INCR 只支持长整型,对它执行也会报错。
举个真实场景:我在项目里用redisTemplate.opsForValue().increment("order:seq", 1)生成订单号,结果一开始没注意"order:seq"这个 key 被业务方手动 SET 成了一个带时间戳的字符串,于是线上就飘这个错。排查的时候不要只盯着代码,先到 Redis 里执行get order:seq看看存的到底是什么类型,再用type order:seq确认类型,基本就能定位。
此外,String 还可以配合 SETNX 实现分布式锁。核心思路:SET key value NX EX seconds,只有当 key 不存在时才能设置成功,利用这个原子操作获得锁;释放锁时为保证"只能删自己持有的锁",需要先用 GET 比对 value,再用 DEL 删除,整个过程要用 Lua 脚本保证原子性。后面讲并发扣库存的时候我再细说。
2.2 Hash:操作对象属性的正确姿势
Hash 类型相当于一个 key 下挂了多个 field-value 映射,非常适合存对象信息。比如用户资料:
HSET user:1001 name "张三" age 25 city "北京" HGETALL user:1001 HINCRBY user:1001 score 10相比把整个对象序列化成 JSON 塞进 String,Hash 的优势是可以单独读写某个字段,不需要把整个对象拉出来再序列化、再写回去。比如用户积分变更,只需要HINCRBY user:1001 score 10,高效又原子。
不过 Hash 也有一个容易被忽视的问题:字段级过期时间。Redis 的过期机制面向的是整个 key,Hash 里的单个 field 不支持 EXPIRE。如果你需要给对象的某个属性设置过期时间,比如优惠券的某个状态字段,只能拆成独立的 String key,或者在应用层自己做过期判断。很多人一开始没意识到这个限制,方案设计到一半才发现,很耽误事。
2.3 List、Set、ZSet:消息队列、去重、排行榜的经典玩法
List 是简单的字符串列表,底层是双向链表结构,支持从两端推入和弹出。它曾经被广泛用作轻量级消息队列,生产者用LPUSH塞消息,消费者用BRPOP阻塞读取消息。BRPOP 是阻塞版本,队列为空时会让连接挂起等待,避免空轮询。这个方案简单直接,适合中小规模、对消息可靠性要求不高的场景。但要注意:List 消息队列没有消费确认机制,消息被弹出后如果消费者处理失败,这条消息就丢了,而且没有广播能力。这也是后来 Redis Stream(Redis 5.0 引入)要解决的问题,Stream 支持消费者组、消息确认、Pending 列表,功能上更接近专业 MQ。
Set 是无序、去重的集合类型,适用场景很明确:去重。比如统计一个活动的 UV(独立访客数),用户每次访问就SADD activity:20250101 userId,然后SCARD activity:20250101就能得到当天不重复的用户数。Set 还支持交集、并集、差集运算,比如计算两个用户的好友共同关注,一句SINTER user:1:follow user:2:follow就搞定了,比在数据库里写关联查询不知道快多少倍。
ZSet 在 Set 的基础上给每个元素加了一个 score 分数,按分数排序。最经典的场景就是排行榜。比如积分榜,ZADD leaderboard 100 "userA",要查 Top10 直接ZREVRANGE leaderboard 0 9 WITHSCORES。ZSet 底层是跳表加哈希表,插入、删除、查找的时间复杂度都是 O(log n),性能非常好。电商里"热销榜""新人榜",游戏里"战力榜",全是这个套路。
3. 持久化选型:RDB 和 AOF 的组合拳怎么打
Redis 是内存数据库,一旦进程退出或者机器宕机,内存里的数据就全部没了。所以持久化不是可选项,而是必选项。Redis 提供了 RDB(快照)和 AOF(追加日志)两种机制,理解它们的原理和取舍,是生产环境配置的关键。
3.1 RDB 快照:全量备份的利器与"丢数据"的窗口
RDB 生成的是某个时间点的全量内存快照,默认文件是 dump.rdb。手动执行SAVE会同步阻塞主进程,数据量大时千万别在生产用;正确方式是BGSAVE,Redis 会 fork 一个子进程来生成快照,主进程继续处理命令。
RDB 的触发方式主要有三种:
- 手动执行
BGSAVE或SAVE - 配置自动触发,比如默认配置:
save 900 1 # 900 秒内至少有 1 次写操作 save 300 10 # 300 秒内至少有 10 次写操作 save 60 10000 # 60 秒内至少有 10000 次写操作 - 关闭 Redis 时,如果配置了 RDB,也会触发一次快照
RDB 是"定期全量快照",这意味着两次快照之间的数据可能丢失。比如 15 分钟一次快照,刚写完一条重要数据就宕机了,这条数据就丢了。所以在追求数据不丢失的场景下,RDB 不能单独扛大旗。但它也有不可替代的优点:文件体积小、恢复速度快,非常适合做冷备和灾备。我习惯每天定时把 dump.rdb 同步到云存储,保留近 7 天版本,出问题能快速回滚。
3.2 AOF 追加日志:每一次写操作都记录,但要控制文件膨胀
AOF 的思路是:把每一条写命令(SET、LPUSH、HINCRBY 等)以 Redis 协议格式追加到日志文件末尾,宕机后重放日志就能恢复数据。相比 RDB,AOF 丢失数据的窗口小得多,这取决于刷盘策略。
AOF 有三种刷盘策略,通过appendfsync配置:
| 策略 | 行为 | 数据安全性 | 性能影响 |
|---|---|---|---|
| always | 每条写命令都 fsync 到磁盘 | 最高,最多丢一条命令 | 最慢,吞吐下降明显 |
| everysec | 每秒 fsync 一次 | 较好,最多丢 1 秒数据 | 性能折中,生产最常用 |
| no | 交给操作系统决定何时刷盘 | 最差,可能丢大量数据 | 最快 |
生产环境我基本都用 everysec,兼顾性能和数据安全。你要做严格的金融类对账,可能得考虑 always,但要提前做好性能压测。
AOF 文件会不断增长,所以需要BGREWRITEAOF重写机制:子进程根据当前内存中的数据集,重新生成一条最小命令集合的 AOF 文件,把历史冗余命令清掉。Redis 也会根据配置自动触发重写,比如:
auto-aof-rewrite-percentage 100 auto-aof-rewrite-min-size 64mb意思是 AOF 文件比上次重写时增长了一倍,且文件超过 64 MB,就自动重写。
3.3 混合持久化:生产环境的最优解
很多团队在 RDB 和 AOF 之间"二选一",其实 Redis 4.0 开始支持混合持久化,这也是我目前线上配置的默认方案。开启方式:
aof-use-rdb-preamble yes具体效果是:BGREWRITEAOF 时,子进程先把当前内存快照以 RDB 格式写入 AOF 文件头部,再追加后续的增量写命令。这样既利用了 RDB 快速恢复的优势,又保留了 AOF 的少量数据丢失窗口。重启恢复时,Redis 加载 AOF 文件,前半段直接按 RDB 格式载入,速度快;后半段重放增量命令,数据完整。我在多台 4~8 GB 内存的实例上测试过,纯 AOF 恢复可能要几十秒,混合持久化能把时间压缩到几秒。这个优化效果非常明显,强烈建议生产环境开启。
另外提醒一个细节:RDB 和 AOF 同时开启时,Redis 重启优先加载 AOF 文件,因为 AOF 的数据完整度更高。如果 AOF 文件损坏,启动会失败,这时可以用 Redis 自带的修复工具redis-check-aof --fix处理。我自己遇到过一次 AOF 文件因磁盘写坏导致重启失败的情况,修复流程不复杂,但一定要有备份习惯。
4. 高可用架构:主从复制、哨兵、集群各自解决什么问题
很多人把 Redis 的三种高可用方案混为一谈,其实它们的定位完全不同。主从复制是基础,哨兵解决故障自动切换,集群解决数据量大、单机内存不够的问题。下面按层次拆开讲。
4.1 主从复制:读写分离的底层机制
主从复制是 Redis 高可用的地基。一个主节点(Master)可以挂多个从节点(Replica),从节点实时同步主节点的数据。最直接的价值是读写分离:主节点负责写,从节点负责读,把读压力分散到多个节点。
同步流程要理解几个关键点。第一次同步时,从节点发送 PSYNC 命令,主节点执行 BGSAVE 生成 RDB 快照传给从节点,同时把期间产生的写命令缓存到复制缓冲区;从节点加载完 RDB,再继续接收增量命令。之后的同步就是持续的命令传播阶段,主节点把写命令实时推给从节点。
配置主从也简单。传统方式是在从节点的 redis.conf 里加一行:
replicaof 192.168.1.10 6379或者在运行时执行REPLICAOF host port。用 Docker 部署主从时,注意要把端口映射出来,并确保网络互通,比如用 docker-compose 定义两个服务,从节点的启动命令里带上--replicaof参数。
主从复制有一个坑需要提前知道:它是异步复制的。主节点执行完写命令就立即返回客户端,从节点的同步存在延迟。如果主节点刚写完就宕机,而数据还没来得及传给从节点,这部分数据就丢了。所以在设计和选型时,对于必须保证不丢数据的场景,主从复制本身还不够,需要配合其他机制。此外,默认配置下从节点是只读的,直接往从节点写会报错,这算一个保护机制。
生产环境我一般在主从基础上再做读写分离:所有写请求走主节点,复杂统计、报表查询走从节点。但要注意,如果业务对数据一致性要求极高,刚写入就立刻去读,可能会因为复制延迟读到旧值。这种场景要么接受短暂不一致,要么在代码里强制读主节点。
4.2 哨兵(Sentinel):故障转移是"怎么发现"和"怎么切换"的完整链路
主从复制解决了读压力,但没有解决高可用:如果主节点宕机了,从节点不会自动上位,整个写入能力就断了。哨兵(Sentinel)就是来盯主节点、做自动故障转移的。
哨兵本质上是一个独立运行的 Redis 进程,它的作用可以用三句话概括:监控、通知、自动故障转移。部署哨兵要至少 3 个实例(奇数个),这是为了满足 Raft 协议的一致性要求,避免脑裂。比如主节点挂了,哨兵们要投票决定谁来做新主节点,3 个哨兵中超过半数(2 个)确认故障,才会触发切换。
"主观下线"和"客观下线"这两个概念要搞清楚。单个哨兵发现主节点心跳超时,会标记为主观下线;多个哨兵互相确认都发现主节点不可达,达到 quorum 数量后,才会标记为客观下线,进而开始选主。选主时会优先选择复制偏移量最大(数据最全)的从节点,同时会考虑从节点的优先级配置。
在 Spring Boot 项目里集成哨兵模式,配置很简单:
spring.redis.sentinel.master=mymaster spring.redis.sentinel.nodes=192.168.1.10:26379,192.168.1.11:26379,192.168.1.12:26379应用连接的是哨兵地址,哨兵会告诉你当前的主节点是谁;主节点切换后,客户端会自动感知并连接新的主节点。我在部署时踩过一个坑:哨兵的配置文件里sentinel monitor mymaster 127.0.0.1 6379 2,如果写的是 127.0.0.1,哨兵只会监控本机的 Redis,用在单机测试没问题,但各哨兵节点分布在多台机器上时就无法正确监控网络中的主节点。生产环境一定要写真实 IP 或可解析的主机名。
4.3 Redis Cluster:数据分片不是简单的"多放几台机器"
当单机内存不够用,或者写入并发太高时,就需要 Redis Cluster 集群。集群的核心机制是数据分片:整个键空间被分成 16384 个哈希槽,每个节点负责一部分槽位。执行CLUSTER KEYSLOT key就能算出一个 key 属于哪个槽,这个计算是基于 key 的 CRC16 校验值对 16384 取模。
举个例子,三主三从的集群,节点 A 负责 0~5460 槽,节点 B 负责 5461~10922 槽,节点 C 负责 10923~16383 槽。客户端计算key的哈希槽后,如果请求发给了错误的节点,该节点会返回一个 MOVED 重定向错误,客户端需要重新请求正确的节点。这也是为什么 Jedis、Lettuce 这类客户端会维护一张"槽位与节点映射"的缓存表。
集群模式需要注意几个限制:
- 不支持多 key 操作,除非这些 key 在同一个槽里。可以在 key 里使用哈希标签
{},比如{user:1001}:cart和{user:1001}:orders,大括号里的内容参与哈希计算,保证两个 key 落到同一个槽。 - 单 key 的 value 不宜太大,因为集群的数据迁移单位是槽,而大 key 会让迁移变得很重。
- 集群模式下的数据可靠性依赖于副本。每个主节点至少挂一个从节点,主节点挂了,从节点才能顶上。
集群扩容或缩容时,数据迁移过程对线上请求是有影响的。Redis 的迁移方式是按槽迁移,迁移过程中 key 可能从源节点迁到目标节点,客户端可能收到 ASK 重定向,需要感知这种临时状态。建议在业务低峰期做集群变更,并在迁移前充分评估数据量。使用redis-cli --cluster reshard命令时,可以指定迁移槽的数量和目标节点,但要计算好每个节点最终的槽位分布,避免数据倾斜。
用 Docker 搭建 Cluster 测试环境时,最简单的做法是docker run时用--cluster-enabled yes --cluster-config-file nodes.conf --cluster-node-timeout 5000参数启动多个实例,再通过redis-cli --cluster create命令完成集群初始化。要提醒的是,容器重启后 IP 可能会变,而 nodes.conf 里记录的是旧的节点信息,所以容器建议用固定 IP 或 host 网络模式。
5. 生产环境防坑指南:那些在文档里查不到的教训
前面讲的都是基础架构,这一节我重点说说在真实业务里踩过的具体坑。这些东西文档里大多不会写,但对线上稳定性影响极大。
5.1 未授权访问:最危险的安全漏洞
Redis 默认只绑定了 127.0.0.1,所以本机访问没问题。但不少人在部署时为了方便,把 bind 改成 0.0.0.0,又没设密码,这时 Redis 就像裸奔在公网上。攻击者可以连接后执行CONFIG SET dir /var/spool/cron/写入计划任务反弹 shell,或者写入 SSH 公钥实现免密登录,这就是经典的"Redis 未授权访问漏洞利用"链路。
解决方案不复杂:一是配置文件里bind指定内网 IP,不要把服务直接暴露到公网;二是设置强密码,在 redis.conf 里加requirepass配置;三是如果走公网访问,务必用防火墙或安全组白名单;四是不要用 root 用户运行 Redis 进程,尽量用独立低权限用户。
还有一个容易被忽略的点:很多人只在 redis.conf 里设置了requirepass,却忘了主从复制模式下,从节点连接主节点也需要认证。如果从节点没配置masterauth,主从同步会一直报"NOAUTH Authentication required",表现为从节点数据永远同步不过去。这个问题排查起来很隐蔽,我栽过一次,所以记得在主从两边的配置文件里都配好认证信息。
5.2 可视化客户端与序列化问题:乱码的真相
热词里有 redis desktop manager、another redis desktop manager 这些可视化客户端。它们确实方便,但你在 Java 项目里用 RedisTemplate 存数据后,打开客户端一看,可能是一堆类似\xAC\xED\x00\x05t\x00\x04name的二进制乱码,这是怎么回事?
凡是使用过 RedisTemplate 的人大概率都见过这个。原因很直接:Spring Data Redis 默认使用 JDK 序列化器,RedisTemplate 会把 Java 对象先序列化成二进制字节数组再写入 Redis。所以你在客户端上看到的不是可读字符串,而是序列化后的字节流。
解决方式是在配置类里显式指定 key 和 value 的序列化方式。生产环境我一般用 StringRedisSerializer 存 key,用 Jackson2JsonRedisSerializer 或 GenericJackson2JsonRedisSerializer 存 value,这样存进去的是可读的 JSON 字符串,排查问题友好很多。注意:序列化方式变更可能导致原有 key 无法反序列化,所以上线前要考虑是老 key 兼容还是清理重建。如果你只是临时看看数据,用 RDM 时选择 Hex 显示也能看出来大概内容,但不如从源头解决。
5.3 热 Key 与大 Key:缓存治理里的两个隐形杀手
大 Key 和热 Key 是缓存治理里最常见的两类问题,很多团队都是等线上出故障了才回头治理。
大 Key 指的是单个 key 的 value 特别大,比如一个 List 里有几百万条数据,或者一个 String 值有几 MB。大 Key 的危害很具体:一是读写大 Key 的耗时明显增加,会阻塞 Redis 单线程;二是在集群模式下,大 Key 所在节点会成为数据倾斜点;三是删除大 Key 时,如果直接用 DEL,主线程卡顿可能会持续数秒。解决方案有:对大集合分片存储,比如用多个小 key;对确实要删除的大 Key,用UNLINK命令异步删除,或者用 SCAN 分批删除元素。
热 Key 是某一瞬间被超高并发访问的 key,比如双十一的爆款商品、微博热搜词条。热 Key 会把请求全部打到同一台 Redis 节点上,导致单节点 CPU 被打满,而其他节点很闲。常见应对方案有:给热 key 加随机后缀,把访问分散到多个副本;做本地缓存(比如 Caffeine),把一层 Redis 变成两级缓存;或者对热 key 增加副本并让读请求负载均衡。我之前处理过一个案例,某个活动商品详情接口的 QPS 冲到 20 万,Redis 单分片扛不住,最后靠本地缓存加 key 的副本扩容撑住了。
5.4 并发扣减库存与 DECR 的原子性:别再走"先查后改"
很多业务场景需要扣减库存、扣减余额,比如下单扣库存、积分扣减。很多初级同学习惯先 GET 再 SET,或者先查出来再在 Java 内存里减一,然后写回去。这在并发情况下一定会出问题,属于典型的"竞态条件"。
正确姿势是直接使用原子命令:
// 扣减库存,返回扣减后的值 Long remain = redisTemplate.opsForValue().decrement("stock:1001", 1); if (remain < 0) { // 库存不足,需要把超卖的补回来 redisTemplate.opsForValue().increment("stock:1001", 1); throw new RuntimeException("库存不足"); }但要小心这个写法的 bug:如果并发量极高,多个线程同时 decrement,可能出现多个线程都拿到同一个负数结果,然后多个线程都执行 increment 回补,导致库存数量被错误恢复。更严谨的做法是用 Lua 脚本保证"判断库存并扣减"的整体原子性。在 RedisTemplate 里执行 Lua 脚本的代码结构大致如下:
String luaScript = "local stock = redis.call('GET', KEYS[1]) " + "if tonumber(stock) >= tonumber(ARGV[1]) then " + "return redis.call('DECRBY', KEYS[1], ARGV[1]) " + "else return -1 end"; Long result = redisTemplate.execute( new DefaultRedisScript<>(luaScript, Long.class), Arrays.asList("stock:1001"), "1" );Lua 脚本在 Redis 里是原子执行的,脚本执行期间不会插入其他命令,所以判断和扣减就能作为一个整体完成。这也是分布式锁和复杂原子操作的核心实现方式。热词里提到"java 使用 redistemplate 将 redis 的数减一",建议直接采用上面这种方式做。
对于分布式锁,相比早期用 SETNX 加 EXPIRE 两条命令的做法,现在推荐直接用一条原子命令:
SET lock:order:1001 uuid_value NX PX 30000这样加锁和设置过期时间保持在一条命令里,避免"锁刚加上就宕机导致死锁"的问题。释放锁时用前面说的 Lua 脚本判断 value 是否匹配再 DEL。
6. 从入门到落地的学习路径与选型建议
写到这里,Redis 的核心脉络基本清楚了。最后聊点偏"软"的经验:新人应该按什么顺序学习,以及什么场景才值得引入 Redis。
第一,不要一上来就抠源码。先把五种基础类型用熟,每个类型找两个真实场景做一遍,比如用 ZSet 写一个排行榜 demo,用 RedisTemplate 配置序列化并操作 Hash。这个阶段重点是"会用"。第二,理解持久化和主从复制的原理,用 docker-compose 搭一套一主两从三哨兵的架构,手动 kill 掉主节点,观察哨兵怎么切换主从。这个实验做一遍,比看十遍文档都管用。第三,再去看 Redis Cluster 官方文档,理解哈希槽和数据迁移,用 redis-cli 自己建一个三主三从集群试试。第四,遇到问题了再回头看源码,比如排查大 Key 阻塞、分析内存碎片率,这时候源码才能真正读进去。
关于选型,Redis 不是万能的。如果你的数据量很小,QPS 也很低,直接查数据库完全没问题,引入 Redis 反而增加架构复杂度。如果数据要求强一致性,Redis 的异步复制和缓存更新策略都要仔细设计。下面是我个人总结的几个"适合用 Redis"的信号:
| 场景特征 | Redis 的价值 |
|---|---|
| 热点数据读多写少 | 缓存层显著降低数据库压力 |
| 需要计数器、排行榜、去重统计 | 数据结构原生支持,性能极高 |
| 分布式环境下需要锁或限流 | 原子命令和 Lua 脚本天然适合 |
| 需要毫秒级响应 | 内存访问,平均延迟在亚毫秒到毫秒级 |
| 数据库扛不住大并发读 | 前置缓存或读写分离缓解压力 |
反向的"不建议用 Redis"信号也很明确:数据量大且必须全量落盘、需要复杂 SQL 关联查询、需要事务回滚和多表一致性——这些场景关系型数据库更合适。Redis 的事务虽然也有 MULTI 和 EXEC,但它没有回滚机制,命令队列里某条报错,其他命令照样执行,和 MySQL 事务完全不是一回事。
最后再分享一个小技巧:学习时给自己搭一个本地的可视化管理环境,个人使用可以选择 Redis Desktop Manager 或 Another Redis Desktop Manager,连接上之后,能直观看到不同类型的 key 长什么样。配合redis-cli --stat观察实时操作数,再配合MONITOR命令观察线上命令执行情况。不过我个人不太建议随便在生产环境用 MONITOR,它在高并发下会输出海量命令日志,反而拖慢性能。真想排查,用SLOWLOG GET 20看慢命令列表更安全,它只会把执行时间超过阈值的命令记录到日志里,不干扰线上业务。
Redis 这个东西,用起来不难,难的是把它用对、用稳。希望这篇文章能帮你把基础打牢,少走一些我走过的弯路。