Redis 大概是后端技术栈里“学了就会用,用了就想吐槽,吐槽完还得接着用”的典型代表。你说它难吧,日常无非是 get/set 那几个命令;你说它简单吧,真到缓存穿透、分布式锁、序列化乱码这些场景,翻车的人一抓一大把。
这份速记不是从零开始的完整教程,而是我自己这几年在项目里折腾 Redis 攒下来的笔记。内容覆盖面比较杂,包括:Windows 和 Docker 环境下的安装部署、五种核心数据类型的选型思路、GUI 连接工具的选择与避坑、分布式锁和序列化两个高频翻车点、缓存穿透/击穿/雪崩的治理套路、日志与慢查询的排查方法,外加一份面试题底层逻辑速记。不管你是在校生准备面试,还是后端开发想把手上的 Redis 用得更稳,这份笔记应该都能帮你省掉一些试错的时间。
需要提前说明的是:文章里的命令和配置我都在 Redis 6.x/7.x 上验证过,如果用的是老版本(比如 3.x/4.x),个别参数可能对不上,务必以你本机的redis-server --version为准。
1. 安装部署这块,先把坑摸清楚
1.1 Windows 安装路线怎么选
很多人在公司用的是 Windows 笔记本,本地调试想装个 Redis,搜“redis下载”就懵了:官方不提供 Windows 安装包,Windows 版只是社区维护的移植分支。所以在看到某个所谓的“redis下载官网”时,先看清楚站点到底是不是官方,再决定装不装。
我在 Windows 上装 Redis 的三种方式,按推荐程度给你排个序:
- 用 WSL 装官方版。
sudo apt update && sudo apt install redis-server就完事了,版本跟 Linux 一致,不会踩 Windows 移植版的坑。缺点是 WSL 2 的网络端口映射偶尔让人头疼,需要确认宿主机能访问到 WSL 里的 6379。 - 直接下载社区维护的 Windows 二进制包,比如 tporadowski/redis。解压后
redis-server.exe就能跑,胜在省事,适合快速验证。注意这类包没有注册 Windows 服务,重启之后不会自动启动。 - 用 Chocolatey 包管理器:
choco install redis-64。装完环境变量自动配好,命令随便敲,适合喜欢包管理器的同学。
这里有个 Windows 特有的坑:很多教程让你双击 exe 启动,结果窗口一关 Redis 就没了。你在本地调试还行,但要是想让它常驻,得注册成服务才行。
1.2 Docker 单机和主从部署
搜“docker安装redis主从”能翻出一大堆教程,但多数只是把两个容器跑起来就算完事,完全没考虑持久化、密码、节点间认证。我这边给一份能直接拿去用的docker-compose.yml,思路很简单:主节点负责写,从节点负责读,两者配置都挂载出来,方便改参数。
version: '3.8' services: redis-master: image: redis:7.0-alpine container_name: redis-master restart: always ports: - "6379:6379" command: ["redis-server", "/usr/local/etc/redis/redis.conf"] volumes: - ./master/redis.conf:/usr/local/etc/redis/redis.conf - ./master/data:/data redis-slave: image: redis:7.0-alpine container_name: redis-slave restart: always ports: - "6380:6379" command: ["redis-server", "/usr/local/etc/redis/redis.conf"] volumes: - ./slave/redis.conf:/usr/local/etc/redis/redis.conf - ./slave/data:/data depends_on: - redis-master从节点的配置文件里,核心就三行:
slaveof redis-master 6379 masterauth yourpassword requirepass yourpassword注意masterauth不能少。主节点设了密码后,从节点同步数据时必须先完成认证,否则日志里会持续刷MASTER aborted replication with an error: NOAUTH Authentication required。这个过程不会立刻报错,但读从库你会发现数据一直不更新,属于那种“看起来没事、其实已经坏了”的隐蔽故障。
官方镜像选哪个 tag,搜“redis镜像”的话我建议直接用redis:7.0-alpine。alpine 版本体积小、依赖少,生产环境省内存,该有的功能一个不少。如果你是给公司建内部基础设施,记得顺便配一个私有镜像仓库,别让公网镜像源成为你部署链路上的单点。
1.3 配置参数:改错了可能直接玩崩
Redis 的默认配置在本地跑没问题,但放到生产环境,有几个参数是必须改的。我把最重要的汇总成一张表:
| 参数 | 默认值 | 建议 | 备注 |
|---|---|---|---|
maxmemory | 0(不限制) | 按机器内存的 70% 设置 | 超过后触发淘汰策略 |
maxmemory-policy | noeviction | 缓存场景用allkeys-lru | noeviction会导致写命令直接报错 |
appendonly | no | yes | 开启 AOF 持久化,防重启丢数据 |
save | 多条 | 按业务保留 | RDB 快照的触发条件 |
requirepass | 空 | 生产必设 | 别用太简单的密码 |
bind | 127.0.0.1 | 按需改 | 改成0.0.0.0时一定配合密码 |
最容易被忽略的是maxmemory-policy。我见过不止一次这样的情况:团队把 Redis 当缓存用,但没配淘汰策略,流量一上来内存就被打满,然后所有写命令返回OOM command not allowed when used memory > 'maxmemory',接口瞬间全挂。最简单的做法就是理解清楚:allkeys-lru适合纯缓存,不设过期时间的 key 也会被淘汰;volatile-lru只淘汰设置了过期时间的 key,适合部分数据需要留存的场景。
2. 数据类型:别背命令,先想场景
2.1 五种基础类型速查表
Redis 的数据类型面试必考、实战必用。这里我按“结构特点、常用命令、典型场景”三个维度做个速查:
- String:最简单的 KV,value 可以是字符串、数字或二进制。常用命令
SET、GET、INCR、DECR、SETNX。典型场景:计数器、分布式 ID、缓存热点数据。 - Hash:field-value 的散列结构,适合存对象。常用命令
HSET、HGET、HGETALL。典型场景:用户资料、商品详情、配置项。相比把整个对象序列化成 String,Hash 可以单独操作某个字段,节省带宽。 - List:双向链表,支持头尾插入弹出。常用命令
LPUSH、RPUSH、LPOP、BRPOP。典型场景:消息队列(简单的任务排队)、最近列表。BRPOP是阻塞读取,适合做简单的消费者模式。 - Set:无序去重集合。常用命令
SADD、SPOP、SMEMBERS、SISMEMBER。典型场景:标签、好友关系、抽奖去重。SISMEMBER判断成员是否存在是 O(1)。 - ZSet(有序集合):每个成员带一个 score,按 score 排序。常用命令
ZADD、ZRANGE、ZREVRANGE、ZSCORE。典型场景:排行榜、延迟队列、限流滑动窗口。底层是跳表 + 哈希表,插入和查询都是 O(logN)。
2.2 三个真实场景教你怎么选
光看表还是容易迷糊,我用三个工作中真实遇到过的场景来说明。
第一个是排行榜。需求是展示用户积分前 100,支持实时更新排名。用 ZSet 是天然适配:ZADD rank 100 user_1写入或更新积分,ZREVRANGE rank 0 99 WITHSCORES查前 100 名,ZREVRANK rank user_1查某个用户的排名。三条命令解决,全部 O(logN)。如果不用 ZSet,你要么在数据库里建排行榜表,要么用 Redis 的 List 每次全量排序,都很别扭。
第二个是“最近浏览记录”。很多人第一反应是 List,LPUSH history user_1 product_2,然后LTRIM history user_1 0 99截断。但你要是想判断“这个商品用户是不是已经浏览过”,List 只能遍历,很尴尬。更好的做法是 Set 和 List 配合:Set 负责去重,List 负责顺序。写入时先SADD,返回 1 说明是第一次浏览,才LPUSH压入列表。
第三个是防重的限流场景。比如限制某个用户每分钟最多请求 10 次。简单粗暴的写法是用一个过期 key 计数:SET rate:user_1001 1 EX 60 NX,后续INCR并判断是否超过阈值。更优雅的方案是用 ZSet 做滑动窗口,score 存时间戳,每次请求ZREMRANGEBYSCORE清理窗口外的记录,再ZCARD统计窗口内请求数。窗口限流比固定窗口限流更平滑,不会出现“最后 1 秒猛冲”的毛刺。
顺带提一下 key 的命名。很多项目里 key 命名极其随意,user_1001、u1001、user::1001混着来,后期排查问题就是灾难。建议统一成业务名:实体名:ID的格式,比如user:info:1001、cart:list:1001。冒号分隔写起来清晰,用SCAN批量匹配时也方便。
3. 连接工具与日常操作
3.1 选 RDM 还是 Another RDM
搜“redis desktop manager”或“redis连接工具”时,市面上主流的 GUI 客户端其实就那几个。我个人的建议是:如果你在 Redis 7 上工作,直接用 Another Redis Desktop Manager(ARDM);如果你还在用老版本 Redis,RDM 免费版也能满足日常需求。
RDM(Redis Desktop Manager)是老牌工具,界面简洁,连接、看 key、执行命令都很快,但免费版在 Redis 7 的数据类型展示上有些兼容问题。ARDM 是基于 Electron 的开源客户端,界面现代,支持暗色主题和树形 key 展示,团队排查问题的时候比纯命令行直观很多。它的内存占用稍微高一点,但这在 GUI 工具里属于正常水平,不影响体验。
使用 GUI 工具时有个经常被忽略的细节:数据库编号。Redis 默认有 16 个逻辑库(db0 到 db15),很多项目只用 db0,但有些项目会切到 db1 存缓存、db2 存临时数据。你如果连上工具后发现看不到 key,先去右上角切换一下数据库编号,十有八九能解决。
另一个安全细节:连接生产环境时,务必用密码连接,并且别把 6379 端口暴露到公网。有些人图方便,把 Redis 端口映射到云服务器公网,没设密码或密码很弱,结果被扫描器爆破,数据被删、被勒索的新闻你肯定看过。生产环境的 Redis,宁可访问麻烦一点,也不要裸奔在公网上。
3.2 命令行速查与生产禁用命令
GUI 工具再方便,命令行永远是最后那道保险。这里整理一份我平时最常用的命令备忘:
# 连接与认证 redis-cli -h 127.0.0.1 -p 6379 -a yourpassword AUTH yourpassword # Key 操作 SET key value [EX seconds] [NX] GET key EXISTS key DEL key TTL key EXPIRE key 60 # 各类型常用命令 HSET user:1001 name "张三" age 30 HGETALL user:1001 LPUSH queue task1 BRPOP queue 0 SADD tags:1001 "vip" SISMEMBER tags:1001 "vip" ZADD rank 100 user_1 ZREVRANGE rank 0 9 WITHSCORES # 诊断命令 INFO memory INFO clients SLOWLOG GET 100 DBSIZE CLIENT LIST这里必须多说一句:线上环境禁用KEYS *。Redis 是单线程模型,KEYS *会遍历整个 key 空间,数据量大时直接阻塞所有请求。需要批量遍历时用SCAN 0 MATCH user:info:* COUNT 100,每次只返回少量 key,不会卡主线程。这个坑我见得太多,很多“Redis 突然卡死”的事故,最后查出来都是有人手动敲了KEYS。
4. 分布式锁与序列化:两个高频翻车点
4.1 分布式锁:正确实现和容易“看着对”的写法
搜“redis分布式锁”的人,不是面试就是踩坑。我先把最常见的错误写法放在这里,你看看眼不眼熟:
SETNX lock_key 1 # 执行业务 DEL lock_key这段代码至少有四个问题:业务抛异常时锁永远不会释放;进程崩溃时锁也会变成死锁;锁超时但业务没执行完,导致锁提前失效、两个线程同时进临界区;解锁时没有校验持有者身份,可能误删别人的锁。四舍五入,这就是个雷区。
行业里目前比较稳妥的落地方式有两个方向。方向一:用 Redisson 的RLock,它自带看门狗自动续期,Java 生态直接引入依赖就不用管续期问题。方向二:自己用 Lua 脚本保证加锁/解锁的原子性。
-- 加锁:SET 命令带 NX + PX -- key: 锁名, value: 唯一标识, ttl: 过期时间(ms) if redis.call('set', KEYS[1], ARGV[1], 'NX', 'PX', ARGV[2]) then return 1 else return 0 end -- 解锁:先比对唯一标识,再删除 if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end为什么 value 里要存唯一标识?因为如果不校验,线程 A 的锁过期后被线程 B 抢到,A 执行完直接DEL,把 B 的锁给删了,临界区就废了。用 UUID 做 value,解锁前比对,就能避免这种情况。这个细节在面试里也是高频考点,回答“从 Redisson 源码讲起”比只说“加锁解锁”要加分不少。
4.2 序列化乱码:从现象到根源再到解法
搜“redis序列化”的人,绝大多数症状都一样:用 Spring Boot 往 Redis 里存对象,重启后读出来一串以\xAC\xED\x00\x05开头的乱码;或者存进去的时候是对象,读出来强转直接抛异常。
这个问题的根源在于Spring Data Redis 默认用 JDK 序列化。JDK 序列化会把对象转成二进制字节流,体积大、不可读,而且要求类实现Serializable接口。解决办法是统一改用 JSON 序列化器。下面是一份我在项目里验证过的配置:
@Configuration public class RedisConfig { @Bean public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) { RedisTemplate<String, Object> template = new RedisTemplate<>(); template.setConnectionFactory(factory); StringRedisSerializer stringSerializer = new StringRedisSerializer(); template.setKeySerializer(stringSerializer); template.setHashKeySerializer(stringSerializer); GenericJackson2JsonRedisSerializer jsonSerializer = new GenericJackson2JsonRedisSerializer(); template.setValueSerializer(jsonSerializer); template.setHashValueSerializer(jsonSerializer); template.afterPropertiesSet(); return template; } }注意两个细节。第一:key 用 String 序列化,value 用 JSON 序列化,不要一刀切。第二:用GenericJackson2JsonRedisSerializer而不是普通 Jackson 序列化器,因为前者会在 JSON 里自动带上@class类型信息,反序列化时才能还原成正确的类。
还有一个很容易跟序列化混淆的问题:泛型丢失。很多人在 Redis 里存了一个List<String>,读出来强转成List<String>时报类型转换异常。原因不是序列化器问题,而是反序列化时类型信息被擦除了。解决办法是读的时候用ObjectMapper的TypeReference来指定具体泛型类型,或者直接存 JSON 字符串,业务层自己解析。
5. 缓存治理与日志监控
5.1 缓存穿透、击穿、雪崩:应对套路一次讲清
这三个词是面试高频,也是线上事故高发。我用自己的话说一遍,顺便带做法。
缓存穿透:查询一个根本不存在的数据,每次请求都会打到数据库。攻击者完全可以构造大量不存在的 ID 来拖垮你的数据库。应对手段有三种:接口层做参数校验(非法 ID 直接拒绝);缓存空值(把不存在的 key 也缓存 60 秒左右);用布隆过滤器挡在前面。我这边推荐“参数校验 + 缓存空值”的组合,性价比最高。布隆过滤器虽然好用,但要考虑误判率和维护成本,小项目不一定划算。
缓存击穿:某个热点 key 过期的一瞬间,大量请求同时落到数据库。解法是互斥锁:缓存没命中时先抢锁,抢到锁的线程查库回填缓存,其他线程等待锁释放后再查缓存。注意这里的锁要用上一条说的分布式锁,别自己SETNX+DEL糊弄过去。
缓存雪崩:大量 key 在同一个时间点集中过期,数据库瞬间被打爆。解法是让过期时间分散开,比如EXPIRE key (base + random*300);另一个思路是热点数据永不过期,由后台任务提前更新缓存。
说个我踩过的真实例子:曾经有个列表接口,业务方把所有 key 的过期时间统一设成了整点后的第 30 分钟,导致每天 10:30、11:30、12:30 这种时刻数据库压力都会有一个尖峰。后来改成“基础过期时间 + 随机偏移量”,曲线就平稳了。这种问题排查起来非常难,因为你不是每次都能注意到数据库压力尖峰和缓存过期时间的关系。
5.2 日志与慢查询:排查问题从哪下手
搜“redis日志”的,基本都是在线上遇到状况了。Redis 的日志体系分两块:运行日志和慢查询日志。
运行日志用logfile指定路径,Docker 部署时建议挂载到宿主机:
logfile "/data/redis.log" loglevel noticeloglevel有四档:debug、verbose、notice、warning。生产环境用 notice 就够,debug 在流量上来的时候日志能刷到磁盘直接被打满。
慢查询日志配置很简单:
slowlog-log-slower-than 10000 # 单位微秒,这里表示 10ms slowlog-max-len 128查看方式:
SLOWLOG GET 50如果慢查询里频繁出现某个命令,基本就是大 key 或者 O(N) 操作。最常见的有KEYS *、SMEMBERS、大数据量的HGETALL。解决方向是把大 key 拆分,或者把一次取大量数据的逻辑改成多次增量读取。
还有一个排查利器:MONITOR命令能实时打印 Redis 收到的所有命令,适合定位“到底是谁在连接 Redis、发了什么命令”。但生产环境慎用,因为在高流量下它会把大量的命令输出到客户端,反而拖垮 Redis。我一般只在测试环境或流量很低的时候用。
6. 常见问题排查速查表
6.1 连接类问题快查
连接问题是最常见的入门问题,直接上表:
| 现象 | 原因 | 排查步骤 |
|---|---|---|
Could not connect to Redis at 127.0.0.1:6379 | Redis 没启动 | 确认进程是否存在,redis-server启动 |
| 本机能连,远程连不上 | bind配置限制 | 检查redis.conf的 bind 项和防火墙 |
连接报NOAUTH | 缺少密码认证 | 用-a参数或AUTH命令 |
连接后立刻断开,日志提示max number of clients reached | 连接数超过maxclients | INFO clients查看连接数,优化连接池,必要时调大maxclients |
| 只有 Win 本机连不上 Docker Redis | 端口映射未开 | docker ps看端口绑定,检查 Windows 防火墙 |
| 客户端工具连不上,命令行能连 | 工具的 IP/端口/密码配置错误 | 检查连接配置,确认是否选择了正确的数据库编号 |
6.2 内存与性能问题快查
这类问题一出现就是线上事故,我把经验浓缩成排查路径。
内存问题:先INFO memory看used_memory和maxmemory。如果内存持续上涨,优先查 key 是否没设过期时间。用redis-cli --bigkeys能扫描大 key,但注意这命令本身也是遍历式扫描,挑业务低峰期跑。删除大 key 用UNLINK而不是DEL,因为UNLINK是异步删除,不会阻塞主线程。
CPU 飙升:Redis 单线程模型下,CPU 高基本就是 O(N) 操作太多。用redis-cli --stat看实时请求量,用SLOWLOG定位慢命令,重点排查KEYS、SMEMBERS、HGETALL这些命令。
命令超时:如果慢查询日志为空但客户端确实超时,检查网络往返时间(用redis-cli --latency)和客户端连接池配置,别急着甩锅给 Redis。
这里有个我每次写文档都会强调的小技巧:给 Redis 做一个最小化的监控。不需要一上来就上 Prometheus + Grafana 那套全家桶,写个每分钟执行的脚本,把INFO memory、INFO clients、SLOWLOG GET的关键字段打到日志或者群机器人,就已经能提前发现 80% 的问题了。
7. 面试题背后的底层逻辑
7.1 高频问题与回答思路
“redis面试题”能搜出一堆面经,但很多都在背答案。我挑几个高频题,讲清楚考官到底在考什么。
- Redis 为什么快?考点:内存操作 + 单线程避免上下文切换 + IO 多路复用。可以额外提一句,单线程也带来约束,一个慢命令会阻塞所有请求,所以
KEYS要禁用。这样比单纯背“快因为内存”要立体。 - RDB 和 AOF 选哪个?考点:两种持久化的权衡。RDB 是快照型,恢复快但可能丢最后一次快照后的数据;AOF 是追加型,根据
fsync策略最多丢几百毫秒数据,但文件大、恢复慢。生产上建议 RDB + AOF 都开,AOF 用于尽量减少丢失,RDB 用于快速恢复。 - 缓存和数据库一致性怎么保证?考点:无法做到强一致,只能追求最终一致。我用的方案是“先更新数据库,再删缓存”,删缓存失败的兜底用消息队列重试。这里如果能把“为什么是删缓存而不是更新缓存”讲清楚,面试官会觉得你真懂。
- Redis Cluster 为什么是 16384 个槽位?考点:CRC16 算法对 key 计算后取模得到槽位,然后槽位分布到节点。回答时能补充“哈希槽的设计是为了数据均匀分布和扩缩容时的小范围迁移”就到位了。
7.2 几个容易加分的深入点
面试想拿高分,光会背基础题不够,还要能体现深度。我整理几个加分项。
- Redis 的 IO 多路复用到底是怎么工作的?可以讲讲 epoll 和 select 的区别,Redis 的事件循环,文件事件和时间事件的调度。能讲到这个层次,说明你不是只会用工具。
- 分布式锁的 RedLock 到底好不好用?很多大厂其实没用 RedLock,因为它在极端网络分区下并不能保证绝对安全。如果你能说出这个观点,并给出“业务幂等 + 简单锁”的兜底方案,会让面试官眼前一亮。
- Redis 的淘汰策略和过期策略的区别?过期策略是被动过期(访问时检查)+ 定期抽样删除;淘汰策略是内存满了以后主动删。很多人会把两者混为一谈,能分开讲就是加分项。
- 大 key / 热 key 的治理方案?大 key 的问题在于阻塞和网络开销,解决方案是拆分、压缩、异步删除;热 key 的问题是单点压力,解决方案是本地缓存、读写分离、多副本。这些在你的简历项目里最好都真实做过,面试时才能讲出细节。
回想这些年和 Redis 打交道的经历,我最想分享的不是某个具体命令,而是一种处理问题的思路:先看数据是怎么流动的,再看每个环节可能挂在哪,最后再决定用什么命令去解决。安装、数据类型、GUI 工具是“术”,分布式锁、序列化、缓存一致性是“道”。这份速记是我自己踩坑换来的总结,你拿去用的时候,也建议每一条都亲手验证一遍。后面的路还长,Redis 的生态还在不断演进,希望这份笔记能陪你走一段,省下一些本来可以避免的夜宵和凌晨三点。