# 一、概述
30 秒速记
Redis= 内存键值数据库,快在内存访问 + 高效数据结构 + 单线程执行命令没有锁竞争- 键是字符串,值有五种基础类型:
STRING/LIST/SET/HASH/ZSET(后来又有Stream、Bitmap、HyperLogLog等) - 持久化防重启丢数据(
RDB快照 /AOF日志),复制扩展读,分片扩展写,三者各管各的 - 单条命令原子,多条命令组合不原子:先
GET再SET照样会并发出错 - 快不代表所有命令都快:
KEYS *、大key删除、HGETALL大哈希都会卡住整个实例
Redis 是一个把数据放在内存里的键值数据库,最大的特点就是快,而且值不只是字符串,还有列表、集合、哈希、有序集合这些现成的数据结构。 快主要因为三点:数据在内存,常用操作是 O(1) 或 O(log N),命令由一个线程顺序执行,没有锁开销(Redis 6 加的多线程只用在网络读写上)。前端最常接触的用法是缓存接口数据、存登录会话、做计数和排行榜。它能把数据持久化到磁盘、用主从复制扩展读、用分片扩展写。踩坑最多的是单线程这一点:一条慢命令会让所有客户端一起等。
Redis 是速度非常快的非关系型(NoSQL)内存键值数据库,可以存储键和五种不同类型的值之间的映射。
键的类型只能为字符串,值支持五种数据类型:字符串、列表、集合、散列表、有序集合。
Redis 支持很多特性,例如将内存中的数据持久化到硬盘中,使用复制来扩展读性能,使用分片来扩展写性能。
原理拆解: 一次访问通常经历客户端编码命令、网络传输、服务端查找键、执行数据结构操作、返回结果几个环节。多数普通命令以单条命令为原子执行单位,因此并发客户端不会观察到某条命令执行到一半的状态;但由多条命令组成的“先读后写”流程仍可能发生竞争,需要使用原子命令、事务或脚本合并操作。Redis 的低延迟来自内存访问和高效数据结构,并不意味着所有命令都是常数复杂度,遍历大集合、删除超大键或返回大量数据仍可能拉高延迟。
最小验证: 输入一个计数键、散列表和有序集合,验证原子自增、字段级更新、按分值排序及过期时间。以下命令要求本机已有正在运行的 Redis,可直接交给 redis-cli 执行:
redis-cli <<'EOF'
DEL demo:count demo:user:1 demo:rank
SET demo:count 0
INCR demo:count
INCRBY demo:count 4
HSET demo:user:1 name Alice level 3
HINCRBY demo:user:1 level 1
ZADD demo:rank 80 Alice 95 Bob 88 Carol
GET demo:count
HGETALL demo:user:1
ZREVRANGE demo:rank 0 1 WITHSCORES
EXPIRE demo:count 60
TTL demo:count
EOF
INCR 与 INCRBY 直接在服务端完成读改写,计数结果应为 5;HINCRBY 后 level 应为 4;排行榜前两名应为 Bob、Carol;最后的 TTL 通常接近 60。若键原先保存的不是可解析整数,增量命令会报类型或整数错误,而不会静默覆盖原值。
边界与排查: 过期时间只表达缓存生命周期,不能替代持久化;内存达到上限后的行为还取决于 maxmemory 与淘汰策略。排查性能时应同时观察慢命令、键和值大小、客户端连接数、网络往返和热点键分布。批量请求可用流水线减少往返,但不会降低命令本身的计算量;分片也只能分散不同键,单个高频键仍需通过拆键、本地缓存或限流治理。验证可靠性时还应分别模拟进程退出、主从延迟和节点故障,因为“命令已返回”与“数据已持久化或已复制”并非天然等价。
面试官追问
追问 1库存扣减写成先 GET、算完再 SET,压测时超卖了,单条命令不是原子的吗?
单条是原子的,两条之间不是。两个请求可能都读到 1,都算出 0 写回去。改成一条原子命令:
需要判断加更新的复杂逻辑就用 Lua 脚本,整个脚本执行期间不会被其他命令插队。
追问 2用 pipeline 批量发 20 条命令,能降低每条命令的执行开销吗?
不能,它省的是网络往返,20 次 RTT 变成 1 次,每条命令在服务端该花多少时间还是多少。pipeline 也不保证原子,中间可能插进别的客户端的命令。
追问 3单节点写入扛不住了,加从库还是做分片?
做分片。从库只能分担读,写还是全打在主库上。Redis Cluster 把 key 按 CRC16 映射到 16384 个槽,分散到多个节点;但如果压力集中在一个热点 key 上,分片也救不了,得拆 key 或者加本地缓存。
追问 4接口延迟突然升高,Redis 在内存里应该不是它的问题吧?
不能这么排除。先看 SLOWLOG GET,常见元凶是 KEYS *、一次性取上万成员的集合、DEL 一个几百 MB 的大 key。单线程模型下一条慢命令会让后面所有请求排队。
追问 5SET 返回了 OK,进程立刻崩了,数据一定还在吗?
不一定。OK 只说明写进了内存。RDB 是隔一段时间拍快照,会丢最后一段;AOF 默认 appendfsync everysec,最多丢约 1 秒;主从复制是异步的,主库挂了从库可能还没收到。
# 二、数据类型
30 秒速记
- 选类型看要做什么操作,不看「功能多不多」
STRING:缓存JSON、计数器(INCR)、分布式锁(SET NX EX)LIST:按顺序进出,消息队列、最新动态;SET:去重、判断存在、交并差、随机抽取HASH:对象按字段读写,比如用户资料只改昵称;ZSET:带分数排序,排行榜、延时队列- 同一个
key只能是一种类型,用错命令会报WRONGTYPE
Redis 五种基础类型各管一类场景:STRING 存单个值,LIST 管顺序,SET 管去重,HASH 管对象字段,ZSET 管排名。 选型我一般反过来想,先问业务要做什么操作:要按写入顺序消费就是 LIST;要判断用户有没有点过赞、两个活动重合了哪些人就是 SET;用户资料经常只改一个字段就用 HASH,比整个 JSON 序列化成 STRING 再覆盖省事,也不怕并发时互相覆盖;积分排行榜取前十就是 ZSET。别因为 ZSET 能力最多就什么都用它,内存和复杂度都更高。
| 数据类型 | 可以存储的值 | 操作 |
|---|---|---|
| STRING | 字符串、整数或者浮点数 | 对整个字符串或者字符串的其中一部分执行操作 对整数和浮点数执行自增或者自减操作 |
| LIST | 列表 | 从两端压入或者弹出元素 对单个或者多个元素进行修剪, 只保留一个范围内的元素 |
| SET | 无序集合 | 添加、获取、移除单个元素 检查一个元素是否存在于集合中 计算交集、并集、差集 从集合里面随机获取元素 |
| HASH | 包含键值对的无序散列表 | 添加、获取、移除单个键值对 获取所有键值对 检查某个键是否存在 |
| ZSET | 有序集合 | 添加、获取、删除元素 根据分值范围或者成员来获取元素 计算一个键的排名 |
面试官追问
追问 1积分排行榜,有人说用 SET 存用户,因为不会重复,可以吗?
不行,SET 是无序的,没法按积分排。用 ZSET:ZINCRBY rank 10 user:1 加分,ZRANGE rank 0 9 REV WITHSCORES 取前十(Redis 6.2 之前用 ZREVRANGE)。分数相同时按成员名字典序排。
追问 2用户资料每次只改一个字段,却整个对象 JSON.stringify 后 SET 覆盖,有什么问题?
两个接口同时改不同字段,后写的会把先写的覆盖掉,每次还要传整个对象。改成 HASH,HSET user:1 nickname Tom 只动一个字段。如果对象总是整体读写,STRING 存 JSON 反而更简单。
追问 3消息队列要求同一消息只保留一次,继续用 LIST 会怎样?
LIST 不去重,同一条消息推两次就有两份。要顺序加去重,可以用 ZSET 把时间戳当分数,同一成员再写只会更新分数;或者 LIST 加一个 SET 记录已入队的 ID,两步操作要放进 Lua 脚本里保证原子。
追问 4随机抽奖加计算两个活动的重合用户,用什么?
SET。SRANDMEMBER 随机抽不删除,SPOP 抽完删除,SINTER 算交集。不需要分数排序就没必要上 ZSET。
追问 5某个 key 执行 HGET 报 WRONGTYPE,怎么回事?
这个 key 实际存的不是 HASH,多半是另一处代码用 SET 把它覆盖成了字符串。TYPE key 先确认实际类型,再找哪条写入路径改了它。key 命名带上业务前缀,比如 user:profile:1,能减少这种撞名。
# STRING
30 秒速记
SET key value写入返回OK,GET读出值,键不存在返回(nil)DEL返回的是删掉了几个key,不是旧值(nil)= 键不存在,和空字符串""不是一回事;Node.js客户端里分别对应null和''- 常用参数:
SET k v EX 60设过期,SET k v NX不存在才写,合起来就是简易分布式锁 STRING存的是二进制安全的字节,单个值最大512MB;能解析成整数时可以INCR
STRING 是 Redis

