ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Redis 之 【常见面试题总结】(从数据类型到高可用、缓存一致性全解析)

Redis 之 【常见面试题总结】(从数据类型到高可用、缓存一致性全解析) 目录1. 面试考点地图2. Redis 基础认知2.1. 什么是 Redis有什么特点2.2. Redis 为什么把数据放到内存中2.3. Redis 常见应用场景3. 数据类型与底层编码3.1. Redis 支持哪些数据类型3.2. 底层数据结构和编码方式3.3. ZSet 为什么用跳表不用红黑树4. 线程模型与网络 IO4.1. Redis 为什么是单线程模型4.2. Redis 的 IO 多路复用是怎么回事5. 持久化机制5.1. RDB5.2. AOF5.3. AOF 重写机制5.4. RDB 和 AOF 如何选择6. 过期、淘汰与内存管理6.1. 如何设置 key 过期时间6.2. Redis 的 key 过期删除策略6.3. 大量 key 同时过期会怎样6.4. Redis 淘汰策略7. 高可用主从、哨兵、集群7.1. 主从复制7.2. 哨兵 Sentinel7.3. Redis Cluster 集群7.4. 一致性哈希8. 事务、Pipeline 与消息队列8.1. Redis 事务8.2. Pipeline8.3. Redis 作为消息队列9. 缓存与分布式实战9.1. 分布式锁9.2. 缓存穿透、雪崩、击穿9.3. 热 key 问题9.4. Redis 和 MySQL 双写一致性9.5. bigkey 问题10. 运维、协议与高级命令10.1. 测试连通性10.2. 常用管理命令10.3. 为什么生产禁用 KEYS *10.4. Redis 网络协议10.5. 查找附近的人11. 面试速查表1. 面试考点地图Redis 面试通常围绕以下主线展开基础认知是什么、为什么快、为什么放内存、应用场景数据类型String、List、Hash、Set、ZSet以及 Bitmap、HyperLogLog、GEO、Stream底层编码raw、int、embstr、ziplist、listpack、quicklist、hashtable、intset、skiplist线程模型单线程、6.0 多线程、IO 多路复用、epoll持久化RDB、AOF、混合持久化、AOF 重写内存管理过期删除、淘汰策略、内存用尽高可用主从复制、哨兵、集群、哈希槽、一致性哈希功能特性事务、Pipeline、消息队列、分布式锁缓存实战穿透、雪崩、击穿、热 key、bigkey、双写一致性运维与协议常用命令、RESP、SCAN、GEO面试时不要只背答案最好按“结论 → 原理 → 场景 → 权衡 → 版本差异”回答2. Redis 基础认知2.1. 什么是 Redis有什么特点Redis 是一个高性能的key-value内存数据库。它不同于 MySQL 这类关系型数据库数据主要存储在内存中也支持持久化到硬盘不使用“表”组织数据而是使用键值对没有复杂查询、外键、事务隔离级别等关系型数据库能力换来的是简单、灵活和高性能Redis 的典型特点内存存储性能极高支持多种数据结构支持持久化RDB、AOF核心命令执行是单线程模型支持主从复制、哨兵、集群支持事务、Lua 脚本、Pipeline支持多语言客户端支持发布订阅、Stream 消息队列高版本支持多线程网络 IORedis 本质是一个基于内存的 KV 数据库常用于缓存、计数器、排行榜、分布式锁、消息队列等场景。它的核心优势是快原因是内存访问、单线程无锁、IO 多路复用、高效数据结构。缺点主要是内存成本高、容量有限、持久化和主从复制存在一致性权衡2.2. Redis 为什么把数据放到内存中核心原因是效率内存随机访问大约 100ns硬盘随机访问大约 10,000,000ns差距 3 到 4 个数量级。因此 Redis 比 MySQL 等磁盘数据库有显著性能优势但内存存储也有劣势内存容量比硬盘小成本更高掉电易失所以需要 RDB/AOF 持久化数据量受内存限制需要淘汰策略和集群分片2.3. Redis 常见应用场景缓存热点数据缓存降低数据库压力计数器点击量、访问量、收藏数排行榜基于 ZSet 实现分布式会话多模块共享 Session分布式锁SET NX 过期时间 Lua 释放消息队列List、Pub/Sub、Stream附近的人GEO签到、UV 统计Bitmap、HyperLogLog限流计数器、滑动窗口、令牌桶3. 数据类型与底层编码3.1. Redis 支持哪些数据类型类型说明典型场景String字符串、整数、二进制缓存、计数器、分布式锁List双向列表消息队列、时间线Hash哈希表对象属性、购物车Set无序集合去重、共同好友、标签ZSet有序集合排行榜、延迟队列扩展类型Bitmap二进制位适合签到、活跃用户统计Bitfield把字符串当位图并进行位操作HyperLogLog基数统计用极小内存估算 UV有误差Geospatial地理经纬度支持附近查询Stream消息队列支持消费组、ACK、持久化前五种是通用类型后几种是特定场景类型3.2. 底层数据结构和编码方式Redis 会根据数据量、元素长度、版本自动选择编码。常用查看命令object encoding key常见对应关系数据类型内部编码Stringint、embstr、rawHashziplist/listpack、hashtableListziplist、linkedlist、quicklistSetintset、hashtable、listpackZSetziplist/listpack、skiplist关键点int字符串是整数且范围合适时直接存整数embstr短字符串优化通常小于等于 39 字节raw长字符串ziplist压缩列表本质是字节数组省内存但增删可能连锁更新listpackRedis 7 开始引入用来替代 ziplist解决连锁更新问题quicklist链表每个节点是 ziplist/listpack兼顾内存和性能hashtable真正哈希表O(1) 查找intset整数集合适合小整数集合skiplist跳表支持 O(logN) 查找、插入、删除和范围查询3.3. ZSet 为什么用跳表不用红黑树跳表和红黑树的插入、查询、删除复杂度都是 O(logN)但 Redis 选择跳表跳表实现更简单代码可维护性高跳表不需要红黑树那样的旋转和重新平衡跳表天然支持范围查询ZRANGE、ZREVRANGE 更方便跳表可以通过概率平衡实现难度低Redis 是单线程简单稳定比极致复杂更重要跳表不会像普通链表那样退化因为层级通过随机函数生成期望复杂度 O(logN)4. 线程模型与网络 IO4.1. Redis 为什么是单线程模型Redis 的主要瓶颈通常不是 CPU而是内存和网络 IO。使用多线程不会带来太大收益反而引入线程安全、锁竞争、上下文切换问题单线程的好处命令串行执行天然避免并发安全问题不需要复杂锁机制实现简单行为可预测配合 IO 多路复用可以高效处理大量连接Redis并不是完全单线程。它通过后台线程或子进程来处理一些耗时任务持久化通过fork()创建子进程进行RDB快照和AOF重写异步删除使用UNLINK等命令由后台线程负责回收内存AOF刷盘在默认的everysec策略下fsync操作由后台线程执行若设为always则由主线程同步执行网络I/ORedis 6.0使用多个I/O线程并行处理网络请求和协议解析但命令执行仍是单线程4.2. Redis 的 IO 多路复用是怎么回事“IO 多路复用”就是用一个线程管理多个 socket按需激活处理。传统“一连接一线程”模式下大量连接可能大部分时间不活跃线程都在挂起浪费资源。Redis 使用 Linux 的 epoll内核维护红黑树管理所有 socket每个节点关联事件回调网卡收到数据后内核判断属于哪个 socket触发回调唤醒用户线程Redis 线程处理就绪的请求Linux IO 多路复用三种实现select最早有文件描述符数量限制poll链表实现没有数量限制但仍需轮询epoll最先进事件驱动适合高并发Redis 高并发不是因为多线程而是因为单线程 epoll 内存操作 高效数据结构Redis 的高效数据结构指的是它为不同数据类型定制的底层编码String 用 SDS支持 O(1) 长度获取和二进制安全Hash 用 dict 渐进式 rehash小数据量时用 listpack/ziplist 压缩存储List 用 quicklist兼顾内存和操作效率Set 用 intset 或 dictZSet 用 listpack skiplist兼顾排序和范围查询还有 rax、HyperLogLog、GEO 等专用结构。它们通过紧凑内存布局、按数据量自动切换编码、减少指针和内存碎片、提供 O(1)/O(log N) 操作让 Redis 在单线程下也能以极低 CPU 和内存开销处理海量请求5. 持久化机制Redis 持久化主要有两种RDB和AOF。Redis 4.0 后支持混合持久化5.1. RDBRDB 是内存快照把某一时刻数据以二进制写入磁盘触发方式手动SAVE、BGSAVE自动配置 save m nm 秒内发生 n 次修改触发从节点全量复制触发执行 SHUTDOWN 关闭 Redis 时触发区别SAVE阻塞主线程期间无法处理写请求BGSAVEfork 子进程生成 RDB父进程继续处理请求子进程写 RDB 期间新写入数据不一定进入当前 RDB需要下一轮持久化优点文件紧凑、恢复快、适合备份缺点可能丢数据fork 有开销5.2. AOFAOF 记录写命令重启时重放命令恢复数据AOF 同步策略策略说明丢失风险always每条命令 fsync 后返回几乎不丢但性能差everysec先 write每秒 fsync最多丢 1 秒no只 write由 OS 控制 fsync丢失可能较多5.3. AOF 重写机制AOF 文件会越来越大需要重写压缩过程简述触发 BGREWRITEAOF 或自动重写fork 子进程根据当前内存数据生成新 AOF主进程继续处理命令同时写入 AOF 重写缓冲区子进程完成后主进程把重写期间新命令追加到新 AOF用新 AOF 替换旧 AOFAOF 重写会阻塞吗fork 瞬间可能阻塞之后子进程重写主进程继续服务但 fork 成本与内存页表有关内存越大越慢5.4. RDB 和 AOF 如何选择只做缓存可以只开 RDB 或不开数据不能丢太多AOF everysec RDB恢复速度优先RDB数据安全优先AOF生产常见混合持久化RDB 快照 AOF 增量6. 过期、淘汰与内存管理6.1. 如何设置 key 过期时间SET key value EX 60 SET key value PX 60000 EXPIRE key 60 PEXPIRE key 60000EX 是秒PX 是毫秒。也可以单独用 EXPIRE6.2. Redis 的 key 过期删除策略Redis 同时使用惰性过期访问 key 时才判断是否过期过期则删除。省 CPU但可能浪费内存定期过期每隔 100ms 随机抽取一定数量 key 检查删除。CPU 和内存折中expires 字典保存所有设置了过期时间的 key 及其过期时间6.3. 大量 key 同时过期会怎样如果大量 key 在同一时间过期Redis 可能短暂卡顿原因定期采样删除时如果过期 key 比例超过 25%会持续删除直到最大耗时 25ms对高并发场景25ms 阻塞也可能造成抖动解决方案过期时间加随机值打散过期点业务允许时不必精确到毫秒监控过期 key 数量避免集中过期6.4. Redis 淘汰策略当内存不足继续写入会触发淘汰策略策略说明volatile-lru从设置了过期时间的 key 中按 LRU 淘汰allkeys-lru从所有 key 中按 LRU 淘汰volatile-lfu从过期 key 中按 LFU 淘汰4.0allkeys-lfu从所有 key 中按 LFU 淘汰4.0volatile-random从过期 key 中随机淘汰allkeys-random从所有 key 中随机淘汰volatile-ttl从过期 key 中优先淘汰更早过期的noeviction默认不淘汰写入报错LRU最近最久未使用LFU最近使用频率最低默认 noeviction 能暴露问题但很多缓存场景会用 allkeys-lru如果数据重要不要用 allkeys-*应该有监控报警内存接近上限就扩容Redis 内存耗尽时会触发淘汰noeviction 下写入报错7. 高可用主从、哨兵、集群7.1. 主从复制作用提高可用性主节点挂了可以从节点读分担读压力主写从读配置 slaveof master_ip master_port 或配置文件 replicaof流程从节点启动后清空自身数据全量复制主节点数据后续主节点写命令同步到从节点主节点可读写从节点默认只读优点读扩展、数据备份缺点主节点故障需要手动或哨兵切换异步复制可能丢数据7.2. 哨兵 Sentinel哨兵解决主从故障自动切换功能监控主从节点主观下线、客观下线选举 Leader 哨兵从从节点中选新主通知客户端新主地址继续监控主节点宕机后单个哨兵认为主下线是主观下线多个哨兵确认是客观下线哨兵选举 LeaderLeader 选择合适从节点提升为主其他从节点指向新主通知客户端7.3. Redis Cluster 集群集群用于分片存储解决单机内存和性能上限特点数据分片每个主节点负责一部分槽共有 16384 个哈希槽槽计算CRC16(key) mod 16384至少 3 主 3 从主从保证高可用支持 MOVED、ASK 重定向不支持 SELECT 多数据库只能使用 db0异步复制特定条件下可能丢写最大节点数作者建议不要超过 1000 个不是硬限制7.4. 一致性哈希一致性哈希用于分布式缓存分片原理把哈希值空间组织成环节点和数据都映射到环上数据顺时针找到第一个节点增加虚拟节点解决数据倾斜优点扩缩容时只影响相邻数据迁移量小缺点实现复杂需要虚拟节点仍有倾斜可能Redis Cluster 没有用一致性哈希而是用哈希槽因为槽更易管理、迁移和重新分配8. 事务、Pipeline 与消息队列8.1. Redis 事务相关命令MULTI开启事务EXEC执行事务DISCARD取消事务WATCH乐观锁监视 key特点命令入队EXEC 时按顺序执行不支持回滚。运行时错误其他命令仍可能继续执行不是 MySQL 那种强原子、隔离级别、回滚事务更像“命令打包串行执行”WATCH 可实现乐观锁若 key 被修改则 EXEC 失败和 MySQL 区别维度RedisMySQL原子性弱不支持回滚强支持回滚隔离性单线程串行多隔离级别持久性取决于持久化取决于日志和配置复杂度简单复杂8.2. PipelinePipeline 把多个命令合并到一个请求发送减少 RTT普通模式发送命令 - 排队 - 执行 - 返回 发送命令 - 排队 - 执行 - 返回Pipeline发送多个命令 - 排队 - 执行 - 一次性返回注意Pipeline 命令不是原子的只是节省网络往返命令太多可能阻塞服务器可用 cat cmd.txt | redis-cli --pipe8.3. Redis 作为消息队列三种方式ListLPUSH/RPUSH 生产BLPOP/BRPOP 阻塞消费Pub/Sub发布订阅但不持久化消费者离线会丢消息StreamRedis 5 引入支持消费组、ACK、持久化功能更完整Redis Pub/Sub 是发布订阅模式用 PUBLISH 发消息SUBSCRIBE 订阅频道PSUBSCRIBE 模式订阅。它的特点是实时推送但不持久化、不存储、无 ACK消费者离线就丢消息投递语义是至多一次。所以它只适合实时通知、广播这类场景不能当可靠消息队列用。可靠消息一般用 Redis Stream 或者 Kafka/RabbitMQ。Sentinel 内部就用 Pub/Sub 来互相通知9. 缓存与分布式实战9.1. 分布式锁基本实现SET lock_key unique_value NX PX 30000释放锁要用 Lua 保证原子if redis.call(get, KEYS[1]) ARGV[1] then return redis.call(del, KEYS[1]) else return 0 end要点value 必须唯一防止误删别人的锁必须设置过期时间防止死锁释放锁必须原子业务未完成可续期看门狗机制主从切换可能丢锁强一致场景考虑 Redlock 或 ZooKeeperRedlock 有争议面试可提“多数派加锁但时钟漂移和网络延迟仍有挑战”Redlock 是 Redis 作者提出的分布式锁算法部署多个独立 Redis 节点客户端在多数派节点上加锁成功才认为获取锁以此提高可靠性。但它依赖各节点本地时钟判断锁过期并假设网络延迟有界时钟漂移、GC 停顿、网络抖动都可能导致锁提前失效或客户端在锁已过期后仍误以为自己持有锁从而破坏互斥。因此 Redlock 无法保证绝对安全业界对其争议较大ZooKeeper 和 etcd 都是分布式协调服务核心是在分布式环境中提供强一致、高可用的元数据存储与协调能力。ZooKeeper 基于 ZAB 协议通过 ZNode 树、临时顺序节点和 Watch 实现分布式锁、选主、服务发现和配置管理etcd 基于 Raft 协议提供键值存储、租约 Lease、Watch 和事务是 Kubernetes 的核心数据存储。两者都依赖多数派共识不依赖物理时钟来判断锁安全因此在强一致要求的分布式锁、选主等场景中比 Redis Redlock 更可靠。区别上ZooKeeper 较老、Java 生态常用etcd 更轻量、云原生生态更主流。9.2. 缓存穿透、雪崩、击穿问题含义解决方案缓存穿透查不存在的数据请求打到 DB缓存空值、布隆过滤器、参数校验缓存雪崩大量 key 同时过期或 Redis 宕机随机过期、多级缓存、限流熔断、集群缓存击穿热点 key 过期大量请求打到 DB互斥锁、逻辑过期、永不过期、后台更新9.3. 热 key 问题某些 key 访问频率极高可能打挂 Redis。集群下热 key 仍在同一分片其他分片帮不上忙解决扩大集群增加热 key 所属分片的从节点应用层识别热 key做二次哈希拆成多个 key热 key 单独部署 Redis 集群使用本地缓存减少 Redis 压力限流、降级、熔断9.4. Redis 和 MySQL 双写一致性双写一致性指修改数据库时也要更新或删除缓存否则缓存可能是脏数据方案一延时双删先删除缓存更新数据库再次删除缓存目的第一次删除失败第二次兜底因为直接改缓存可能并发覆盖不如删除缓存后续读时从 DB 加载方案二删除缓存重试删除失败把 key 放入 MQ反复重试可用阿里 canal 订阅 MySQL binlog异步删除缓存binlog 订阅是指通过 Canal、Debezium、Maxwell 等工具伪装成 MySQL 从库向主库发送 dump 协议请求实时接收并解析 binlog从而捕获数据库的增删改操作。在缓存一致性场景中应用更新数据库后订阅程序监听到对应变更事件再异步删除或更新缓存避免业务代码耦合双写实现最终一致此外也常用于数据同步、审计、搜索索引更新等。方案三先更新 DB再删缓存这是常见 Cache Aside 模式Cache Aside 是旁路缓存模式应用先读缓存未命中查数据库并回填写时先更新数据库再删除缓存。它简单常用能保证最终一致但并发下可能短暂脏读可通过延迟双删、binlog 订阅、过期时间兜底9.5. bigkey 问题bigkey 指 value 占用空间过大比如超长字符串、超大 Hash/Set/List危害读写慢阻塞 Redis网络拥塞集群数据倾斜删除时可能阻塞解决拆分大 key 为多个小 key用 redis-cli --bigkeys 查找删除用 UNLINK后台异步删除集合遍历用 HSCAN、SSCAN、ZSCAN10. 运维、协议与高级命令10.1. 测试连通性PING返回 PONG 即连通10.2. 常用管理命令DBSIZE # 当前数据库 key 数量 INFO # 服务器状态和统计 MONITOR # 实时监听请求 SHUTDOWN # 保存并关闭 CONFIG GET parameter # 获取配置 CONFIG SET parameter value DEBUG OBJECT key # 查看 key 调试信息 DEBUG SEGFAULT # 制造宕机慎用 FLUSHDB # 删除当前库慎用 FLUSHALL # 删除所有库慎用10.3. 为什么生产禁用 KEYS *KEYS *会遍历所有 key数据量大时耗时极长甚至阻塞 Redis导致生产故障替代SCANSCAN cursor [MATCH pattern] [COUNT count]特点渐进式遍历每次 O(1)返回下一次游标游标为 0 表示结束可能重复或遗漏还有 HSCAN、SSCAN、ZSCANSCAN 是渐进式遍历不保证快照一致性。遍历期间哈希表 rehash 或元素被修改会导致同一元素重复返回或被删除的元素不再返回。对于遍历期间一直存在的元素SCAN 保证至少返回一次但可能重复10.4. Redis 网络协议Redis 使用 RESPRedis Serialization Protocol应用层纯文本协议客户端和服务器通过 RESP 通信。注意不是 RSP10.5. 查找附近的人使用 GEOGEOADD key 经度 纬度 member ... GEORADIUS key 经度 纬度 距离底层基于 ZSet 和 Geohash。适合附近的人、打车、门店查询11. 面试速查表问题一句话答案Redis 是什么高性能内存 KV 数据库为什么快内存、单线程、epoll、高效数据结构五种类型String、List、Hash、Set、ZSetZSet 为什么跳表实现简单、范围查询方便、无需重平衡单线程原因瓶颈在内存/IO不在 CPU避免锁6.0 多线程网络 IO 和协议解析多线程命令执行单线程IO 多路复用epoll 管理大量 socket事件驱动持久化RDB、AOF、混合持久化RDB 触发save/bgsave、save m n、全量复制、shutdownAOF 同步always、everysec、no过期删除惰性 定期大量 key 同时过期随机过期时间打散淘汰策略LRU、LFU、random、ttl、noeviction主从作用读扩展、备份、故障恢复哨兵作用监控、选主、自动故障转移集群分片16384 哈希槽集群选库不支持只能 db0集群丢数据异步复制可能丢写事务命令MULTI、EXEC、DISCARD、WATCH事务区别不支持回滚弱原子Pipeline减少 RTT非原子消息队列List、Pub/Sub、Stream分布式锁SET NX PX Lua 释放 续期缓存穿透缓存空值、布隆过滤器缓存雪崩随机过期、多级缓存、熔断缓存击穿互斥锁、逻辑过期热 key本地缓存、拆分、单独集群双写一致延时双删、删除重试、binlogbigkey拆分、UNLINK、SCAN 遍历遍历 keySCAN不用 KEYS *附近的人GEOADD、GEORADIUS
返回列表