<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Io多路复用 on Liangweidong's blog</title><link>https://liangweidonggood.github.io/tags/io%E5%A4%9A%E8%B7%AF%E5%A4%8D%E7%94%A8/</link><description>Recent content in Io多路复用 on Liangweidong's blog</description><generator>Hugo -- gohugo.io</generator><language>zh-cn</language><lastBuildDate>Fri, 15 Jun 2018 00:00:00 +0800</lastBuildDate><atom:link href="https://liangweidonggood.github.io/tags/io%E5%A4%9A%E8%B7%AF%E5%A4%8D%E7%94%A8/index.xml" rel="self" type="application/rss+xml"/><item><title>Java 面试合集：Redis 数据结构与线程模型</title><link>https://liangweidonggood.github.io/p/java-mianshi-redis-shujujiegou-xiancheng/</link><pubDate>Fri, 15 Jun 2018 00:00:00 +0800</pubDate><guid>https://liangweidonggood.github.io/p/java-mianshi-redis-shujujiegou-xiancheng/</guid><description>&lt;img src="https://liangweidonggood.github.io/p/java-mianshi-redis-shujujiegou-xiancheng/image/cover.jpg" alt="Featured image of post Java 面试合集：Redis 数据结构与线程模型" /&gt;
 &lt;blockquote&gt;
 &lt;p&gt;&lt;strong&gt;本文写于 2018 年 6 月&lt;/strong&gt;——Redis 4.0 主线、Redis 5.0（Stream 类型）2018-10 发布前夜。&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;h2 id="一8-大数据类型应用场景"&gt;一、8 大数据类型应用场景
&lt;/h2&gt;&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;类型&lt;/th&gt;
					&lt;th&gt;场景&lt;/th&gt;
					&lt;th&gt;底层实现&lt;/th&gt;
					&lt;th&gt;说明&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;String&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;缓存 / 计数器 / 限流 / 分布式锁 / 共享 Session&lt;/td&gt;
					&lt;td&gt;int + SDS（简单动态字符串）&lt;/td&gt;
					&lt;td&gt;最基础类型&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;Hash&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;缓存对象 / 购物车&lt;/td&gt;
					&lt;td&gt;压缩列表 / 哈希表&lt;/td&gt;
					&lt;td&gt;&lt;strong&gt;7.0 中，压缩列表已经废弃了，交由 listpack 来实现了&lt;/strong&gt;&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;List&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;消息队列&lt;/td&gt;
					&lt;td&gt;双向链表 / 压缩列表&lt;/td&gt;
					&lt;td&gt;简单的字符串列表，最大长度为 &lt;code&gt;2^32 - 1&lt;/code&gt;，40 亿。&lt;strong&gt;3.2 版本之后，底层数据结构就只由 quicklist 实现了&lt;/strong&gt;&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;Set&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;点赞 / 共同关注 / 抽奖活动&lt;/td&gt;
					&lt;td&gt;哈希表 / 整数集合&lt;/td&gt;
					&lt;td&gt;无序并唯一的键值集合&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;ZSet&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;排行榜 / 电话排序 / 姓名排序&lt;/td&gt;
					&lt;td&gt;压缩列表 / 跳表&lt;/td&gt;
					&lt;td&gt;&lt;strong&gt;7.0 中，压缩列表已经废弃了，交由 listpack 来实现了&lt;/strong&gt;&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;BitMap（2.2 版新增）&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;签到 / 用户登陆状态 / 布隆过滤器&lt;/td&gt;
					&lt;td&gt;String&lt;/td&gt;
					&lt;td&gt;统计二值状态&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;HyperLogLog（2.8 版新增）&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;百万级以上的网页 UV 计数&lt;/td&gt;
					&lt;td&gt;自定义&lt;/td&gt;
					&lt;td&gt;用于「统计基数」&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;GEO（3.2 版新增）&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;滴滴叫车&lt;/td&gt;
					&lt;td&gt;Sorted Set&lt;/td&gt;
					&lt;td&gt;地理坐标&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;Stream（5.0 版新增）&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;消息队列&lt;/td&gt;
					&lt;td&gt;自定义&lt;/td&gt;
					&lt;td&gt;完美地实现消息队列&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id="11-选型决策"&gt;1.1 选型决策
