Redis 学习笔记
hi,随着ai的迅速发展,我认为学习一项技术不能花太长时间,所以我结合最简单的苍穹外卖来对redis进行一个系统性的讲述,让你两天就能够快速的入门
一、Redis 在苍穹外卖中的作用
1. 存储店铺营业状态
SHOP_STATUS 这个 key 存储店铺是"营业中"还是"打烊中",admin 端设置,user 端查询,双方读写同一个 key。
对应文件:
admin/ShopController.java--- 设置 + 查询user/ShopController.java--- 只查询
java
// 设置状态
redisTemplate.opsForValue().set(KEY, status);
// 查询状态
Integer status = (Integer) redisTemplate.opsForValue().get(KEY);
2. 菜品列表缓存(核心用途)
用户按分类查菜品时,先查 Redis 缓存,有就直接返回,没有再查数据库并写入缓存。
对应文件: user/DishController.java
java
String key = "dish_" + categoryId;
// 先查缓存
List<DishVO> list = (List<DishVO>) redisTemplate.opsForValue().get(key);
if (list != null && list.size() > 0) {
return Result.success(list); // 缓存命中,直接返回
}
// 缓存未命中,查数据库
list = dishService.listWithFlavor(dish);
redisTemplate.opsForValue().set(key, list); // 写入缓存
return Result.success(list);
缓存清理 (admin/DishController.java):
后台增删改菜品时,调用 cleanCache() 把相关缓存全部清掉,保证数据一致性。
java
private void cleanCache(String pattern) {
Set keys = redisTemplate.keys(pattern);
redisTemplate.delete(keys);
}
// 新增菜品 → 清理该分类缓存
cleanCache("dish_" + categoryId);
// 删除/修改菜品 → 清理所有菜品缓存
cleanCache("dish_*");
二、什么时候使用 Redis
核心原则:读多写少、数据变化不频繁 的场景。
适用场景
| 场景 | 原因 | 本项目例子 |
|---|---|---|
| 缓存热点数据 | 减少数据库压力,加快响应 | 菜品列表、分类列表、店铺状态 |
| 存共享状态 | 多个接口/服务都要读写同一份数据 | 店铺营业状态(admin 写,user 读) |
| 验证码/临时数据 | 需要自动过期 | 短信验证码(set(key, code, 5, MINUTES)) |
| 计数/限流 | 需要原子操作 | 接口访问频率控制 |
不适用的场景
- 数据量大且查询条件复杂 → 用数据库
- 数据实时性要求极高、不能有缓存延迟 → 直接查库
- 写入频繁、读取很少 → 缓存收益不大
判断口诀
问自己三个问题:
- 这个数据被频繁读取吗? → 是 → 考虑缓存
- 数据变化慢吗? → 是 → 适合缓存
- 允许短暂的不一致吗? → 是 → 可以用 Redis
三个都满足,就放心用 Redis。
三、RedisTemplate 常用 API
java
@Autowired
private RedisTemplate redisTemplate;
// String 类型(项目里用得最多)
redisTemplate.opsForValue().set(key, value); // 写入
redisTemplate.opsForValue().get(key); // 读取
redisTemplate.opsForValue().set(key, value, 30, TimeUnit.MINUTES); // 带过期时间
// 删除
redisTemplate.delete(key); // 删除单个
redisTemplate.delete(keys); // 批量删除(传入 Set)
// 按模式查找 key
redisTemplate.keys("dish_*"); // 返回所有匹配的 key 集合
// 判断 key 是否存在
redisTemplate.hasKey(key);
四、项目 Redis 配置
application-dev.yml:
yaml
redis:
host: localhost
port: 6379
password: 123456
database: 10 # 使用第 10 号库
RedisConfiguration.java 中设置了 key 序列化方式:
java
redisTemplate.setKeySerializer(new StringRedisSerializer());
把 key 的序列化方式从默认的 JDK 序列化改成字符串序列化,Redis 里看到的 key 是可读的 dish_1,而不是乱码字节。
五、Day 1 上午:认识 Redis + String 类型
1. Redis 是什么
Redis 是一个内存数据库,数据存在内存里,读写速度极快。
它存储数据的方式是 键值对(Key-Value),就像一本字典:
vbnet
key → value
"username" → "zhangsan"
"age" → 25
打个比方:
- MySQL = 图书馆找一本书(要去书架上翻,慢)
- Redis = 你桌上的便签纸(低头就看到,快)
2. Redis vs MySQL
| MySQL | Redis | |
|---|---|---|
| 存储位置 | 硬盘 | 内存 |
| 速度 | 毫秒级 | 微秒级 |
| 数据结构 | 表/行/列 | 键值对 |
| 持久化 | 天然持久 | 需要配置 |
3. 为什么需要 Redis
假设一个接口每秒被调用 1000 次,每次都查 MySQL,数据库压力很大。
但这个数据其实很少变化(比如菜品分类),那查一次 MySQL,把结果抄一份到 Redis,下次直接从 Redis 读,数据库就轻松了。
这就是缓存,也是项目里 Redis 最核心的用途。
4. String 类型基本操作
String 是 Redis 最基础、用得最多的数据类型,项目里的菜品缓存和店铺状态都是用它存的。
核心命令:
| 命令 | 作用 | 场景 |
|---|---|---|
SET key value |
写入 | 通用 |
GET key |
读取 | 通用 |
DEL key |
删除 | 通用 |
SETEX key 秒 value |
写入并设过期时间 | 验证码、临时缓存 |
TTL key |
查剩余过期时间 | 检查缓存状态 |
INCR key |
数字+1 | 计数器 |
DECR key |
数字-1 | 计数器 |
基本读写:
vbnet
SET name zhangsan → OK (存入成功)
GET name → zhangsan (读取到值)
DEL username → 1 (删除了1个key)
GET username → (空) (已删除)
带过期时间(验证码场景):
yaml
SETEX phone_code 10 8888 → OK (写入,10秒后自动消失)
GET phone_code → 8888 (10秒内可以读到)
TTL phone_code → 10 (还剩10秒存活)
TTL 的含义:正数 = 还剩多少秒,-1 = 永不过期,-2 = 已经不存在了。
计数器:
sql
SET count 0 → 初始值为 0
INCR count → 1 (自动+1)
INCR count → 2
INCR count → 3
GET count → 3
INCR 是原子操作,多个人同时操作也不会算错。
5. 对应到项目的代码
java
// 存店铺状态 --- 就是 SET
redisTemplate.opsForValue().set("SHOP_STATUS", 1);
// 读店铺状态 --- 就是 GET
Integer status = (Integer) redisTemplate.opsForValue().get("SHOP_STATUS");
// 存菜品缓存 --- 就是 SET
redisTemplate.opsForValue().set("dish_" + categoryId, list);
// 读菜品缓存 --- 就是 GET
List<DishVO> list = (List<DishVO>) redisTemplate.opsForValue().get("dish_" + categoryId);
// 清缓存 --- 就是 DEL
redisTemplate.delete(keys);
opsForValue() 就是操作 String 类型的意思。
6. 上午小结
- Redis 是内存里的字典,key→value,读写极快
- String 是最基础的类型,SET 写、GET 读、DEL 删
- SETEX 可以加过期时间,到时间自动消失,适合验证码和缓存
六、Day 1 下午:List 列表类型 + Hash 哈希类型
1. List 列表类型
List 是一个有序的队列,你可以从左边或右边往里塞东西,也可以从两边往外拿。
想象一根吸管,从左边塞珠子进去,也可以从右边塞,拿的时候也从两头拿。
核心命令:
| 命令 | 作用 |
|---|---|
LPUSH key value |
从左边插入 |
RPUSH key value |
从右边插入 |
LPOP key |
从左边弹出 |
RPOP key |
从右边弹出 |
LRANGE key start stop |
查看范围(0 -1 表示全部) |
LLEN key |
获取列表长度 |
演示:
css
LPUSH mylist a → 从左边插入 a → 列表:[a]
LPUSH mylist b → 从左边插入 b → 列表:[b, a]
RPUSH mylist c → 从右边插入 c → 列表:[b, a, c]
LRANGE 0 -1 → 查看全部 → b a c
LPOP mylist → 从左边拿走 b → 列表:[a, c]
RPOP mylist → 从右边拿走 c → 列表:[a]
LLEN mylist → 长度是 1
使用场景: 消息队列、最近浏览记录、排队系统
2. Hash 哈希类型
Hash 就像一个对象,一个 key 里面可以存多个字段。
对比 String:
- String 是一张便签只写一个值
- Hash 是一张表格可以填多个格子
yaml
String:name → zhangsan (一张便签只存一个信息)
Hash: user:1 → {
name: zhangsan, (一个对象存多个字段)
age: 20,
city: beijing
}
核心命令:
| 命令 | 作用 |
|---|---|
HSET key field value |
设置字段 |
HGET key field |
获取单个字段 |
HGETALL key |
获取所有字段和值 |
HDEL key field |
删除字段 |
HKEYS key |
获取所有字段名 |
HVALS key |
获取所有值 |
演示:
sql
HSET user:1 name zhangsan → 存入姓名,成功
HSET user:1 age 20 → 存入年龄,成功
HSET user:1 city beijing → 存入城市,成功
HGET user:1 name → 只取姓名:zhangsan
HGETALL user:1 → 取出全部:
name → zhangsan
age → 20
city → beijing
HDEL user:1 city → 删除 city 字段
HKEYS user:1 → 只看字段名:name age
HVALS user:1 → 只看值: zhangsan 20
使用场景: 存储对象信息(用户、商品、订单详情),比 String 存 JSON 更方便读写单个字段。
3. 下午小结
| 类型 | 特点 | 适合场景 |
|---|---|---|
| String | 一个 key 对应一个值 | 缓存、计数器、验证码 |
| List | 有序队列,两头操作 | 消息队列、浏览记录 |
| Hash | 一个 key 存多个字段 | 存对象信息(用户、商品) |
七、Day 2 上午:Sorted Set + 重要机制
1. Sorted Set 有序集合
每个元素都有一个分数(score),Redis 会按分数自动排序,最适合做排行榜。
和 Set 的区别:
- Set:无序,存进去是
{a, b, c},没有先后 - Sorted Set:有序,每个元素带分数,
{a:100, b:80, c:90},按分数排名
核心命令:
| 命令 | 作用 |
|---|---|
ZADD key score member |
添加元素(设置分数) |
ZREVRANGE key start stop |
从高到低查看排名(排行榜用这个) |
ZRANGE key start stop |
从低到高查看排名 |
ZSCORE key member |
查看某人分数 |
ZREVRANK key member |
查看某人排名(从大到小,0=第1名) |
ZRANK key member |
查看某人排名(从小到大) |
演示:
ini
ZADD rank 95 zhangsan → zhangsan 得分 95
ZADD rank 80 lisi → lisi 得分 80
ZADD rank 100 wangwu → wangwu 得分 100
ZADD rank 70 zhaoliu → zhaoliu 得分 70
ZREVRANGE rank 0 -1 WITHSCORES → 按分数从高到低:
第1名:wangwu 100分
第2名:zhangsan 95分
第3名:lisi 80分
第4名:zhaoliu 70分
ZSCORE rank zhangsan → 分数:95
ZREVRANK rank zhangsan → 排名第 2(从0开始算,0=第1名)
ZADD rank 120 lisi → 更新 lisi 分数为 120
ZREVRANGE 0 -1 → lisi 马上变成第1名
排名从 0 开始:0=第1名,1=第2名,2=第3名
使用场景: 排行榜、热搜榜、积分排名
2. 过期策略
Redis 可以给任意 key 设置过期时间,时间到了自动删除。
| 命令 | 作用 |
|---|---|
EXPIRE key 秒数 |
给已存在的 key 设置过期时间 |
TTL key |
查看剩余时间(正数=秒数,-1=永不过期,-2=已删除) |
PERSIST key |
取消过期,变成永久 |
为什么缓存要用过期时间?防止缓存永远存在,数据改了但缓存没更新。
3. 持久化
Redis 数据在内存里,服务器重启就没了。持久化就是把内存数据定期保存到硬盘。
| 方式 | 原理 | 特点 |
|---|---|---|
| RDB | 定期拍快照存到硬盘 | 恢复快,可能丢失最后几秒数据 |
| AOF | 把每条写命令都记录下来 | 数据更安全,文件更大、恢复更慢 |
了解即可,实际项目用默认配置就够。
4. 事务
把多条命令打包一起执行,中间不会被其他命令打断:
sql
MULTI → 开始事务
SET a 1 → 命令入队
SET b 2 → 命令入队
EXEC → 一起执行
了解即可,实际项目用得少。
5. 发布订阅(Pub/Sub)
类似广播电台:有人发消息,所有订阅者都能收到。
SUBSCRIBE news → 订阅 news 频道
PUBLISH news hello → 发布消息,所有订阅者收到
使用场景:实时通知、消息推送、解耦系统间通信
6. 上午小结
- Sorted Set 带分数排序,排行榜首选
- 过期策略 用 EXPIRE/TTL 控制缓存存活时间
- 持久化 RDB(快照)和 AOF(日志)两种方式
- 发布订阅 用于广播消息给多个订阅者
八、Day 2 下午:Spring Boot 集成 Redis
1. 项目配置
pom.xml 依赖:
xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
application-dev.yml 配置:
yaml
redis:
host: localhost # Redis 服务器地址
port: 6379 # 端口
password: 123456 # 密码
database: 10 # 用第10号库(0-15共16个库)
RedisConfiguration.java --- 把 key 序列化改成可读的字符串:
java
redisTemplate.setKeySerializer(new StringRedisSerializer());
没有这行,Redis 里存的 key 会是乱码字节,加了之后才是可读的 dish_1。
2. RedisTemplate 五种类型操作
注入方式:
java
@Autowired
private RedisTemplate redisTemplate;
String 类型(项目里用得最多):
| Java 代码 | 等价的 Redis 命令 |
|---|---|
redisTemplate.opsForValue().set(k, v) |
SET k v |
redisTemplate.opsForValue().get(k) |
GET k |
redisTemplate.opsForValue().set(k, v, 30, TimeUnit.MINUTES) |
SETEX k 1800 v |
redisTemplate.delete(k) |
DEL k |
redisTemplate.keys("dish_*") |
KEYS dish_* |
redisTemplate.hasKey(k) |
EXISTS k |
Hash 类型:
| Java 代码 | 等价的 Redis 命令 |
|---|---|
redisTemplate.opsForHash().put(k, field, value) |
HSET k field value |
redisTemplate.opsForHash().get(k, field) |
HGET k field |
redisTemplate.opsForHash().entries(k) |
HGETALL k |
redisTemplate.opsForHash().delete(k, field) |
HDEL k field |
List 类型:
| Java 代码 | 等价的 Redis 命令 |
|---|---|
redisTemplate.opsForList().leftPush(k, v) |
LPUSH k v |
redisTemplate.opsForList().rightPush(k, v) |
RPUSH k v |
redisTemplate.opsForList().leftPop(k) |
LPOP k |
redisTemplate.opsForList().range(k, 0, -1) |
LRANGE k 0 -1 |
Set 类型:
| Java 代码 | 等价的 Redis 命令 |
|---|---|
redisTemplate.opsForSet().add(k, v) |
SADD k v |
redisTemplate.opsForSet().members(k) |
SMEMBERS k |
redisTemplate.opsForSet().isMember(k, v) |
SISMEMBER k v |
Sorted Set 类型:
| Java 代码 | 等价的 Redis 命令 |
|---|---|
redisTemplate.opsForZSet().add(k, member, score) |
ZADD k score member |
redisTemplate.opsForZSet().reverseRange(k, 0, -1) |
ZREVRANGE k 0 -1 |
redisTemplate.opsForZSet().score(k, member) |
ZSCORE k member |
3. 项目实战:菜品缓存流程
读(user/DishController.java):
java
// 第一步:构造 key
String key = "dish_" + categoryId;
// 第二步:先查 Redis 缓存
List<DishVO> list = (List<DishVO>) redisTemplate.opsForValue().get(key);
if (list != null) {
return Result.success(list); // 命中,直接返回,不查数据库
}
// 第三步:缓存没有,查数据库
list = dishService.listWithFlavor(dish);
// 第四步:查出来后存进 Redis
redisTemplate.opsForValue().set(key, list);
return Result.success(list);
写(admin/DishController.java):
java
// 新增/修改/删除菜品时,把缓存清掉
// 这样下次查询会重新从数据库读,保证数据一致
private void cleanCache(String pattern) {
Set keys = redisTemplate.keys(pattern);
redisTemplate.delete(keys);
}
cleanCache("dish_" + categoryId); // 清该分类缓存
cleanCache("dish_*"); // 清全部菜品缓存
4. 加过期时间的写法
java
// 写入缓存,30分钟后自动过期
redisTemplate.opsForValue().set(key, list, 30, TimeUnit.MINUTES);
加上过期时间,即使忘记清理缓存,数据也不会永远过时。
5. 两天学习总结
| 类型 | 命令 | Java 方法 | 使用场景 |
|---|---|---|---|
| String | SET/GET/SETEX | opsForValue() |
缓存、验证码、计数器 |
| List | LPUSH/RPUSH/LRANGE | opsForList() |
消息队列、浏览记录 |
| Hash | HSET/HGET/HGETALL | opsForHash() |
存对象信息 |
| Set | SADD/SMEMBERS | opsForSet() |
标签、去重 |
| Sorted Set | ZADD/ZREVRANGE | opsForZSet() |
排行榜 |
相信你通过这个学习已经能够理解redis的作用以及能够应用了