Redis 是什么
这一课你将能做到
- 用自己的话说清 Redis 的定位:内存键值数据结构存储,常用于缓存、会话、计数、限流,并指出它和 MySQL 在本站架构里各管什么
- 用 Docker 在本机起一个 Redis,用 redis-cli 完成
PING、SET、GET,第一次亲手摸到数据
- 打开本站
backend 的 application.yml 和 RedisConfig.java,说出 codeforge.redis.enabled 开关打开后会启用哪些能力(Sa-Token 会话、Redis 限流、StringRedisTemplate)
为什么要学
想象一个真实场景:你的 GET /api/tracks 接口,每个访客打开首页都会触发一次 MySQL 全表查询。100 个人同时访问,就是 100 次几乎一模一样的 SQL。数据没变,结果却每次都重新算——这就是典型的热点读问题。
Redis 把「按 key 立刻拿到值」这件事做到了微秒级:数据放内存、结构简单、没有 SQL 解析开销。同样的请求,第一次查 MySQL 并把结果写进 Redis,之后 5 分钟内的请求全部由 Redis 直接返回,数据库压力骤降。
除了缓存,Redis 还解决另外几个 MySQL 不擅长的问题:
- 会话共享:本站生产环境跑多个 Spring Boot 实例,登录态必须存在所有实例都能读到的地方——Redis 是本站的选择(Sa-Token +
sa-token-redis-jackson)
- 计数与限流:
INCR 原子自增,天然适合「每分钟最多 90 次请求」这种配额判断,本站 RedisRequestRateLimiter 就是这么实现的
- 排行榜、去重:ZSet、Set 等结构后面会学
本站的实际分工非常明确:MySQL 是权威数据源(课程、用户、进度都必须落库),Redis 是加速与共享层(缓存只读视图、存会话、做限流)。这门课的全部内容都围绕这个边界展开。
面试里 Redis 也是 Java 后端必考题:为什么快、缓存三兄弟(穿透/击穿/雪崩)、怎么保证一致性,几乎每场面试都会遇到。学完这 12 课,这些问题你都能结合亲手做过的项目回答。
先建立图像
先看本站的整体架构,Redis 的位置一目了然:
浏览器
→ platform(Next.js 前端)
→ backend(Spring Boot :8080)
├─ MySQL(:3306):cf_track 等权威表
│ 课程内容、用户、进度、订单类数据 → 必须落这里
└─ Redis(:6379,可选,codeforge.redis.enabled=true)
├─ Sa-Token 会话(sa-token-redis-jackson)→ 多实例共享登录态
├─ 公开接口限流(rl:* + INCR)→ 防爬虫/防刷
└─ 业务缓存(coderoadmap:*)→ 综合练习课你会亲手加
再看 Redis 内部,它本质上是一个大 Map:
Redis
├─ key: "coderoadmap:tracks:list" → value: '[{"slug":"redis-essentials",...}]'(String)
├─ key: "rl:203.0.113.7" → value: '42'(String 计数,TTL 60s)
├─ key: "satoken:login:token:..." → value: 会话信息(Sa-Token 管理)
└─ key: "user:1001:bookmarks" → value: Set{...}
和 MySQL 对比,差异一目了然:
| 存储介质 | 内存为主(RAM) | 磁盘为主(InnoDB) |
| 访问方式 | 按 key 取,常见 O(1) | SQL 解析、索引、可复杂查询 |
| 数据丢了 | 缓存可从 MySQL 重建;会话用户需重登 | 不可接受,需要备份和事务 |
| 事务 | 简单(MULTI/EXEC,无回滚) | 完整 ACID |
| 本站开关 | codeforge.redis.enabled | 始终需要 |
人话:MySQL 是公司账本,必须准、可审计、永不丢;Redis 是柜台上的一摞便签,谁问得勤就先看便签,便签丢了可以从账本再抄一份。
跟着做
步骤 1:用 Docker 起一个本地 Redis
确保 Docker Desktop 已启动,然后:
docker run -d --name redis-dev -p 6379:6379 redis:7-alpine
参数拆解:-d 后台运行;--name redis-dev 给容器起名;-p 6379:6379 把容器的 6379 端口映射到宿主机;redis:7-alpine 是官方 7.x 精简镜像。
docker ps --filter name=redis-dev
步骤 2:第一次 PING 和 SET/GET
docker exec -it redis-dev redis-cli
PING
SET hello "redis-works"
GET hello
TTL hello
应看到:PONG、OK、"redis-works"、-1。
-1 表示这个 key 存在但没有设过期时间。现在删掉它并退出:
如果你在 Windows 上没有本地 redis-cli,用 docker exec -it redis-dev redis-cli 即可,功能完全一样。第 9 课会专门讲 Docker 运行 Redis 的各种姿势,这里先能连上就行。
步骤 3:读本站的 Redis 开关
打开 backend/src/main/resources/application.yml,注意这一段:
codeforge:
redis:
enabled: false
默认值是 false——本机开发不启动 Redis 也能把 backend 跑起来,登录走单机内存实现,限流走内存实现(见 RateLimitConfig 的 matchIfMissing = true 分支)。这是有意设计的:本地开发少一个外部依赖,新人克隆仓库就能启动。
步骤 4:读 RedisConfig,看开关背后是什么
打开 backend/src/main/java/com/codeforge/config/RedisConfig.java:
@Configuration
@ConditionalOnProperty(name = "codeforge.redis.enabled", havingValue = "true")
public class RedisConfig {
@Bean
public LettuceConnectionFactory redisConnectionFactory(...) { ... } // 1. 连接 Redis 的"水管"
@Bean
public StringRedisTemplate stringRedisTemplate(LettuceConnectionFactory factory) { ... } // 2. 字符串读写工具
@Bean
public RequestRateLimiter redisRequestRateLimiter(StringRedisTemplate stringRedisTemplate) { ... } // 3. Redis 限流
@Bean
public SaTokenDao saTokenDao(LettuceConnectionFactory factory) { ... } // 4. Sa-Token 会话转存 Redis
}
类上的 @ConditionalOnProperty 是关键:只有 codeforge.redis.enabled=true 时,这 4 个 Bean 才会被创建。开关一开,会话和限流就一起切换到 Redis 实现——不是「多了一个模板」,而是整组能力换轨。
步骤 5:对照生产配置
打开 backend/src/main/resources/application-prod.yml:
spring:
data:
redis:
host: ${REDIS_HOST:127.0.0.1}
port: ${REDIS_PORT:6379}
password: ${REDIS_PASSWORD:}
codeforge:
redis:
enabled: true
结论写进笔记:「本机默认关,生产 profile 默认开」。host、密码全部走环境变量注入,代码里不出现明文配置。
步骤 6:给数据分类(练习)
拿一张纸或笔记,把下面这些数据分两列——「必须 MySQL」和「适合 Redis」:
课程标题、用户密码哈希、登录 token、 tracks 列表接口的返回值、学习进度、每 IP 每分钟请求次数、订单金额。
参考答案:课程标题(MySQL,可加缓存副本);密码哈希(只 MySQL,绝不放 Redis);登录 token(Redis);列表返回值(Redis 缓存,MySQL 权威);学习进度(MySQL);限流计数(Redis);订单金额(MySQL)。
判断标准只有两条:丢了能不能接受?需不需要复杂查询/事务? 两个都「不需要」的,才有资格进 Redis。
讲清楚要点
- Redis 是内存数据结构存储,不只是「字符串 KV」:String、Hash、List、Set、ZSet 各有场景,第 4 课展开。
- 因为数据在内存,所以快;因为内存贵,所以不能当主存——只放热点、可重建、临时的数据。
- 因为命令在服务端基本由单线程执行,所以慢命令会拖住所有人——这个特性决定了后面 KEYS 禁用、SCAN 代替等一系列纪律。
- 因为本站用条件装配,所以
enabled=false 时连连接工厂都不创建——本机无 Redis 也能启动;开了却连不上,会在启动或首次访问时报错。
- 因为多实例共享登录态需要中心存储,所以生产把 Sa-Token 会话放进 Redis——任意节点都能校验同一个 token。
- 因为公开接口要防刷,所以限流用
rl: 前缀 + INCR 共享配额——多实例下内存计数会各算各的,Redis 计数是全局的。
- 因为缓存是副本,所以写路径永远先保证 MySQL 成功——「先写库再删缓存」是第 6 课的核心。
- Redis 可选持久化(RDB/AOF),不是「内存存储 = 断电必丢」,第 9 课细讲。
你可能会懵的地方
-
Redis 和 Memcached 有什么区别?
入门记住三点就够:Redis 数据结构更丰富(Memcached 基本只有字符串)、支持持久化、支持发布订阅。Memcached 更纯粹的多线程缓存。本站选 Redis 是因为会话、限流、缓存能用同一个实例解决。
-
「内存存储」是不是断电就全没了?
未开持久化时,进程一停数据就丢。可以开 RDB(定时快照)或 AOF(命令日志)来降低丢失窗口。但要分数据看后果:缓存丢了回源重建即可;会话丢了用户要重新登录——所以第 9 课会讲「会话实例更需要 AOF」。
-
Redis 为什么快?是不是用了多线程?
快的根源:① 数据在内存,没有磁盘 IO;② 命令执行是单线程,没有锁竞争和上下文切换;③ IO 多路复用(epoll)让单线程能同时监听大量连接;④ 数据结构针对场景优化(如跳表、ziplist/listpack)。核心命令执行确实是单线程,Redis 6+ 的多线程只用于网络读写和部分异步任务。
-
Redis 能替代 MySQL 吗?
本课和整个 track 都不会教你这么做。Redis 没有完整 SQL、没有外键、事务不回滚,做不了复杂关联查询。正确姿势是 MySQL 存权威数据 + Redis 放加速副本。
-
为什么 enabled: false 时加了 spring-boot-starter-data-redis 依赖也没连接产生?
依赖只是把类放进 classpath,Spring Boot 会在 classpath 有 Redis 类时自动配置连接工厂——但本站用 @ConditionalOnProperty 主动拦住了自动装配,开关不开、Bean 不建、连接不发生。这解释了为什么「引入依赖」不等于「已连接」。
错法对照
| 错法 | 问题 | 正解 |
|---|
| 把 Redis 当唯一用户数据仓库 | 丢数据、难查询、难事务 | MySQL 权威,Redis 加速 |
开发环境强制 enabled: true 但没起 Redis | 启动失败或登录异常 | 本机保持 false,或先起 Docker Redis |
| 以为加了 starter 依赖就在用 Redis | 忽略条件装配 | 看 codeforge.redis.enabled 与 RedisConfig |
| 会话 key 和业务缓存 key 混用前缀 | 清理时误删登录态 | satoken: / rl: / coderoadmap: 三类前缀分立 |
| 一上来就学集群、哨兵、分布式锁全家桶 | 没有单机模型做地基 | 先完成本 track 12 课单机内容 |
卡点急救
docker run 报 port is already allocated:6379 被占(可能已有本地 Redis 或旧容器)。docker ps 查旧容器 docker rm -f 掉,或换端口 -p 6380:6379(后端配置也要同步改 port)。
- PING 报
NOAUTH Authentication required:你连的 Redis 设了密码。交互模式里先 AUTH 你的密码,或用 redis-cli -a 密码 连接。
Connection refused:容器没起来或端口映射错了。docker ps 确认 0.0.0.0:6379->6379/tcp 在列;不在就 docker logs redis-dev 看报错。
- Windows 找不到 redis-cli 命令:不必装,直接
docker exec -it redis-dev redis-cli 用容器内的客户端。
- backend 开了
enabled: true 却启动报错:八成是 Redis 没起或 host/port 不对。先 PING 通再开开关;本地开发顺手保持 false 最省心。
面试常问
-
Redis 为什么快?
内存操作 + 单线程命令执行(无锁、无上下文切换)+ epoll IO 多路复用 + 为场景优化的高效数据结构。注意别说「因为它多线程」——恰恰相反,核心是单线程 + 多路复用。
-
Redis 和 MySQL 怎么分工?
MySQL 是持久化的权威数据源,负责事务、复杂查询;Redis 是内存加速层,负责缓存热点读、共享会话、计数限流。写路径先落 MySQL,读路径先走 Redis、miss 回源 MySQL。
-
Redis 是单线程的,为什么还能扛高并发?
单线程避免了锁和线程切换开销,而它的瓶颈从来不是 CPU 而是内存和网络;epoll 让一个线程管理成千上万连接,每个命令又是内存级的微秒操作,所以吞吐量依然极高。
-
你们项目里 Redis 具体用在哪儿?
按本站真实情况答:① Sa-Token 分布式会话(sa-token-redis-jackson),多实例共享登录态;② 公开接口限流(INCR + EXPIRE 固定窗口);③ 业务只读接口的 Cache-Aside 缓存(tracks 列表)。同时说明本机开发通过配置开关可整体关闭,生产 profile 开启——体现「依赖可降级」的工程意识。
自测三问
- 用一句话说明:本站什么数据必须进 MySQL,什么数据适合进 Redis?判断标准是什么?
codeforge.redis.enabled=true 之后,RedisConfig 会注册哪 4 个 Bean?分别服务什么能力?
- 为什么说「缓存丢了可以重建,会话丢了用户会被踢下线」?
小结一句话
Redis 是内存键值数据结构存储,本站把它当缓存、会话与限流的共享层,MySQL 永远是权威数据源。
延伸