&lt;/h3&gt;&lt;pre class="mermaid" style="visibility:hidden"&gt;graph TD
 A[需要存什么?] --&gt; B{单个值?}
 B --&gt;|是| C[String / BitMap / HyperLogLog]
 B --&gt;|否| D{有序?}
 D --&gt;|是| E[ZSet / List / Stream]
 D --&gt;|否| F{唯一?}
 F --&gt;|是| G[Set]
 F --&gt;|否| H[Hash / List]&lt;/pre&gt;&lt;h2 id="二redis-线程模型"&gt;二、Redis 线程模型
&lt;/h2&gt;&lt;h3 id="21-单线程-vs-多线程"&gt;2.1 单线程 vs 多线程
&lt;/h3&gt;
 &lt;blockquote&gt;
 &lt;p&gt;Redis 单线程指的是「接收客户端请求 → 解析请求 → 进行数据读写等操作 → 发送数据给客户端」这个过程是由一个线程（主线程）来完成的，这也是我们常说 Redis 是单线程的原因。&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;p&gt;但是，&lt;strong&gt;Redis 程序并不是单线程的&lt;/strong&gt;，Redis 在启动的时候是会启动后台线程（BIO）的：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Redis 在 2.6 版本&lt;/strong&gt;，会启动 2 个后台线程，分别处理关闭文件、AOF 刷盘这两个任务&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Redis 在 4.0 版本之后&lt;/strong&gt;，新增了一个新的后台线程，用来异步释放 Redis 内存，也就是 lazyfree 线程&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="22-redis-60-引入多线程-io"&gt;2.2 Redis 6.0 引入多线程 I/O
&lt;/h3&gt;
 &lt;blockquote&gt;
 &lt;p&gt;虽然 Redis 的主要工作（网络 I/O 和执行命令）一直是单线程模型，但是 &lt;strong&gt;在 Redis 6.0 版本之后，也采用了多个 I/O 线程来处理网络请求&lt;/strong&gt;，这是因为随着网络硬件的性能提升，Redis 的性能瓶颈有时会出现在网络 I/O 的处理上。&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;所以为了提高网络 I/O 的并行度，Redis 6.0 对于网络 I/O 采用多线程来处理。但是对于命令的执行，Redis 仍然使用单线程来处理。&lt;/strong&gt; 所以大家&lt;strong&gt;不要误解&lt;/strong&gt; Redis 有多线程同时执行命令。&lt;/p&gt;

 &lt;blockquote&gt;
 &lt;p&gt;Redis 官方表示，&lt;strong&gt;Redis 6.0 版本引入的多线程 I/O 特性对性能提升至少是一倍以上&lt;/strong&gt;。&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;h3 id="23-60-线程数配置"&gt;2.3 6.0 线程数配置
&lt;/h3&gt;&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-c" data-lang="c"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// 读请求也使用 io 多线程
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;io&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;threads&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="k"&gt;do&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;reads&lt;/span&gt; &lt;span class="n"&gt;yes&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// io-threads N，表示启用 N-1 个 I/O 线程（主线程也算一个 I/O 线程）
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;io&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;threads&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;
 &lt;blockquote&gt;
 &lt;p&gt;官方的建议是如果为 4 核的 CPU，建议线程数设置为 2 或 3，如果为 8 核 CPU 建议线程数设置为 6，线程数一定要小于机器核数，线程数并不是越大越好。&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;h3 id="24-redis-启动后的线程组成"&gt;2.4 Redis 启动后的线程组成
&lt;/h3&gt;
 &lt;blockquote&gt;
 &lt;p&gt;因此， Redis 6.0 版本之后，Redis 在启动的时候，默认情况下会 &lt;strong&gt;额外创建 6 个线程&lt;/strong&gt; （&lt;em&gt;这里的线程数不包括主线程&lt;/em&gt; ）：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Redis-server&lt;/strong&gt;：Redis 的主线程，主要负责执行命令&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;bio_close_file、bio_aof_fsync、bio_lazy_free&lt;/strong&gt;：三个后台线程，分别异步处理关闭文件任务、AOF 刷盘任务、释放内存任务&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;io_thd_1、io_thd_2、io_thd_3&lt;/strong&gt;：三个 I/O 线程，io-threads 默认是 4，所以会启动 3（4-1）个 I/O 多线程，用来分担 Redis 网络 I/O 的压力&lt;/li&gt;
&lt;/ul&gt;

 &lt;/blockquote&gt;
