主题
Redis 入门:从安装到第一个缓存
本文是 Redis 系统学习系列的 L1 入门篇。前置:无。 学完可以配合面试题食用:05-cache-penetration-breakdown-avalanche
Redis 是什么
Redis 是一个内存键值存储,常被当作缓存、计数器、队列用。它快的原因一句话就能说清:数据在内存,读写走 epoll 事件循环,单线程避免锁竞争。详细原理在面试题 08 和 31 展开。
装一个用 Docker 最快:
bash
docker run -d --name redis -p 6379:6379 redis:7-alpine
redis-cli ping
# PONGredis-cli 是自带的命令行客户端,后面所有实验都靠它。
基础和库:别拿 SELECT 当 namespace
Redis 默认有 16 个库(0-15),用 SELECT n 切换。但生产上别用库做隔离——每个库共用同一条持久化策略,一个库内操作阻塞会影响其他库。不同业务用独立实例,或者用 key 前缀区分(user:1000:profile)。
key 命名规范社区约定是 : 分隔,如 user:1000:profile。没有层级结构,纯字符串,冒号只是给人看的。
五大类型速览
Redis 不是简单的 key-value。同样一个 key,value 可以是五种类型之一,每种有对应的业务场景:
| 类型 | 一句话场景 | 命令 |
|---|---|---|
| String | 计数、缓存一段 JSON | SET key value / INCR key |
| Hash | 对象字段增删不改整体 | HSET user:1000:profile name "Alice" age 30 |
| List | 最新 N 条动态、消息队列雏形 | LPUSH timeline:1 {msg} + LTRIM timeline:1 0 99 |
| Set | 标签、去重、共同关注、抽奖 | SADD article:1:tags java / SINTER |
| ZSet | 排行榜、带权重的队列 | ZADD leaderboard 1000 "user:1" / ZREVRANGE |
每类型的内部编码(SDS、Dict、Listpack、Skiplist)在 L2 篇细讲,入门阶段先记住 "什么场景用什么类型" 就行。
第一个缓存:Cache Aside 模式
缓存最常用的模式是 Cache Aside(旁路缓存)。读流程:先查缓存,没命中则查数据库,回写缓存。写流程:更新数据库后删除缓存,而不是更新缓存。
为什么删不是改?因为并发写场景下,更新缓存会遇到"先更新缓存还是先更新数据库"的时序问题,删缓存让下一次读触发重建,天然避免脏数据。
下面是一个 Spring Boot + StringRedisTemplate 的完整示例:
java
@Service
public class UserService {
private final StringRedisTemplate redis;
private final JdbcTemplate db;
public UserService(StringRedisTemplate redis, JdbcTemplate db) {
this.redis = redis;
this.db = db;
}
// 读:先查缓存,没命中查 DB 回写
public Map<String, Object> getUser(Long id) {
String key = "user:" + id;
String cached = redis.opsForValue().get(key);
if (cached != null) {
return JSON.parseObject(cached); // 缓存命中
}
// 缓存未命中 -> 查 DB
Map<String, Object> user = db.queryForMap("SELECT * FROM user WHERE id = ?", id);
// 回写缓存,过期时间 1 小时,防止缓存雪崩
redis.opsForValue().set(key, JSON.toJSONString(user), 1, TimeUnit.HOURS);
return user;
}
// 写:更新 DB -> 删除缓存
public void updateUser(Long id, Map<String, Object> data) {
db.update("UPDATE user SET name = ? WHERE id = ?", data.get("name"), id);
// 一定删缓存,而不是 SET 新值
redis.delete("user:" + id);
}
}注意三点:
- 缓存 TTL 必须设置,否则数据改完永远不更新
- 删缓存之前先确认 DB 写入成功,如果 DB 失败则不能删(保持缓存不变)
- 并发场景下,读未命中查 DB 和回写缓存之间可能被另一个写覆盖,带上
SET NX加锁可进一步稳,入门阶段上面代码够用
过期与内存:别让 Redis 说爆就爆
Redis 会给每个 key 设置 TTL(秒级),到期自动删除。常用命令:
bash
SET key value EX 3600 # 设置 1 小时过期
TTL key # 看剩余秒数,-1 表示没设过期,-2 表示已过期
PERSIST key # 去掉过期
EXPIRE key 300 # 修改过期时间不设 TTL 的 key 会一直占内存。生产上 maxmemory 设实例内存上限(比如 4GB),超限后 Redis 按淘汰策略(LRU/LFU/TTL)逐出。不做配置的话,Redis 会一直涨到 OOM 被系统杀掉。
常见误区与小结
- 误区 1:用 SELECT 库做隔离。不同库共享 fork 和持久化,一个库的大 key 倒腾会影响全实例。每个业务该单独实例或集群。
- 误区 2:缓存更新用 SET 而不是 DELETE。并发写 + 读时容易读到旧值("先更新缓存再写 DB"或"先写 DB 再更新缓存"都有时序问题),DELETE 让下一次读重新拉取最安全。
- 误区 3:所有 key 不设 TTL。Redis 不是关系型数据库,不设 TTL 的缓存就是内存泄漏的源头。
- 误区 4:一个 key 塞超大数据。String 塞几 MB 大 JSON 或百万成员 Hash,读写都阻塞事件循环,用
MEMORY USAGE key可以看实际占用量。
小结:这一篇跑通了 Redis 安装、五大类型的基本认知、Cache Aside 的 Java 写法、和 TTL 的必要性。接下来去 28. 五种数据类型的典型业务用法,每个类型会展开具体业务场景代码。
参考
参考:Redis 官方文档 Data Types / Redis Documentation