1. Redis 是什么
Redis 是主要基于内存的键值型数据库,数据以 key → value 的形式保存。
user:1:name → 张三
- key 是数据的名字,value 是保存的内容。
user:1:name中的冒号是命名约定,不是真正的文件夹。- Redis 常用于缓存、验证码、计数、排行榜等。
- Redis 支持持久化,可以把数据保存到磁盘。
- Redis 属于 NoSQL,但不能把所有 NoSQL 都理解成内存数据库。
2. 五种常用数据类型
| 类型 | 特点 | 常见用途 |
|---|---|---|
| String | 保存一个字符串,也能保存数字文本 | 验证码、JSON 缓存、计数 |
| Hash | 一个 key 内保存多个字段和值 | 用户资料 |
| List | 按位置排列,允许重复,可从两端操作 | 列表、简单队列 |
| Set | 成员不重复,不保证排列顺序 | 好友集合、去重 |
| ZSet | 成员不重复,每个成员带分数,按分数排序 | 排行榜 |
3. 常用命令
| 类型 | 命令示例 | 含义 |
|---|---|---|
| String | SET name "张三" |
写入,已有 key 时覆盖 |
| String | GET name |
读取 |
| String | INCR views |
整数值加 1 |
| String | INCRBY views 5 |
整数值加 5 |
| String | SETNX name "李四" |
仅当 key 不存在时写入 |
| Hash | HSET user:1 name "张三" age 20 |
设置字段 |
| Hash | HGET user:1 name |
读取一个字段 |
| Hash | HGETALL user:1 |
读取全部字段和值 |
| Hash | HINCRBY user:1 age 2 |
数字字段加 2 |
| List | LPUSH names "张三" |
从左端放入 |
| List | RPUSH names "李四" |
从右端放入 |
| List | LPOP names / RPOP names |
从左端/右端移除并返回 |
| List | LRANGE names 0 -1 |
查看整个列表 |
| Set | SADD friends "张三" "李四" |
添加成员,自动去重 |
| Set | SCARD friends |
查询成员数量 |
| Set | SMEMBERS friends |
查询所有成员 |
| Set | SINTER A B |
交集:双方都有 |
| Set | SUNION A B |
并集:合并并去重 |
| Set | SDIFF A B |
差集:A 有、B 没有 |
| ZSet | ZADD scores 85 "张三" |
设置成员及其分数 |
| ZSet | ZSCORE scores "张三" |
查询分数 |
| ZSet | ZINCRBY scores 10 "张三" |
分数增加 10 |
| ZSet | ZREVRANGE scores 0 2 WITHSCORES |
查询分数从高到低的前三名及分数 |
特别注意:
ZADD scores 85 "张三" 92 "李四" 78 "王五"
添加的是 3 个成员。参数按"分数、成员"配对,分数是成员的属性。
4. 通用命令与有效期
| 命令 | 含义 |
|---|---|
EXISTS key |
判断 key 是否存在 |
DEL key |
删除整个 key |
TYPE key |
查看 value 的类型 |
EXPIRE key 60 |
设置 60 秒有效期 |
TTL key |
查看剩余有效期 |
TTL 返回值:
- 大于等于
0:剩余秒数,0表示不足一秒。 1:key 存在,但没有有效期。2:key 不存在。
可以在写入时一起设置有效期:
SET code "583921" EX 60
仅在不存在时写入,并设置有效期:
SET code "583921" NX EX 60
普通 SET 覆盖数据时,会清除原有有效期。
5. 你需要特别记住的几个区别
| 容易混淆的地方 | 正确理解 |
|---|---|
LPOP 与 RPOP |
L 是左,R 是右 |
SADD 的返回值 |
本次新增了几个成员 |
SCARD 的返回值 |
集合总共有几个成员 |
| Hash 的 key 与 field | user:1 是 key,name 是内部字段 |
GET 的用途 |
只能读取 String,不能读取所有类型 |
| 同名 key 的类型 | 同一数据库内,一个 key 同时只能对应一种类型 |
SET 覆盖 Hash |
会替换整个 Hash,原字段消失 |
| 类型不匹配 | 通常报 WRONGTYPE |
6. Java 怎样操作 Redis
redis-server:服务端,存储数据、执行命令。redis-cli:命令行客户端。- Jedis、Lettuce:Java 客户端库。
- Spring Data Redis:封装客户端,方便 Spring 项目使用。
StringRedisTemplate 的操作入口:
| 类型 | Java 方法 |
|---|---|
| String | opsForValue() |
| Hash | opsForHash() |
| List | opsForList() |
| Set | opsForSet() |
| ZSet | opsForZSet() |
例如:
stringRedisTemplate.opsForValue().set("name", "张三");
String name = stringRedisTemplate.opsForValue().get("name");
ZSet 的参数顺序要注意:
Redis:ZADD key 分数 成员
Java: add(key, 成员, 分数)
7. 序列化与连接池
序列化解决"Java 对象怎样保存"的问题:
Java 对象 → 序列化 → JSON 字符串 → Redis
Java 对象 ← 反序列化 ← JSON 字符串 ← Redis
String 保存 JSON,通常整体读写;Hash 分字段保存,可以直接修改单个字段。
连接池解决"连接怎样重复使用"的问题:
借连接 → 执行命令 → 归还连接
- 普通 Jedis 实例不应由多个线程同时随意使用。
- 从连接池借出的 Jedis,正常情况下调用
close()是归还连接。 - 使用
StringRedisTemplate的常规操作时,不需要自己手动借还 Jedis 连接。
8. 缓存的基本流程
请求查询用户
↓
先查 Redis
├─ 命中 → 返回
└─ 未命中 → 查询 MySQL
↓
写入 Redis
↓
返回
用户信息更新后,一种常见基础做法是:先成功更新并提交数据库,再删除对应缓存,下次查询重新加载。复杂并发场景还需要进一步处理一致性。
有效期不会让缓存随数据库修改而立即更新。
9. 原子性
"先 GET、程序加一、再 SET"是多个步骤,并发时可能丢失计数。
INCR views
把加一交给 Redis 一次完成,可以避免这种问题。
同样:
SET code "583921" EX 60
能避免分两条命令时,写入成功却没来得及设置有效期。
单个原子操作,不代表多条命令组合起来也具有原子性。
10. 过期、淘汰与持久化
| 机制 | 解决什么问题 |
|---|---|
| 过期 | 数据什么时候失效 |
| 内存淘汰 | 内存达到上限时,哪些数据需要腾出空间 |
| 持久化 | 重启后怎样恢复数据 |
过期数据通过访问时检查和周期性清理来删除,不一定在到期瞬间立即释放内存。
典型内存策略:
noeviction:不淘汰,无法满足的写入报错。allkeys-lru:优先淘汰最近较少使用的数据。allkeys-lfu:优先淘汰使用频率较低的数据。
两种主要持久化方式:
| 方式 | 核心思路 | 可能丢失的数据 |
|---|---|---|
| RDB | 保存某个时刻的数据快照 | 最近快照之后的修改 |
| AOF | 记录写操作 | 尚未可靠落盘的修改 |
持久化不等于历史备份,误删除也可能被持久化。