&lt;h2 id="三redis-为什么这么快"&gt;三、Redis 为什么这么快
&lt;/h2&gt;&lt;h3 id="31-4-大核心原因"&gt;3.1 4 大核心原因
&lt;/h3&gt;
 &lt;blockquote&gt;
 &lt;p&gt;Redis 快的核心原因，可以用一句话概括：&lt;strong&gt;它干的活极其简单，而且它把单线程的优势发挥到了极致，同时通过操作系统底层的&amp;quot;高级外挂&amp;quot;解决了高并发的排队问题。&lt;/strong&gt;&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;h4 id="原因-1绝大多数请求是纯内存操作"&gt;原因 1：绝大多数请求是纯内存操作
&lt;/h4&gt;&lt;p&gt;这是最根本的基础。Redis 的所有数据都存储在内存中，CPU 读写内存的速度（&lt;strong&gt;纳秒级别&lt;/strong&gt;）比起读写机械硬盘或 SSD（&lt;strong&gt;微秒甚至毫秒级别&lt;/strong&gt;）快了数万倍。&lt;/p&gt;
&lt;h4 id="原因-2避免了多线程的内耗"&gt;原因 2：避免了多线程的&amp;quot;内耗&amp;quot;
&lt;/h4&gt;&lt;p&gt;多线程虽然能并发，但也带来了高昂的代价。Redis 采用单线程，反而完美避开了这些性能杀手：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;没有线程切换的开销&lt;/strong&gt;：操作系统在切换线程时，需要保存当前线程的上下文并加载新线程的数据。单线程一个人干到底，零切换成本&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;没有锁的竞争&lt;/strong&gt;：多线程访问共享资源必须加锁，&lt;strong&gt;加锁、解锁以及等待锁的操作非常重&lt;/strong&gt;。Redis 单线程天然保证了所有操作都是&lt;strong&gt;原子性&lt;/strong&gt;的，根本不需要锁&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id="原因-3io-多路复用技术底层外挂"&gt;原因 3：I/O 多路复用技术（底层外挂）
&lt;/h4&gt;&lt;p&gt;&amp;ldquo;单线程&amp;quot;指的是 &lt;strong&gt;Redis 核心的网络事件处理器和键值对读写命令&lt;/strong&gt; 是由一个线程串行执行的。但面对海量的客户端连接，单线程怎么可能忙得过来？&lt;/p&gt;
&lt;p&gt;这就是 &lt;strong&gt;I/O 多路复用（I/O Multiplexing）&lt;/strong&gt; 大显身手的地方。Redis 利用了操作系统底层的非阻塞 I/O 机制（在 Linux 上通常是 &lt;code&gt;epoll&lt;/code&gt;）。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;epoll 工作原理&lt;/strong&gt;：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&lt;/span&gt;&lt;span class="lnt"&gt;13
&lt;/span&gt;&lt;span class="lnt"&gt;14
&lt;/span&gt;&lt;span class="lnt"&gt;15
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;┌─────────────────────────────────────────┐
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│ Redis 主线程 │
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│ ┌──────────────────┐ │
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│ │ epoll_wait() │ ← 阻塞等事件 │
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│ └──────────────────┘ │
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│ ↓ 有事件发生 │
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│ 遍历 fd_list，处理每个就绪 fd │
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│ ├─ Client 1: read() │
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│ ├─ Client 5: read() │
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│ └─ Client 100: read() │
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│ ↓ 业务逻辑 │
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│ 执行 Redis 命令（单线程原子） │
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│ ↓ 写回响应 │
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│ write() → 多线程异步 │
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;└─────────────────────────────────────────┘
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;&lt;strong&gt;epoll 优势&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;select&lt;/strong&gt;：O(n) 遍历所有 fd&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;poll&lt;/strong&gt;：O(n) 遍历所有 fd&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;epoll&lt;/strong&gt;：O(1) 事件通知（内核维护就绪链表）&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id="原因-4精心优化的数据结构"&gt;原因 4：精心优化的数据结构
&lt;/h4&gt;&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;数据结构&lt;/th&gt;
					&lt;th&gt;优化&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;SDS&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;O(1) 长度获取、避免缓冲区溢出、二进制安全&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;跳跃表&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;O(log N) 范围查询，替代红黑树&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;压缩列表&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;连续内存 + 紧凑编码&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;dict（哈希表）&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;渐进式 rehash，不阻塞&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 id="四内存淘汰策略"&gt;四、内存淘汰策略
