Redis数据结构 → 单线程 → 集群 追问链#
追问路径#
Q: Redis有哪些数据类型?底层数据结构是什么?
→ 5基础(String/Hash/List/Set/ZSet)+扩展(Bitmap/HLL/GEO/Stream);底层SDS/listpack/quicklist/intset/hashtable/skiplist
Q: ZSet底层为什么用跳表而不是红黑树?
→ 跳表范围查询(ZRANGE)更友好、实现简单、并发改造容易;元素少时用listpack压缩,超阈值转skiplist+hashtable
Q: Redis为什么用单线程还这么快?
→ 纯内存 + 单线程避免锁和上下文切换 + IO多路复用(epoll) + 高效数据结构;瓶颈在网络IO不在CPU
├─ Q: 单线程为什么6.0又引入了多线程?
│ → 命令执行仍单线程(保证原子),只把网络IO读写和协议解析多线程化,分摊大流量下的IO开销
│ Q: 单线程下大Key/慢命令有什么风险?
│ → 单线程串行执行,一个O(n)命令(KEYS/大Key DEL)会阻塞后续所有请求;用SCAN渐进式、UNLINK异步删
│ Q: 怎么做单机到高可用的演进?
│ → 主从复制(读扩展+数据冗余) → 哨兵(自动故障转移) → Cluster(分片+水平扩展)
│ Q: 主从复制怎么同步?全量和增量?
│ → 首次全量(主bgsave生成RDB发从+缓冲期命令);之后增量靠repl_backlog环形缓冲按offset续传(部分重同步)
└─ Q: 哨兵怎么实现自动故障转移?
→ 哨兵集群定期PING,主观下线→多数哨兵确认客观下线→Raft选出领头哨兵→选新主→通知客户端切换
Q: Cluster是怎么分片的?怎么找到key在哪?
→ 16384个槽(slot)分给各主节点,CRC16(key)%16384定位槽;客户端缓存槽映射,命中错节点返回MOVED重定向
Q: Cluster下多key操作和扩容怎么处理?
→ 多key要求同槽(用hash tag {}强制同槽);扩容时槽迁移,迁移中的key返回ASK临时重定向plaintext涉及知识点#
- Redis数据结构与底层实现 — 5种类型与底层编码转换
- 跳表原理(ZSet底层) — 跳表 vs 红黑树的取舍
- 扩展数据类型(Bitmap-HLL-GEO) — 位图/基数统计/地理位置
- Redis单线程为什么快 — 内存+epoll+单线程模型
- Redis主从复制 — 全量/增量同步与repl_backlog
- Redis哨兵机制 — 故障检测与自动切换
- Redis-Cluster槽分配 — 16384槽与CRC16路由
- Redis集群方案 — 主从/哨兵/Cluster对比
- Redis过期与淘汰策略 — 惰性+定期删除与8种淘汰
- 大Key问题与解决 — 大Key阻塞与异步删除
- [Redis Pipeline](/topics/redis/Redis Pipeline) — 批量命令减少RTT
核心串联逻辑#
- 类型与编码:同一类型按数据量自动切换底层编码(如Hash小用listpack、大转hashtable),省内存又保性能
- ZSet用跳表:范围查询友好、实现比红黑树简单、便于并发,配合hashtable实现O(1)按成员查分值
- 单线程快的本质:瓶颈是网络IO不是CPU,纯内存+epoll已够快;6.0多线程只优化网络IO,命令执行仍单线程保原子性
- 高可用三段演进:主从(读扩展)→哨兵(自动故障转移)→Cluster(分片突破单机内存与写瓶颈)
- Cluster路由:CRC16(key)%16384定位槽,客户端缓存映射,错节点返回MOVED;多key需同槽用hash tag
- 代码示例:
bash# hash tag 强制多key落同一槽,才能用MGET/事务/Lua MSET {user:1001}:name Tom {user:1001}:age 20 # {}内相同→同槽 # 渐进式遍历,避免KEYS阻塞 SCAN 0 MATCH user:* COUNT 100
面试回答串联#
30秒速答#
“Redis五种基础类型底层是SDS、listpack、quicklist、跳表等,ZSet用跳表是因为范围查询友好且实现简单。单线程快是因为纯内存加epoll多路复用,瓶颈在IO不在CPU,6.0只把网络IO多线程化。高可用上主从复制做读扩展,哨兵做自动故障转移,Cluster用16384个槽分片做水平扩展,CRC16取模定位槽,错节点返回MOVED重定向。“
2分钟展开答#
“Redis有String、Hash、List、Set、ZSet五种基础类型,加上Bitmap、HyperLogLog、GEO、Stream扩展。底层会根据数据量自动切换编码,比如Hash元素少用listpack省内存、超阈值转hashtable。ZSet底层是跳表加哈希表,用跳表而不是红黑树是因为范围查询ZRANGE更友好、实现更简单、并发改造也容易,哈希表则提供O(1)按成员查分值。Redis单线程还快的原因是数据全在内存、单线程避免了锁和上下文切换、用epoll做IO多路复用、加上高效的数据结构,瓶颈其实在网络IO不在CPU。6.0引入多线程也只是把网络IO的读写和协议解析多线程化,命令执行仍是单线程保证原子性。单线程的风险是慢命令会阻塞所有请求,所以要用SCAN代替KEYS、用UNLINK异步删大Key。高可用是三段演进:主从复制做读写分离和数据冗余,首次全量同步靠主库bgsave生成RDB,之后增量靠repl_backlog环形缓冲按offset续传;哨兵在主从基础上做自动故障转移,多个哨兵确认主库客观下线后用Raft选出领头哨兵来选新主并通知客户端;数据量再大就上Cluster分片,把16384个槽分给各主节点,CRC16(key)对16384取模定位槽,客户端缓存槽映射、访问错节点会返回MOVED重定向,多key操作要求落同一个槽可以用hash tag强制。“
相关追问链#
- Redis持久化-OS-fork-COW追问链 — RDB/AOF与主从全量同步的底层
- Redis缓存问题-一致性-分布式锁追问链 — 数据结构在缓存与锁中的应用
- 分布式共识-Raft-ZAB-选主追问链 — 哨兵选主与Raft的关系
- 秒杀系统-限流-库存扣减-分布式锁追问链 — ZSet/Bitmap在排行榜与去重的应用