Redis速记:从安装配置到缓存治理的实战笔记
Redis速记:从安装配到缓存治理,一份能直接落地的实用笔记
刚接触 Redis 的人容易把它当成一个“会飞的 HashMap”,恨不得所有热点数据都往里塞;用过一段时间的人又会走向另一个极端,觉得它不过是个缓存,出问题就重启。这两种想法都有点可惜。Redis 真正值钱的地方,在于它把内存操作、持久化、复制、分布式锁、自动补全这些能力压缩在了一套极简的命令模型里。这篇文章是我自己实际项目中反复用到、反复踩坑之后浓缩出来的速记,覆盖安装、数据类型、可视化工具、缓存治理高并发三件套、分布式锁、主从部署和常见面试题,适合刚上手的人快速建立一个完整框架,也适合写代码的老手留一份避坑清单。
很多人问我“Redis 到底能做什么”,我的回答通常不是背官网的 feature list,而是先讲清楚它解决问题的那条核心链路。下面就从这条链路开始拆。
1. 先弄明白 Redis 到底解决什么问题
1.1 从一次接口超时说起
假设你有一个商品详情接口,数据库里存了商品基础信息、库存、评论数。刚开始流量不大,查一次数据库十毫秒返回,一切岁月静好。某天活动上线,这个接口的 QPS 冲到了几千,数据库的连接池被占满,慢查询一个接一个,接口从 50ms 变成 2s,再变成超时。
这时候最简单的解法不是立刻上各种中间件,而是把热点数据放到一个更快的地方。Redis 就是一个非常典型的“更快的地方”,它把数据放在内存里,读写都在纳秒到微秒级别完成,单实例 QPS 可以冲到十万级甚至更高。改造成本也很低:查库之前先查 Redis,查不到再去查库,再回写 Redis。就这么一个改动,大部分读多写少的场景都能扛住。
1.2 Redis 凭什么能扛高并发
很多人不理解,为什么一个单线程模型的服务,反而比多线程的数据库服务更能扛高并发。关键点在于 Redis 把并发模型简化到了极致。它采用单线程处理网络请求和命令执行,省掉了多线程编程里最头疼的锁竞争、上下文切换、线程调度开销。再加上底层用了多路复用机制,一个线程可以同时盯着成千上万个客户端连接,谁有数据来了就处理谁,没有事做就阻塞等待。
这就像一个高速收费站的收费员,一次性可以照看几十条车道,哪辆车到了窗口就收哪辆车的费,而不是每条车道配一个收费员在那里干等。这种模型的优势在纯内存操作场景下非常明显,因为内存操作本身极快,瓶颈根本不在 CPU 计算,而在网络 IO 和锁开销,Redis 正好把这两块的浪费都省掉了。
1.3 哪些场景别硬上 Redis
Redis 也不是万能药。它本质上是内存数据库,内存很贵,数据量太大成本会失控;它的事务模型比较弱,复杂的事务回滚在 Redis 上很别扭;它的持久化虽然能做,但相对传统数据库来说,依然更适合做缓存层而不是唯一的数据存储。
我自己见过最典型的反面案例,是有人把订单流水全量放 Redis,还要在上面做各种范围查询,结果内存账单爆炸,数据一致性也各种出问题。正确的心态是:Redis 适合高价值的热点数据、需要极低延迟的读操作、需要跨进程共享的状态信息,不适合做全量数据仓库。能想清楚边界,比多背几十条命令重要得多。
2. 五分钟装好一套能用的 Redis
环境不一样,安装方式差别很大。我实际工作中接触过三种最常见的安装场景:Windows 本机调试、Linux 服务器部署、Docker 容器化部署。下面分别说。
2.1 Windows 下最省事的跑法
虽然 Redis 官方其实不原生支持 Windows,但社区一直有移植版本可用。最省事的方式是直接去 Redis 官方下载页或者知名的 Windows 移植项目仓库(比如 tporadowski/redis)找一个稳定版本,下载 zip 包解压就行,不需要安装 MSI。
解压之后目录里有 redis-server.exe 和 redis-cli.exe。命令行直接跑 redis-server.exe,服务就起来了,默认端口 6379;再开一个新窗口跑 redis-cli.exe,输入 ping,回复 PONG 就说明通了。Windows 下调试的时候注意一件事:默认没有任何密码和绑定限制,本机调试无所谓,但如果你把 Windows 防火墙端口开放给了局域网,别人也能访问,所以要么设置密码,要么只允许本机连接。
2.2 Linux 上常规安装
生产环境大部分都在 Linux 上。最常见的做法是直接用包管理器安装,CentOS 系用 yum,Ubuntu/Debian 系用 apt。Ubuntu 上一条命令 apt install redis-server 就能装好,装完用 systemctl status redis 看一眼服务状态。
不过包管理器装的版本可能偏老,如果你需要比较新的版本特性,更推荐官方源码编译安装。流程大致是这样:
编译安装的好处除了版本新,还可以自己指定安装路径和编译参数。坏处是需要装 gcc 和 make 相关工具链,第一次编译可能要几分钟,而且如果机器配置低,编译过程内存不够会报错。我在低配云服务器上遇到过 make 到一半进程被杀的情况,后来加了一个 swap 分区才顺利编完。
2.3 Docker 一键部署 Redis 主从
容器化部署现在基本是生产环境的主流方案。我用 Docker Compose 部署过单机和主从两种模式,这里给一个可以直接抄的主从方案。目录结构大概是这样的:
docker-compose.yml 的核心内容:
主节点的 redis.conf 里,关键配置是开启密码认证:
从节点的配置更简单,除了上面两个,再加一行指向主节点:
启动命令 docker compose up -d 跑起来之后,验证方式很简单:进入主节点容器写一个 key,再进从节点容器查同一个 key,能查到就说明主从复制已经工作正常。
2.4 装完必须改的几个配置
不管用哪种方式装,生产环境里有几个配置我是必然会改的,不然迟早出事。
第一是 bind 和 protected-mode。默认 Redis 只监听 127.0.0.1,如果你需要远程连接,必须改成 0.0.0.0 或者指定内网 IP。同时要把 protected-mode 设置为 no(或者同时设置好密码),否则 Redis 会拒绝远程连接。第二是 requirepass,设置访问密码。第三是 appendonly 持久化开关,如果你的场景能接受少量数据丢失,可以不开;如果 Redis 里存的是不能丢的数据,一定要开 AOF,配合 appendfsync everysec。第四是 maxmemory,给 Redis 设定一个内存上限,防止内存写爆导致机器 OOM。我自己就踩过没设 maxmemory 导致整台云服务器内存被打满的坑,后来老老实实加上,再配好内存淘汰策略。
3. 数据类型就是 Redis 的命根子
Redis 之所以能做那么多事情,就是因为它的数据类型不只是字符串,而是围绕具体场景设计出来的。五种基本类型,每种用对场景都能省很多代码。
3.1 五种常用类型和各自脾气
我用一张表把五种类型的关键特性和典型场景整理出来了,日常使用基本看这张表就够了。
| 类型 | 底层本质 | 适合场景 | 典型命令 |
|---|---|---|---|
| String | 二进制安全字符串 | 缓存对象、计数器、分布式锁 | SET, GET, INCR, SETNX |
| Hash | 字符串映射表 | 存储对象字段,比如用户信息 | HSET, HGET, HGETALL |
| List | 双向链表 | 消息队列、最新列表 | LPUSH, RPUSH, LPOP, LRANGE |
| Set | 无序去重集合 | 标签、点赞去重、共同关注 | SADD, SISMEMBER, SINTER |
| ZSet | 有序去重集合(跳表+哈希) | 排行榜、延迟队列、自动补全 | ZADD, ZRANGE, ZSCORE |
String 是用的最多的,但有一点很多人会忽略:INCR 命令的原子性非常有用。计数器、限流、库存扣减,都能用一条命令搞定,不用担心并发覆盖。
Hash 适合存“对象”。比如缓存用户信息,把 userId 当 key,里面放 name、age、email 这些字段,比序列化成一整串字符串更灵活——你想改其中一个字段,不用整个反序列化再写回去,一条 HSET 就完事。
List 的典型用法是消息队列的简化版。LPUSH 生产,BRPOP 消费,配合阻塞参数可以实现简单的发布订阅队列。注意别用 List 做那种需要随机访问的场景,它本质是链表,索引定位是 O(N) 的。
Set 最有价值的是集合运算。SINTER 可以求两个集合的交集,比如“共同关注的人”一个命令就出来了,不用在应用层写循环判断。ZSet 则是在 Set 的基础上给每个成员加了一个 score,按 score 排序。排行榜是它的招牌场景,还有一个很妙的用法是做自动补全,下面单独说。
3.2 用 ZSet 实现一个自动补全组件
自动补全听起来是一个很“搜索”的功能,实际上用 Redis 的 ZSet 也能做,而且成本很低。思路是这样的:把热门搜索词作为 ZSet 的成员,把搜索次数作为 score。当用户输入前缀的时候,把前缀作为区间查询条件,从 ZSet 中取出 score 最高的前 N 个词,就是补全候选。
命令层面,核心是 ZRANGEBYSCORE 和 ZREVRANGEBYSCORE。比如我们要找以“red”开头的词,技巧是构造一个“red”到“red\xff”的区间,因为 \xff 在字典序上比任意正常字符都大,这个区间就能覆盖所有以 red 开头的字符串。再用 ZREVRANGEBYSCORE 按 score 从高到低取前 5 个,返回的就是最热门的补全结果。
实现的时候要注意,这个方案只适合“候选词数量可控”的场景。如果候选词有几百万条,每次都全量取回来再过滤前缀,性能就崩了。我实际做得比较多的是再加一层前缀索引:把每个候选词的所有前缀都提前存到 Set 里,查询时直接根据前缀拿到词集合,再取交集。这样查询复杂度基本可控,只是写入时会多耗一点内存。
3.3 序列化和反序列化的坑
Java 面试里经常被问 Redis 序列化,实际开发中也确实很容易在序列化上翻车。最常见的坑是默认序列化方式。Spring Data Redis 如果不做任何配置,默认用 JdkSerializationRedisSerializer,结果就是 Redis 里存的 key 变成一串 \xAC\xED\x00\x05t\x00... 的乱码,肉眼根本没法排查,别的语言也没法解析。正确做法是显式指定 key 用 StringRedisSerializer,value 用 JSON 序列化方案。
序列化方案的选择也有讲究。存 JSON 的好处是可读性高、跨语言通用、调试方便;坏处是体积相对大,读写时要多花 CPU 做解析。如果需要极致性能,可以考虑 Protocol Buffers、MessagePack 这类二进制序列化方案,数据量能小很多,但多语言互通会比较麻烦。我自己在内部服务之间传数据偏向 JSON,因为可维护性更高;对外提供高吞吐接口时才会考虑二进制序列化。
4. 可视化工具和常用命令速查
4.1 两款主流可视化客户端,我为什么推荐后者
Redis Desktop Manager(通常简称为 RDM)是最老牌的可视化客户端,功能完整,界面成熟。但它是商业软件,新版本需要付费授权,很多免费的都是老版本。如果你只是偶尔连一下,RDM 的免费旧版完全够用。
现在我个人主力用的是 Another Redis Desktop Manager(很多人叫它 another redis)。它是免费开源的,跨平台支持 Windows、macOS、Linux,界面响应速度很快,功能也一点不少:支持多连接管理、key 的增删改查、命令终端、慢查询日志查看、订阅发布监控。最关键的是它对大 key 的处理做了优化,不会像某些工具一样点一下就把几万条数据全部拉出来把界面卡死。
连接的时候要注意几个细节。第一,生产环境的 Redis 一般都有密码,如果还设置了 TLS 加密,连接时要勾选对应选项。第二,连接信息里的数据库编号 db0 到 db15 要填对,默认是 db0,有些项目数据分布在多个 db 里,你连错库了看不到数据,第一反应是数据丢了,其实是库选错了。第三,连接超时时间不要设太长,我一般设 3 秒,网络有问题可以快速失败,而不是傻等十几秒。
4.2 高频命令速查表
命令不需要全背,但下面这些是日常开发里出现频率最高的,我整理成速查表,建议复制一份放在手边:
| 分类 | 命令 | 作用 |
|---|---|---|
| Key 操作 | DEL key | 删除 key |
| Key 操作 | EXPIRE key seconds | 设置过期时间 |
| Key 操作 | TTL key | 查看剩余过期时间,-1 永不过期,-2 不存在 |
| Key 操作 | EXISTS key | 判断 key 是否存在 |
| String | SET key value [EX seconds] [NX] | 写值,可带过期时间和 NX 条件 |
| String | GET key | 取值 |
| String | INCR / DECR | 原子自增 / 自减 |
| String | SETNX key value | 不存在才设置,分布式锁基础 |
| Hash | HSET / HGET | 设置 / 获取对象字段 |
| List | LPUSH / RPUSH | 左推 / 右推 |
| List | LRANGE key start stop | 取列表区间 |
| Set | SADD / SREM | 添加 / 移除集合成员 |
| ZSet | ZADD key score member | 添加成员并指定分数 |
| ZSet | ZREVRANGE key start stop | 按分数从高到低取排名 |
| 服务器 | INFO | 查看内存、连接数、击穿等指标 |
| 服务器 | DBSIZE | 当前库 key 数量 |
4.3 千万别在生产环境敲的几条命令
有几条命令,我在生产环境是绝对禁止无脑执行的。第一条是 FLUSHALL 和 FLUSHDB,一条命令清空全部数据,没有确认提示,手一抖就是事故现场,我听说过不止一个同行因为脚本写错误清线上数据的案例。第二条是 KEYS *,它在 key 很多的时候会阻塞 Redis 单线程,导致整个服务几秒甚至几十秒不可用,线上排查数据应该改用 SCAN 命令分页迭代。第三条是 MONITOR,它会持续输出所有命令,非常消耗性能,除非万不得已做短期调试,否则不要在生产环境开着。第四条是 DEBUG 开头的命令,那是源码调试用的,普通场景根本不应该碰。
5. 缓存设计三座大山:穿透、击穿、雪崩
缓存用多了之后,你会发现最大的问题不是 Redis 本身,而是缓存和数据库之间那层逻辑设计。高并发场景下,缓存穿透、缓存击穿、缓存雪崩这三个问题几乎必被问到,实际开发中也真的遇到过。
5.1 缓存穿透:数据库被空值打爆
缓存穿透是指查询一个数据库里根本不存在的数据。请求过来先查缓存,缓存没有,再去查数据库,数据库也没有,于是这次查询不会回填缓存。如果攻击者大量构造这种不存在的 key,请求就会全部穿透到数据库,把数据库打挂。
解决思路有三个。第一个是缓存空值:数据库查不到结果时,也往 Redis 里写一个空值,并设置一个较短的过期时间,比如 60 秒。这样后续同样的请求在过期前都不会再打到数据库。第二个是布隆过滤器:用一个位图结构记录所有可能存在的数据 key,请求来的时候先判断 key 是否在位图里,如果明确不存在直接返回,不用查缓存也不用查数据库。布隆过滤器会有极低概率的误判,但不会漏判,误判最多是多几次数据库查询,问题不大。第三个是接口层参数校验,比如对 id 做基本的格式校验,非法参数直接拒绝。这三个方案可以叠加使用,实际效果最好的是空值缓存加参数校验。
5.2 缓存击穿:单点热点过期瞬间
缓存击穿指的是某个热点 key 在过期的瞬间,大量请求同时打到数据库。注意穿透和击穿的区别:穿透是 key 根本不存在,击穿是 key 存在但刚好过期了。工具类产品里的热搜榜、秒杀商品详情,都属于这种热点 key。
最常见的解法是互斥锁。发现缓存过期后,不是立刻查库,而是先获取一个分布式锁,只有拿到锁的线程才去查库,其他线程短暂等待后重试读缓存。这样数据库同一时间只承受一次查询压力,代价是请求可能多等几十毫秒。
我在实际项目里更常用的一个思路是逻辑过期。不设置真正的过期时间,而是在 value 里额外存一个逻辑过期时间戳。读取的时候发现逻辑过期了,先返回旧值给调用方,然后异步去更新缓存。这种方案对接口响应时间几乎没有影响,但缺点是数据一致性会有短暂的延迟窗口,适合那些可以接受短暂旧数据的场景。
5.3 缓存雪崩:大面积 key 同时过期
雪崩和击穿的差别在于规模。如果大量 key 设置了相同的过期时间,比如都设置在整点过期,那么同一时刻它们一起失效,请求就会全部涌向数据库,数据库压力瞬间爆表,这就是缓存雪崩。
解决雪崩的第一个办法是做过期时间随机化。在设置过期时间的时候,基础过期时间上随机加一个偏移量,比如 300 秒再加 0 到 60 秒的随机值,让 key 的过期时间错开。第二个办法是多级缓存,Redis 之上再加一层本地缓存作为兜底,Redis 挂了或大量失效时,本地缓存还能顶一阵子。第三个是集群部署,把 Redis 的压力分散到多台机器,避免单点故障引发连锁反应。我建议这三个办法配合使用,单靠任何一个都很难完全避免极端情况。
6. 分布式锁的正确写法
分布式锁是 Redis 在微服务架构里绕不开的一个用途。很多人觉得分布式锁就是把 SETNX 包装一下就行,实际上这里面坑非常多。
6.1 一个最简单的锁是怎么挂掉的
最简单的分布式锁是这么写的:线程 A 执行 SETNX lock_key unique_value,如果返回 1 表示拿到锁,执行完业务后 DEL lock_key 释放锁。初看没问题,但这里有两个致命缺陷。
第一个缺陷是如果线程 A 拿到锁之后业务逻辑抛异常,或者服务进程直接宕机,DEL 永远都不会执行,锁就一直被持有,其他线程全部阻塞。解决这个问题的标准做法是给锁设置过期时间,SETNX 的时候带上 EX 参数,让锁在超时后自动释放。第二个缺陷是如果一个线程持有的锁过期了,但业务还没执行完,线程 B 此时拿到了锁开始执行,线程 A 执行完后又 DEL 了锁,这把其实是线程 B 的锁,等于线程 A 把线程 B 的锁误删了。解决方法是 DEL 之前先比对 value 是不是自己当初设置的那个值,是才删,这一步要用 Lua 脚本保证原子性。
6.2 基于 SETNX 加 Lua 的工程实现
一个工程上可用的加锁流程可以这样写。加锁这一步:
unique_value 必须保证唯一,通常是 UUID 加线程 ID。加锁成功返回 OK,失败返回 nil。释放锁时用 Lua 脚本保证比对和删除的原子性:
这段脚本做的事是:先取锁的 value,和自己当初设置的 unique_value 比对,相等才删除。用 Lua 执行就避免了检查到删除之间插进来其他命令。
Java 开发我强烈建议直接用 Redisson,它把分布式锁封装得很完善,自带看门狗机制。看门狗的原理是如果加锁后默认不释放,后台线程会每 10 秒自动续期一次,把过期时间延长到 30 秒,防止业务没执行完锁就超时被释放。这个机制解决了我前面说的锁过期导致并发问题,比自己手动管理过期时间省心太多。
6.3 Redlock 说说就好,别太当真
Redis 作者提出过一个叫 Redlock 的算法,试图解决 Redis 主从切换时锁丢失的问题。它的思路是向多个独立的 Redis 节点同时申请锁,超过半数节点成功才算拿到锁。算法本身理论上是自洽的,但工程实践里争议不断,很多分布式系统专家都对它提出过质疑,核心问题是它依赖“所有节点时间同步”和“网络分区”的处理能力,而这在现实环境里很难保证。
我的建议是:绝大多数业务场景不需要 Redlock。Redis 主从架构下锁丢失的概率很低,业务上可以通过唯一业务 ID、幂等校验等方式兜底,而不是把寄托全部压在 Redlock 上。真到了需要用分布式锁来保证资金安全这种级别的强一致场景,更可靠的方案是直接用 ZooKeeper 或 etcd 这类强一致性组件,而不是绕一大圈去完善 Redis 锁。我在实际项目里只在“秒杀库存扣减”这种场景用过 Redlock,后来发现直接用数据库行锁加 Redis 预扣减反而更简单可维护。
7. 持久化、主从复制和内存淘汰,生产配置三件套
7.1 RDB 和 AOF 怎么选
Redis 的持久化有两种方式。RDB 是定期给内存做全量快照,写成二进制文件;AOF 是把每一条写命令追加到日志文件里,重启时回放命令恢复数据。RDB 恢复速度快、文件体积小,但两次快照之间的数据可能丢失;AOF 最多丢 1 秒的数据(配 appendfsync everysec),但文件体积大,恢复速度慢。
我的建议是核心业务数据两个都开,RDB 用于快速恢复,AOF 用于补齐丢失窗口。如果只存缓存、丢了也无所谓,那干脆只开 RDB,甚至不开持久化。有一个容易踩的坑是 AOF 文件随着时间膨胀,重启或者重写不及时会把磁盘占满。解决方法是配置合理的 auto-aof-rewrite-percentage,让 Redis 在 AOF 增长到一定程度时自动重写,压缩掉冗余命令。
7.2 主从复制原理与搭建细节
主从复制是 Redis 高可用的基础。它的基本原理是主节点把写操作同步给从节点,从节点可以分担读流量。同步过程分两个阶段:第一次是全量同步,从节点发送 PSYNC 命令,主节点生成 RDB 快照发给从节点,从节点加载快照;后续是增量同步,主节点把新产生的写命令通过复制积压缓冲区发给从节点。
搭建主从本身不难,docker compose 的方式前面已经给了。实际操作中我要提醒几个细节。第一个,从节点必须配置 masterauth,如果主节点开了密码认证却不配这个,从节点会一直在日志里报同步失败。第二个,要留意复制积压缓冲区的大小,配置项是 repl-backlog-size,默认 1MB,如果主节点写流量很大,这个值太小会导致从节点跟不上,一旦断线重连就只能全量同步,全量同步在数据量大时对主节点性能影响很明显。第三个,读写分离要分场景,从节点数据同步有延迟,读实时性很高的数据时不该走从节点。
7.3 内存淘汰策略,什么时候用哪种
Redis 的内存不是无限的,maxmemory 设置之后,内存满了就要有淘汰策略。常见的有 noeviction(默认,满了直接报错)、allkeys-lru(所有 key 中淘汰最久没用的)、allkeys-random(所有 key 随机淘汰)、volatile-lru(只在设置了过期时间的 key 中淘汰最久没用的)、volatile-ttl(删除剩余过期时间最短的 key)。
我自己的经验是:纯缓存场景用 allkeys-lru,让热点数据自动留存,最省心。需要区分“可淘汰”和“不可淘汰”数据时,用 volatile-lru,只对设置了过期时间的缓存 key 做淘汰,其他 key 不受影响。如果业务能接受丢数据,千万别用 noeviction,否则内存一满全部写操作直接报错,表现就是业务日志突然冒出一堆 OOM command not allowed 错误,排查起来很迷惑。
8. 面试题角度快速复盘
如果这篇文章是面试突击用的,下面这几个问题基本上是必考题目,我把回答思路写出来,你可以顺着这个思路再自己展开。
8.1 Redis 为什么这么快
回答这个问题建议从四个层面拆。第一,纯内存操作,内存访问速度是纳秒级别,而磁盘随机访问是毫秒级别,差了三个数量级。第二,单线程模型避免了锁竞争和线程上下文切换,把所有复杂并发问题简化为顺序执行。第三,IO 多路复用,一个线程用 epoll 同时监听大量 socket,空闲时不占 CPU。第四,高效的数据结构,比如跳表、压缩列表、快速列表,为不同数据规模选择了合适的内存布局。如果有人提到上下文切换时上下文切换消耗被高估,单线程在 IO 密集场景下的优势要结合 Redis 本身内存操作极快的特性来理解,这个补充会更深入。
8.2 缓存和数据库一致性怎么保证
这是一个没有完美答案的问题。最常用的坑最少的方式是 Cache Aside 模式:读的时候先读缓存,缓存没有读数据库,再回写缓存;写的时候先更新数据库,再删除缓存。为什么是删除而不是更新缓存,因为写操作如果更新缓存,可能引入并发时序问题,删除则会让下一次读取时重新加载,逻辑更简单。
删除缓存这里也有个经典坑:如果先删除缓存再更新数据库,更新失败就导致缓存和数据库不一致。我有段时间采用的方案是先更新数据库再删除缓存,但删除缓存也可能失败,所以还会配合一个延迟双删或发送删除消息到消息队列重试。延时双删简单有效,缺点是会短暂出现缓存旧数据,看业务能不能接受。面试官如果继续追问,可以说出“更新数据库成功但删除缓存失败时,用重试机制或订阅 binlog 异步删除”这种主流思路,基本就能过关。
8.3 大 Key 和热 Key 问题
大 Key 指的是某个 key 的 value 特别大,比如一个 Set 里有上百万成员,或者一个 String 有几 MB。大 Key 的危害很明显:读写大 Key 会阻塞 Redis 单线程;删除大 Key 也可能阻塞,用 DEL 删一个大集合会导致这个命令执行时间暴涨。解决思路是用 SCAN 分批删除,或者对大集合做分片,把一个大 key 拆成多个小 key。
热 Key 是某个 key 被超高频率访问,比如爆款商品、明星微博。这个请求全部集中在一台 Redis 上,即使这台机器没挂,网络和 CPU 压力也很大。常见方案是给热 Key 增加副本,把读流量分散到多个从节点;或者做本地缓存,在应用服务器内存里缓存热 Key 的值,Redis 只做兜底。我在线上遇到过一次热点 key 把 Redis 的单分片 CPU 打满的问题,后来加了本地缓存,QPS 降了一大截,问题立刻缓解。
最后说点实际的体会
Redis 这个东西,刚学的时候看命令觉得很简单,真正用起来才发现细节全在各种配置组合和异常场景里。我的建议是,先把这篇文章里的命令和环境折腾一遍,尤其是用 Docker 把主从和持久化跑通,再拿一个真实的业务接口做缓存改造,这样 Redis 从概念到实战的闭环才算真正建立起来。分布式锁和缓存三大坑这类问题,一定要结合自己的项目模拟故障场景去验证,不然真到线上出了事,排查时的压力远比现在学的时候大得多。还有一个小习惯可以分享:给每一个 Redis key 设置过期时间,哪怕你觉得这个数据就是永久存在的,也先设一个很长的过期时间。这个习惯能避免大部分内存泄漏和脏数据问题,是我这几年用得最值的经验。