&lt;/h2&gt;&lt;h3 id="41-8-大策略"&gt;4.1 8 大策略
&lt;/h3&gt;&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;策略&lt;/th&gt;
					&lt;th&gt;描述&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;noeviction&lt;/strong&gt;（默认）&lt;/td&gt;
					&lt;td&gt;不淘汰，写入失败返回错误&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;allkeys-lru&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;所有 key 中淘汰最近最少使用&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;allkeys-lfu&lt;/strong&gt;（4.0+）&lt;/td&gt;
					&lt;td&gt;所有 key 中淘汰最不经常使用&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;allkeys-random&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;所有 key 中随机淘汰&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;volatile-lru&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;设置过期的 key 中淘汰 LRU&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;volatile-lfu&lt;/strong&gt;（4.0+）&lt;/td&gt;
					&lt;td&gt;设置过期的 key 中淘汰 LFU&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;volatile-random&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;设置过期的 key 中随机淘汰&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;volatile-ttl&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;设置过期的 key 中淘汰最近过期&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id="42-选型建议"&gt;4.2 选型建议
&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;缓存场景&lt;/strong&gt;（容忍丢失）：&lt;code&gt;allkeys-lru&lt;/code&gt; / &lt;code&gt;allkeys-lfu&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;数据不能丢&lt;/strong&gt;：&lt;code&gt;noeviction&lt;/code&gt; + 监控告警&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;混合场景&lt;/strong&gt;：&lt;code&gt;volatile-lru&lt;/code&gt;（仅淘汰过期 key）&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="五持久化机制"&gt;五、持久化机制
&lt;/h2&gt;&lt;h3 id="51-三大方式"&gt;5.1 三大方式
&lt;/h3&gt;&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;方式&lt;/th&gt;
					&lt;th&gt;文件&lt;/th&gt;
					&lt;th&gt;频率&lt;/th&gt;
					&lt;th&gt;性能&lt;/th&gt;
					&lt;th&gt;数据安全&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;RDB&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;dump.rdb&lt;/td&gt;
					&lt;td&gt;定时 / 手动&lt;/td&gt;
					&lt;td&gt;高&lt;/td&gt;
					&lt;td&gt;较低（可能丢失最后一次快照后的数据）&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;AOF&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;appendonly.aof&lt;/td&gt;
					&lt;td&gt;每秒&lt;/td&gt;
					&lt;td&gt;中&lt;/td&gt;
					&lt;td&gt;较高（仅丢失 1s 数据）&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;混合（4.0+）&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;dump + aof&lt;/td&gt;
					&lt;td&gt;-&lt;/td&gt;
					&lt;td&gt;中高&lt;/td&gt;
					&lt;td&gt;高&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id="52-rdb-触发"&gt;5.2 RDB 触发
&lt;/h3&gt;&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;span class="lnt"&gt;6
&lt;/span&gt;&lt;span class="lnt"&gt;7
&lt;/span&gt;&lt;span class="lnt"&gt;8
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 自动触发（save 命令）&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;save &lt;span class="m"&gt;900&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt; &lt;span class="c1"&gt;# 900 秒内至少 1 个 key 变更&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;save &lt;span class="m"&gt;300&lt;/span&gt; &lt;span class="m"&gt;10&lt;/span&gt; &lt;span class="c1"&gt;# 300 秒内至少 10 个 key 变更&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;save &lt;span class="m"&gt;60&lt;/span&gt; &lt;span class="m"&gt;10000&lt;/span&gt; &lt;span class="c1"&gt;# 60 秒内至少 10000 个 key 变更&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 手动触发&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;bgsave &lt;span class="c1"&gt;# 后台异步保存&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;save &lt;span class="c1"&gt;# 同步保存（阻塞主线程）&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;h3 id="53-aof-三种策略"&gt;5.3 AOF 三种策略
&lt;/h3&gt;&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# appendfsync 策略&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;appendfsync always &lt;span class="c1"&gt;# 每次写入同步刷盘（最安全，最慢）&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;appendfsync everysec &lt;span class="c1"&gt;# 每秒刷盘（默认，推荐）&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;appendfsync no &lt;span class="c1"&gt;# 操作系统决定（最快，最不安全）&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;h2 id="六redis-集群方案"&gt;六、Redis 集群方案
&lt;/h2&gt;&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;方案&lt;/th&gt;
					&lt;th&gt;数据分片&lt;/th&gt;
					&lt;th&gt;高可用&lt;/th&gt;
					&lt;th&gt;适用&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;主从复制&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;✗&lt;/td&gt;
					&lt;td&gt;手动故障切换&lt;/td&gt;
					&lt;td&gt;简单备份&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;Sentinel&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;✗&lt;/td&gt;
					&lt;td&gt;&lt;strong&gt;自动故障切换&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;中小规模&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;Codis&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;✓&lt;/td&gt;
					&lt;td&gt;手动 + 代理&lt;/td&gt;
					&lt;td&gt;国产分片方案&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;Cluster&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;✓（16384 slot）&lt;/td&gt;
					&lt;td&gt;&lt;strong&gt;自动故障切换&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;大规模生产&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 id="七redis-经典问题"&gt;七、Redis 经典问题
