Redis 面试笔记

Redis 面试笔记

参考:Redis 数据结构与对象、为什么用 bgsave、Redis 多路 I/O 复用、缓存穿透/击穿/雪崩

1 数据类型与底层数据结构

Redis 有五种对象类型:

类型常量 对象名称 底层数据结构
REDIS_STRING 字符串对象 INT / EMBSTR / RAW(简单动态字符串 SDS)
REDIS_LIST 列表对象 LINKEDLIST(双端链表)/ ZIPLIST(压缩列表)/ quicklist
REDIS_HASH 哈希对象 HT(字典)/ ZIPLIST
REDIS_SET 集合对象 HT / INTSET(整数集合)
REDIS_ZSET 有序集合对象 ZIPLIST / SKIPLIST(跳跃表+字典)

有序集合 ZSET 的实现

  • 元素数量小于 128 且所有 member 长度小于 64 字节时使用 ziplist(压缩列表) 编码,上限可通过 zset-max-ziplist-entries 和 zset-max-ziplist-value 修改;
  • 否则使用 hash table + 跳跃表 + 双端链表实现;
  • 为何不用平衡树:内存占用少、支持范围查找、实现相对容易。

后续 Redis 版本把 ziplist 优化为 listpack,作为 quicklist 的节点。

排行榜中分数相同、按时间排序怎么做

  1. 把 score 拆成高 32 位和低 32 位,高 32 位存分数、低 32 位存时间;
  2. 将 score 作为小数整数部分、时间戳作为小数部分,即 score.timestamp 的格式作为分数。

2 五种对象的编码转换

  • 字符串对象:int(保存整数)→ embstr(短字符串,一次性分配一块连续内存)→ raw(长字符串);
  • 列表对象:ziplist → linkedlist(触发条件:列表元素超过配置阈值或单个元素过大);
  • 哈希对象:ziplist → hashtable;
  • 集合对象:intset → hashtable;
  • 有序集合:ziplist → skiplist。

3 为什么 Redis 快

  1. 完全基于内存:绝大部分请求是纯粹的内存操作,类似 HashMap 的查找和操作时间复杂度 O(1);
  2. 数据结构简单,是专门设计的;
  3. 单线程:避免了上下文切换和竞争条件,不存在加锁/死锁性能消耗;
  4. 多路 I/O 复用,非阻塞 IO:利用 evport(solaris) → epoll(linux) → poll(os x/freebsd) → select 同时监察多个流(网络连接)的 I/O 事件,空闲时阻塞当前线程,有 I/O 事件时唤醒并轮询(epoll 只轮询真正触发的流),”多路”指多个网络连接,”复用”指复用同一个线程;
  5. 自己构建了 VM 机制,避免系统调用函数的内核态/用户态切换损耗。

4 持久化:RDB 与 AOF

  • RDB:指定的时间间隔内生成数据集的时间点快照(point-in-time snapshot)。父进程 fork 出子进程,子进程写临时 RDB 文件(bgsave),父进程继续服务;
  • AOF:以 Redis 协议格式记录所有写操作命令,追加到文件末尾;启动时通过重新执行这些命令还原数据集。支持后台 rewrite(BGREWRITEAOF),使 AOF 体积不超过保存数据集的实际大小;
  • 混合:可同时使用 AOF 和 RDB,重启时优先使用 AOF 还原数据集,因为 AOF 保存的数据集通常更完整;
  • 也可以关闭持久化,让数据只在运行时存在。

5 主从同步机制

  • 支持主从同步、从从同步;
  • 第一次同步:主节点做一次 bgsave,同时将后续修改操作记录到内存 buffer;完成后将 RDB 文件全量同步到从节点,从节点加载 RDB 镜像到内存;加载完成后通知主节点将期间的修改操作同步过来重放,完成同步;
  • 后续通过增量同步(积累命令)保持数据一致。

6 性能问题与解决方案

  1. Master 最好不做持久化工作(RDB 内存快照和 AOF 日志文件),save 命令调度 rdbSave 会阻塞主线程,快照大时影响性能、间断性暂停服务;
  2. 数据重要时由某个 Slave 开启 AOF 备份,策略设为每秒同步一次;
  3. 为保主从复制速度和连接稳定,Master 和 Slave 最好在同一局域网;
  4. 尽量避免在压力很大的主库上增加从库;
  5. 主从复制不要用图状结构,用单向链表结构更稳定:Master <- Slave1 <- Slave2 <- Slave3...,方便解决单点故障,Master 挂了可立刻启用 Slave1 做 Master;
  6. BGREWRITEAOF 重写会占大量 CPU 和内存,导致服务 load 过高、短暂暂停。

7 淘汰策略(LRU)

问题:MySQL 有 2000w 数据,Redis 只存 20w,如何保证都是热点数据 → 用 lru 算法淘汰。

Redis 提供 6 种淘汰策略:

策略 说明
volatile-lru 从已设置过期时间的数据集中挑选最近最少使用的数据淘汰
volatile-ttl 从已设置过期时间的数据集中挑选将要过期的数据淘汰
volatile-random 从已设置过期时间的数据集中任意选择数据淘汰
allkeys-lru 从数据集中挑选最近最少使用的数据淘汰
allkeys-random 从数据集中任意选择数据淘汰
no-eviction 禁止驱逐数据(默认策略,内存满时写入报错)

8 Redis 与 Memcache 的区别

维度 Memcache Redis
存储方式 全存内存,断电挂掉,数据不能超过内存大小 可持久化到硬盘
数据类型 支持简单 复杂数据类型(String/List/Set/ZSet/Hash)
底层模型 通用系统调用 自建 VM 机制,减少内核态/用户态切换
value 大小 最大 1MB 最大可达 1GB

9 应用场景

  1. 会话缓存(Session Cache):Redis 提供持久化,比 Memcached 有优势;
  2. 全页缓存(FPC):重启实例有磁盘持久化,页面加载不会下降;
  3. 队列:list 和 set 操作使其成为很好的消息队列平台,类似语言本身对 list 的 push/pop;
  4. 排行榜/计数器:内存中对数字递增递减操作极佳,Sorted Set 让排行榜实现简单;
  5. 发布/订阅:社交网络连接、脚本触发器、聊天系统等。

10 缓存穿透 / 击穿 / 雪崩

缓存穿透

查询一个必然不存在的数据,缓存不命中时不写缓存,导致每次请求都打到存储层。流量大时 DB 可能挂掉,人可利用不存在的 key 攻击。

解决:

  1. 布隆过滤器:把所有可能存在的数据哈希到足够大的 bitmap 中,一定不存在的数据会被拦截,避免对底层存储的查询压力;
  2. 缓存空值:查询返回为空(数据不存在或系统故障)也缓存该空结果,但过期时间很短,最长不超过五分钟。

缓存雪崩

设置缓存时采用了相同过期时间,导致缓存在某一时刻同时失效,请求全部转发到 DB,DB 瞬时压力过重雪崩。

解决:

  1. 加锁或队列保证缓存的单线程(进程)写,避免失效时大量并发请求落到底层存储;
  2. 简单方案:把缓存失效时间分散开,在原失效时间基础上加一个随机值(如 1-5 分钟随机),降低过期时间重复率。

缓存击穿

某个热点 key 过期瞬间被超高并发访问,大并发请求在缓存过期时从 DB 加载都回设缓存,瞬间把 DB 压垮。区别:击穿针对某一个 key,雪崩针对很多 key。

解决:

  1. 互斥锁(mutex key):缓存失效时不立即 load DB,先用 SETNX/Memcache ADD 设置一个 mutex key,设置成功再进行 load DB 并回设缓存;否则重试整个 get 流程;
  2. “提前”使用互斥锁:value 内部设置一个比实际 timeout 更小的 timeout1,读到 timeout1 过期时立即延长 timeout1 并重新设置,再从数据库加载数据回设;
  3. “永远不过期”:
    • 物理不过期:Redis 层面确实不设置过期时间,避免热点 key 过期问题;
    • 逻辑过期:把过期时间存在 key 对应 value 里,快过期时由后台异步线程进行缓存构建。对性能非常友好,唯一不足是构建缓存时其他线程可能访问老数据。

11 布隆过滤器

  • 定义:概率型数据结构,特点是可以高效地插入和查询,能告诉你”某样东西一定不存在或可能存在“;
  • 原理:一个 bit 向量/数组,用多个不同的哈希函数生成多个哈希值,把对应的 bit 位置 1。查询时若 k 个 bit 位都为 1 只能说可能存在(可能有哈希碰撞);若存在为 0 的 bit 位,则一定不存在;
  • 特点:比 List/Set/Map 更高效、占用空间更少,但结果是概率性的;不支持删除(删除会导致 false negative);
  • 参数估算:假设 m 位空间、k 个哈希函数、n 个 item,false positive 概率 p≈(1−e^(−kn/m))^k,最优 k = (m/n)ln2;
  • 应用场景:垃圾邮件地址过滤、爬虫 URL 去重、解决缓存穿透、Bigtable/HBase 查不存在的行或列减少磁盘 IO、Chrome 加速安全浏览。

业务实现(用户 3 天内不收到同样的 item)

构造三个 Bloom filter:Bf0 记录 day0/1/2,Bf1 记录 day1/2/3,Bf2 记录 day2/3/4;每个 Bf 有 3 天寿命,到期清零重新记录,彼此相差 1 天;每个 item 都要 ADD 入三个 Bf。这样任何时刻都至少有一个 Bf 记录了近 2 天的推送历史。

阅读 — · 全站 —
🎸 我的歌单 0 首