这是我学习 Redis 时整理的入门笔记。Redis 可能是被用得最多、也最容易被用错的中间件——十个项目里有九个拿它当缓存,但缓存穿透、缓存击穿、缓存雪崩、缓存和数据库不一致,几乎每个项目都踩过。这篇先把 Redis 的核心概念、数据结构、持久化机制和上手流程过一遍,代码按 Spring Boot 落地。
一、为什么需要 Redis
先想清楚"解决什么问题"。数据库(MySQL)的数据存在磁盘上,读写都要经过磁盘 IO,热点数据的并发一高就扛不住。Redis 把所有数据放在内存里,单线程处理请求,读写都是微秒级,比磁盘快几个数量级。它解决的核心问题:
- 缓存:把"频繁读、不怎么变"的热点数据放进 Redis,扛住大部分读流量,数据库只承受穿透到它那一层的请求。
- 分布式锁:多台服务器抢同一份资源(比如扣库存)时,需要一把"全局的锁",Redis 的
SET NX EX是最轻量的实现。 - 计数器/限流:
INCR原子自增,秒杀库存扣减、接口限流、点赞数都能做。 - 排行榜/实时统计:ZSet 有序集合天然支持按分数排序,排行榜、TopN 就是它的主场。
- 临时数据存储:验证码、Session、接口限流窗口这类"有 TTL 的临时数据",Redis 的过期机制正好对口。
一句话:把"高频、热点、短命"的数据从磁盘搬到内存,用空间换速度。
二、核心数据结构(先记这五种)
Redis 之所以强大,是因为它有五种常用的数据结构,各自解决一类问题:
| 类型 | 底层实现 | 典型场景 |
|---|---|---|
| String(字符串) | 二进制安全,可存任意数据 | 缓存对象(JSON)、计数器(INCR)、分布式锁(SET NX EX) |
| Hash(哈希) | 一个 key 下挂多个 field-value | 存对象属性(用户信息、商品信息),比 String 省内存、可单独改字段 |
| List(列表) | 双向链表 | 消息队列(LPUSH/BRPOP)、最新列表(如时间线) |
| Set(集合) | 无序去重 + 集合运算 | 去重(点赞用户)、交集/并集(共同好友) |
| ZSet(有序集合) | 带分数(score)排序 | 排行榜(按分数排)、延时队列(按时间戳排) |
几个记忆要点:
- String 是万金油:缓存、计数、锁都靠它,用得最多也最基础。
- Hash 适合"对象":改一个字段不用把整个 JSON 读出来重写,
HINCRBY还能对某个属性原子累加。 - ZSet 是"带排序的 Set":每个成员带一个 score,
ZRANGEBYSCORE按分数区间取,排行榜和延时队列都靠这个。
顺带一提:Redis 的 List 也能当消息队列用,但它没有交换机、路由键、死信这些机制,属于"轻量队列"。需要路由、可靠性保障时用真正的 MQ(我整理的《RabbitMQ 快速入门》里有完整对比)。
三、数据持久化:RDB 和 AOF
Redis 是内存数据库,如果不做持久化,进程一挂数据全没。它提供两种持久化方式,可以只开一种也可以同时开:
| 方式 | 原理 | 优点 | 缺点 |
|---|---|---|---|
| RDB(快照) | 定期把内存数据全量生成二进制快照文件 | 文件小、恢复快、对性能影响小 | 两次快照之间的数据会丢 |
| AOF(追加日志) | 把每次写命令追加到日志文件 | 数据几乎不丢(可配每秒刷盘) | 文件大、恢复慢 |
生产环境的常见姿势:两种都开,AOF 保证数据不丢,RDB 用来快速恢复和备份。Redis 重启时优先用 AOF 恢复(因为更完整)。
四、缓存三大问题 + 一致性(重点)
用 Redis 当缓存,绕不开这四个经典问题,面试常问,生产常踩:
1. 缓存穿透:请求的数据缓存和数据库都没有(比如恶意查一个不存在的 ID),每次请求都直接打到数据库,Redis 形同虚设。
- 解法:① 缓存空值(不存在也缓存一个短 TTL 的空结果);② 布隆过滤器(拦截不存在的 key)。
2. 缓存击穿:某个热点 key 突然过期,大量并发同时打到数据库。
- 解法:① 热点数据不设过期时间(后台异步更新);② 互斥锁(只有一个请求去查库,其余等待)。
3. 缓存雪崩:大量 key 在同一时间过期(或 Redis 挂了),请求全打到数据库,数据库被打垮。
- 解法:① 过期时间加随机值,避免同时过期;② 热点数据多级缓存(本地缓存 + Redis);③ Redis 高可用(主从 + 哨兵/集群)。
4. 缓存与数据库一致性:先更新数据库还是先删缓存?
- 主流方案是 Cache Aside(旁路缓存):读时先查缓存,没命中查库并回填;写时先更新数据库,再删除缓存(下次读时重新回填)。
- 为什么不先删缓存再更新库?因为删缓存后、更新库前,可能有请求把旧数据写回缓存,导致缓存长期是脏的。先更新库再删缓存,配合延迟双删(删一次,等几百毫秒再删一次)能最大限度降低脏数据窗口。
五、Docker 安装 + 验证
本地开发用 Docker 一条命令起:
docker run -d --name redis \
-p 6379:6379 \
redis:76379:Redis 默认端口- 连进去验证:
docker exec -it redis redis-cli
# 127.0.0.1:6379> set hello world
# OK
# 127.0.0.1:6379> get hello
# "world"六、Spring Boot 集成实战
1. 依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>2. 配置
spring:
data:
redis:
host: localhost
port: 63793. 基本读写(StringRedisTemplate)
Spring Boot 自动注入 RedisTemplate 和 StringRedisTemplate,后者是 key/value 都是 String 的简化版,日常够用:
@Service
public class CacheService {
@Autowired
private StringRedisTemplate redis;
/** 写缓存,带过期时间(5 分钟) */
public void set(String key, String value) {
redis.opsForValue().set(key, value, Duration.ofMinutes(5));
}
/** 读缓存,没命中返回 null */
public String get(String key) {
return redis.opsForValue().get(key);
}
}4. 缓存注解(@Cacheable)
最简单的玩法是直接加注解,Spring 帮你封装了"先查缓存、未命中查方法、回填缓存":
@Service
public class UserService {
@Cacheable(cacheNames = "user", key = "#id")
public User getUser(Long id) {
// 模拟查数据库
return userMapper.selectById(id);
}
@CacheEvict(cacheNames = "user", key = "#id")
public void updateUser(Long id) {
// 更新数据库后,删除缓存(Cache Aside 的"删缓存"一步)
userMapper.updateById(...);
}
}注意:缓存注解默认用 JDK 序列化(key 有乱码前缀、value 不可读),生产要配置
RedisCacheManager改用 JSON 序列化 + 统一 TTL。具体配置和注解的坑,见我整理的《Spring Cache 序列化与 TTL 配置》。
5. 分布式锁(最简实现)
跨多台服务器抢同一份资源(扣库存、抢优惠券)时,本地锁管不到别的机器,需要一把"全局互斥"的分布式锁。Redis 用 SET NX EX 一条命令原子实现:
// 尝试加锁:key 不存在才设置成功,30 秒自动过期防止死锁
Boolean locked = redis.opsForValue()
.setIfAbsent("lock:stock:1001", "holder-" + requestId, Duration.ofSeconds(30));
if (Boolean.TRUE.equals(locked)) {
try {
// 业务逻辑:扣库存、发优惠券……
} finally {
// 释放锁:必须比对持有者 + Lua 原子删除,防止误删别人的锁
}
}这段只是最简版。它背后是一整条演进线——SETNX(死锁)→ 加 EXPIRE(非原子)→ SET NX EX(原子)→ Lua 原子释放 → Redisson(可重入 + 看门狗 + 高可用),涉及死锁、误删、锁续期、主从丢锁等问题,完整讲解见我整理的《Redis 分布式锁详解》。
七、入门就要知道的几个"坑"
- 别用 KEYS 命令:
KEYS *会全量扫描阻塞 Redis(单线程,一阻塞全站卡住)。需要匹配时用SCAN游标分批。 - 小心大 Key(Big Key):一个 String 几 MB、一个 Hash 几十万字段,读写、删除、迁移都会阻塞。拆分成小 key,或定期扫描治理。
- Redis 单线程 ≠ 随便快:单线程保证原子性和简单,但 O(N) 命令(
KEYS、大范围LRANGE、大 Set 的SMEMBERS)会堵住所有其他命令。 - 分布式锁别自己造轮子:锁过期、误删别人的锁、主从切换丢锁,每个坑都有成熟方案,直接用 Redisson。
- 缓存一致性没有银弹:Cache Aside + 延迟双删能解决大部分问题,但极端一致性要求下,该考虑"先更新库,通过 MQ 异步删缓存"甚至直接不缓存。
小结
- Redis = 内存数据结构存储,五种核心结构:String(缓存/计数/锁)、Hash(对象)、List(轻量队列)、Set(去重/集合运算)、ZSet(排行榜/延时队列)
- 持久化两种:RDB 快照(恢复快、可能丢数据)+ AOF 日志(几乎不丢、文件大),生产常同时开
- 缓存三问:穿透(空值/布隆)、击穿(不设过期/互斥锁)、雪崩(随机过期/多级缓存)
- 一致性主流方案:Cache Aside,先更新库再删缓存(可加延迟双删)
- Spring Boot 集成三步:依赖 + 配置 +
StringRedisTemplate(或缓存注解/分布式锁) - 生产避坑:不用 KEYS、治大 Key、锁用 Redisson
下一步可以看我的《秒杀系统架构分析》里 Redis 怎么做库存预热(setIfAbsent)和 Lua 原子扣减,那是 Redis 在真实高并发场景下的进阶用法。