&lt;/h2&gt;&lt;h3 id="71-缓存雪崩"&gt;7.1 缓存雪崩
&lt;/h3&gt;&lt;p&gt;&lt;strong&gt;现象&lt;/strong&gt;：大量 key 同时过期，请求打到数据库。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;解决方案&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;过期时间加随机值&lt;/strong&gt;：&lt;code&gt;expire = base + random(0, 300)&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;熔断降级&lt;/strong&gt;：数据库压力过大时直接拒绝部分请求&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;缓存预热&lt;/strong&gt;：系统启动时把热点数据加载到缓存&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="72-缓存穿透"&gt;7.2 缓存穿透
&lt;/h3&gt;&lt;p&gt;&lt;strong&gt;现象&lt;/strong&gt;：查询不存在的 key，每次都打到数据库。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;解决方案&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;空值缓存&lt;/strong&gt;：&lt;code&gt;key -&amp;gt; null&lt;/code&gt;（短 TTL）&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;布隆过滤器&lt;/strong&gt;：先过布隆过滤器&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;接口校验&lt;/strong&gt;：参数合法性检查&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="73-缓存击穿"&gt;7.3 缓存击穿
&lt;/h3&gt;&lt;p&gt;&lt;strong&gt;现象&lt;/strong&gt;：热点 key 突然过期，瞬间大量请求打到数据库。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;解决方案&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;分布式锁&lt;/strong&gt;：第一个请求查 DB，后续请求等&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;永不过期&lt;/strong&gt;：逻辑过期（不设 TTL，值带过期时间字段）&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;熔断降级&lt;/strong&gt;：直接返回旧值&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="74-数据不一致"&gt;7.4 数据不一致
&lt;/h3&gt;&lt;p&gt;&lt;strong&gt;现象&lt;/strong&gt;：数据库和缓存数据不一致。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;解决方案&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;先更新 DB，再删除缓存&lt;/strong&gt;（Cache-Aside Pattern）&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;延迟双删&lt;/strong&gt;：更新 DB → 删除缓存 → 异步延迟再删&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;binlog 订阅&lt;/strong&gt;：监听 binlog 异步更新缓存&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;设置较短 TTL&lt;/strong&gt;：最终一致性兜底&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="八写在最后"&gt;八、写在最后
&lt;/h2&gt;
 &lt;blockquote&gt;
 &lt;p&gt;&lt;strong&gt;Redis 面试要点&lt;/strong&gt;：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;基础&lt;/strong&gt;：8 大数据类型 + 选型&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;线程模型&lt;/strong&gt;：单线程 vs 多线程 I/O、I/O 多路复用&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;快的原因&lt;/strong&gt;：内存操作 + 无锁 + epoll + 优化数据结构&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;持久化&lt;/strong&gt;：RDB / AOF / 混合&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;集群&lt;/strong&gt;：主从 / Sentinel / Cluster&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;经典问题&lt;/strong&gt;：雪崩 / 穿透 / 击穿 / 一致性&lt;/li&gt;
&lt;/ol&gt;

 &lt;/blockquote&gt;
&lt;h2 id="参考资料"&gt;参考资料
&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;&lt;a class="link" href="https://redis.io/documentation" target="_blank" rel="noopener"
 &gt;Redis 官方文档&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://book.douban.com/subject/25900156/" target="_blank" rel="noopener"
 &gt;Redis 设计与实现&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://github.com/redis/redis" target="_blank" rel="noopener"
 &gt;Redis 源码&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://redis.io/docs/manual/client-side-caching/" target="_blank" rel="noopener"
 &gt;Redis 6.0 多线程 I/O 解析&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description></item></channel></rss>