Redis 原理详解(详解在注释里面,图片加载需要时间)
2023-07-04 08:38:48 0 举报
AI智能生成
Redis知识要点和原理解析,详细信息和图片均在黄色图标的注释中,鼠标移动到黄色图标上即会显示,图片加载有时较慢。
作者其他创作
大纲/内容
redisObject
两层结构<br>
使用者的角度<br>
暴露给外部的调用接口:string,list,hash,set,sorted set<br>
内部实现的角度<br>
底层的实现:dict,sds,ziplist,quicklist,skiplist,intset<br>
字段<br>
type<br>
OBJ_STRING<br>
OBJ_LIST<br>
OBJ_SET<br>
OBJ_ZSET<br>
OBJ_HASH<br>
encoding<br>
lru<br>
refcount<br>
ptr<br>
作用<br>
redisObjec是<b><font color="#f44336">联结两个层面的数据结构的桥梁</font></b><br>
<b><font color="#f44336">为多种数据类型提供一种统一的表示方式</font></b><br>
<b><font color="#f44336">允许同一类型的数据采用不同的内部表示</font></b>,从而在某些情况下尽量节省内存
<b><font color="#f44336">支持对象共享和引用计数</font></b>。当对象被共享的时候,只占用一份内存拷贝,进一步节省内存<br>
内部数据结构<br>
简单动态字符串SDS
C++实现及缺点<br>
SDS结构优势<br>
SDS内部结构<br>
结构
len:数组已使用长度<br>
alloc:数组分配空间总长度<br>
alloc - len:数组未使用空间长度<br>
flags
5 种类型<br>
sdshdr5、sdshdr8、sdshdr16、sdshdr32、sdshdr64,区别在于数组的 len 长度和分配空间长度 alloc<br>
目的:节省内存<br>
字符串对象的内部编码(encoding)类型<br>
int:用于整数类型<br>
embstr:用于短字符串<br>
raw:保存长度超过 44 的字符串<br>
目的<br>
<b><font color="#f44336">为了针对不同大小的字符串,使用不同的 SDS 类型保存,从而节省内存占用</font></b><br>
应用场景
String底层实现之一
<font color="#f44336">整数</font><b style=""><font color="#b71c1c">集合</font></b> intset<br>
定义
字段
encoding<br>
length<br>
contents<br>
可用保存的数据类型<br>
int16_t<br>
int32_t<br>
int64_t<br>
特性<br>
整数集合中的<b>元素有序</b><br>
方便使用<b><font color="#f44336">二分法</font></b>进行查找<br>
集合升级<br>
1、分配空间
2、调整位置和类型<br>
3、添加元素<br>
升级后不会降级<br>
<b>保证集合中不会出现重复元素</b>
复杂度
查找是 O(log n)<br>
插入和删除都是 O(n)<br>
<b><font color="#f44336">存储元素相对较少的时候</font></b>,O(log n) 和 O(n) 差距不是很大
优点<br>
灵活<br>
节省内存空间
应用场景
set 的底层实现之一
压缩列表 ziplist
ziplist 的结构<br>
entry 节点<br>
prevlen<br>
encoding<br>
content<br>
<b><font color="#f44336">字符串(字节数组)或者整数</font></b>
为什么要这样设计呢<br>
连锁更新<br>
当添加或删除节点时,可能就会因为previous_entry_length的变化导致发生连锁的更新操作
常见操作<br>
获取<br>
先从 ziplist 中拿到 field 的指针,然后向后一个节点就是 value<br>
删除<br>
删除其实就是先查找,后删除<br>
插入 / 更新<br>
<b><font color="#f44336">每次更新都要重新申请空间</font></b>
特征<br>
结构紧凑,访问效率高<br>
一整块连续内存,没有多余的内存碎片,<font color="#f44336"><b>更新会导致内存 realloc 与内存复制,平均时间复杂度为 O(N)</b></font>
<b>逆向遍历</b><br>
从<b><font color="#f44336">表尾</font></b>开始<b><font color="#f44336">向表头</font></b>进行<b><font color="#f44336">遍历</font></b>(注意了!,但是<font color="#f44336"><b>插入还是在表头</b></font>)<br>
连锁更新<br>
压缩列表是一种<b><font color="#f44336">为节约内存</font></b>而开发的顺序型数据结构,是<font color="#f44336"><b> ZSET、HASH 和 LIST </b></font>的底层实现之一
ziplist 是一个双向链表,可以在时间复杂度为 O(1) 从头部、尾部进行 pop 或 push。
应用场景
ZSET、HASH 和 LIST 的底层实现之一
快速列表 quicklist<br>
quicklist
quicklistNode<br>
没压缩:ziplist
压缩:quicklistLZF<br>
布局结构<br>
特征
双向链表的复合结构体<br>
底层实现是quicklist<br>
需要合理的配置节点中 ziplist 的 entry 个数<br>
元素可以重复(<font color="#f44336"><b>即在List实现中,每个节点的zipList中的元素也可以重复</b></font>),按插入顺序排序
应用场景
List底层实现
跳跃表 skiplist<br>
为什么使用跳跃表(类比红黑树/ 平衡树)<br>
性能考虑<br>
实现考虑<br>
特征
有序<br>
链表
创建过程
skiplist 与平衡树、哈希表的比较<br>
应用场景
ZSet底层实现
字典 dict
Redis 的字典用哈希表(dictht)的方式实现<br>
哈希节点dictEntry<br>
哈希算法
哈希冲突<br>
拉链法<br>
rehash<br>
渐进式 rehash
五大类型<br>
String<br>
介绍
内部实现<br>
int<br>
SDS<br>
应用场景<br>
缓存对象<br>
常规计数<br>
分布式锁<br>
List<br>
列表对象有 3 种编码<br>
ziplist<br>
quicklist<br>
linkedlist<br>
3.2 版本及后续版本将不再使用<br>
配置<br>
fill (控制 ziplist 大小)<br>
compress (控制压缩程度)<br>
特征
双向链表的<b><font color="#f44336">内存开销很大</font></b>,每个<b><font color="#f44336">节点的地址不连续,容易产生内存碎片</font></b><br>
quicklist <b><font color="#f44336">利用 ziplist减少节点数量</font></b><br>
但 <b>ziplist</b> <font color="#f44336"><b>插入和删除数麻烦</b></font>,<b><font color="#f44336">复杂度高</font></b><br>
为<b>避免长度较长的 ziplist</b>修改时带来的内存拷贝开销,<b>通过配置项配置合理的 ziplist长度</b>
应用场景<br>
消息队列<br>
必须要满足三个需求<br>
消息保序<br>
使用 LPUSH + RPOP<br>
问题及解决<br>
性能消耗<br>
解决方案<br>
BRPOP
处理重复的消息<br>
生产者自行实现全局唯一 ID<br>
保证消息可靠性<br>
使用 BRPOPLPUSH
Hash<br>
哈希对象的编码有两种<br>
ziplist<br>
hashtable
编码转换<br>
应用场景<br>
缓存对象<br>
Set<br>
集合对象的编码有两种<br>
intset<br>
hashtable<br>
特征
不要求排序(intset本身是有序的)
不重复
应用场景<br>
点赞<br>
共同关注<br>
抽奖活动<br>
ZSet
有序集合有两种编码方式<br>
压缩列表 ziplist<br>
跳表 skiplist<br>
图示
编码转换<br>
应用场景<br>
排行榜<br>
电话、姓名排序<br>
内存模型<br>
内存统计
info memory
Redis内存划分<br>
进程本身运行内存<br>
对象内存<br>
缓冲内存<br>
内存碎片<br>
内存回收<br>
<b><font color="#f44336">定期删除+惰性删除</font></b>
定时删除
惰性删除<br>
内存淘汰机制<br>
noeviction(默认)<br>
allkeys-random
allkeys-lru<br>
allkeys-lfu
volatile-random
volatile-lru<br>
volatile-lfu<br>
volatile-ttl<br>
lru - 最近最少使用<br>
传统lru
问题
维护前后指针及哈希表,空间消耗大
redis lur - 近似lru
增加<b>24bit</b>长度时间属性
随机取5个,淘汰最晚时间的那个<br>
lru存在问题<br>
lfu - 最近最不经常使用
目的:淘汰内存中<b><font color="#f44336">不经常使用</font></b>的数据
字段
time
counter<br>
增长<br>
衰减<br>
工作过程<br>
主从同步<br>
分类<br>
1、从节点与主节点建立连接时进行全量同步<br>
<font color="#f44336"><b>通过RDB + replication_buffer</b></font>
异常情况<br>
2、主节点与从节点正常运行时的同步<br>
3、主节点与从节点连接断开后又重连时会进行增量同步或全量同步<br>
增量同步<br>
<font color="#f44336"><b>实现方式</b></font>
环形数组repl-backlog-buffer
master-repl-offset
slave-repl-offset
特别注意
repl_backlog_buffer:复制积压缓冲区<br>
默认 1MB
心跳<br>
注意问题<br>
数据延迟<br>
读到过期数据<br>
规避全量复制<br>
规避复制风暴<br>
单一主节点<br>
单一机器<br>
总结
持久化
RDB
触发过程<br>
手动<br>
save 命令<br>
<b><font color="#f44336">阻塞 Redis 服务</font></b>,直到整个 RDB 持久化完成,<b><font color="#f44336">不建议</font></b><br>
bgsave 命令<br>
该模式下的 RDB 持久化由子进程完成,<b><font color="#f44336">非阻塞</font></b><br>
自动<br>
save m n<br>
RDB 数据恢复<br>
优缺点<br>
优点<br>
由于 RDB 文件是一个<b><font color="#f44336">非常紧凑</font></b>的二进制文件,所以<b><font color="#f44336">加载的速度快于 AOF 方式</font></b><br>
fork 子进程方式,<b><font color="#f44336">不会阻塞</font></b>
RDB 文件代表着 Redis 服务器的某一个时刻的全量数据,所以它非常<font color="#f44336"><b>适合做冷备份和全量复制的场景</b></font><br>
缺点<br>
<b><font color="#f44336">没办法做到实时持久化,会存在丢数据的风险</font></b>
AOF(默认关闭)<br>
目的<br>
<b><font color="#f44336">解决数据持久化的实时性</font></b>
执行流程<br>
命令写入<br>
文件同步<br>
文件重写<br>
手动触发<br>
自动触发<br>
优缺点<br>
优点<br>
相比于 RDB,<b><font color="#f44336">AOF 更加安全,默认同步策略为 everysec 即每秒同步一次</font></b>,所以顶多我们就失去一秒的数据<br>
根据关注点不同,AOF 提供了不同的同步策略,我们可以根据自己的需求来选择<br>
AOF 文件是以 append-only 方式写入,相比如 RDB 全量写入的方式,它没有任何磁盘寻址的开销,<b><font color="#f44336">写入性能非常高</font></b>
缺点<br>
由于 AOF 日志文件是命令级别的,所以相比于 RDB 紧致的二进制文件而言它的<b><font color="#f44336">加载速度会慢些</font></b><br>
AOF 开启后,支持的写 QPS 会比 RDB 支持的<b><font color="#f44336">写 QPS 低</font></b><br>
RDB 和 AOF 混合模式<br>
RDB-AOF 混合持久化<br>
1、在 AOF 重写阶段创建一个同时包含 RDB 数据和 AOF 数据的 <b><font color="#f44336">AOF 文件</font></b>,其中 <b><font color="#f44336">RDB 数据位于 AOF 文件的开头</font></b>,RDB<b>存储了服务器开始执行重写操作时 </b>Redis 服务器<b>的数据状态</b>(RDB 快照方案)。<br>
2、<b>重写操作执行之后</b>的 Redis 命令(即已存储当前RDB之后的命令),则会<b>继续 append 在 AOF 文件末尾</b>,<b>即 RDB 数据之后</b>(AOF 日志追加方案),一般这部分数据都会比较小<br>
1. Redis 重启的时候,则可以先加载 RDB 的内容,然后再加载 AOF 的日志内容。<br>
哨兵模式
功能描述<br>
监控(Monitoring)<br>
自动故障转移(Automatic failover)<br>
配置提供者(Configuration provider)<br>
通知(Notification)<br>
组成
哨兵节点
数据节点<br>
工作机制<br>
心跳检查<br>
<b><font color="#f44336">每隔 10 秒</font></b>,每个 Sentinel 节点会向已知的主从节点发送 info 命令获取最新的主从架构。
<b><font color="#f44336">每隔 2 秒</font></b>,Sentinel 节点都会向主从节点的 _sentinel_:hello 频道发送自己的信息。
<b><font color="#f44336">每隔一秒</font></b>,哨兵会每个主从节点、Sentinel 节点发送 PING 命令。该定时任务是哨兵心跳机制中的核心,它涉及到 Redis 数据节点的运行状态监控,哨兵领导者的选举等细节操作。当哨兵节点发送 PING 命令后,若超过 down-after-milliseconds 后,当前哨兵节点会认为该节点主观下线。
主观下线<br>
客观下线<br>
注意
Sentinel 选举<br>
故障转移<br>
哨兵机制下的数据丢失<br>
异步复制同步丢失<br>
集群产生脑裂数据丢失<br>
如何保证尽量少的数据丢失<br>
min-slaves-to-write:从节点最小数量,默认0<br>
min-slaves-max-lag:从节点最大延迟,默认10<br>
解释<br>
集群Cluster模式<br>
Cluster 实现原理<br>
集群的组群过程<br>
集群数据分片原理<br>
哈希槽(slots)的划分<br>
均匀分配<br>
使用 <font color="#f44336">cluster addslots</font> 命令来指定<br>
哈希槽(slots)的映射<br>
<b>哈希槽(slots)用来划分数据存储区域,即通过slots定位目标node,真正存储数据的还是redis node</b><br>
数据复制过程和故障转移<br>
数据复制<br>
故障检测<br>
主从故障转移<br>
1、如果只有一个slave节点<br>
2、多个slave节点<br>
3、新的主节点会撤销所有对已下线主节点的slots指派,并将这些slots全部指派给自己<br>
4、新的主节点向集群广播一条PONG消息,这条PONG消息可以让集群中的其他节点立即知道这个节点已经由从节点变成了主节点,并且这个主节点已经接管了原本由已下线节点负责处理的槽。
5、新的主节点开始接收和自己负责处理的槽有关的命令请求,故障转移完成
client 访问 数据集群的过程<br>
定位数据所在节点<br>
1、客户端连接任一实例,获取到slots与实例节点的映射关系,并<font color="#f44336"><b>将该映射关系的信息缓存在本地</b></font><br>
2、将需要访问的redis信息的key,经过CRC16计算后,再对16384<font color="#f44336"><b> 取模得到对应的 Slot 索引</b></font><br>
3、通过slot的位置进一步定位到具体所在的实例,再<b><font color="#f44336">将请求发送到对应的实例上</font></b>
Redis Cluster集群扩缩容<br>
扩容<br>
扩容原理
迁移slot和数据<br>
slot迁移的其他说明<br>
缩容<br>
缩容原理<br>
忘记节点<br>
主从、哨兵、集群优缺点
单机模式
优点
架构简单,部署方便<br>
高性价比<br>
高性能<br>
缺点<br>
不保证数据的可靠性<br>
高性能受限于单核 CPU 的处理能力<br>
主从模式<br>
优点
读写分离,提高效率<br>
数据热备份,提供多个副本<br>
缺点<br>
无法自动故障转移<br>
Master的写的压力难以降低<br>
主节点存储能力受到单机限制<br>
主从数据同步,可能产生部分的性能影响甚至同步风暴。<br>
哨兵模式
优点
对节点进行监控,来完成自动的故障发现与转移<br>
缺点<br>
特别是在主从切换的瞬间存在访问瞬断的情况,等待时间比较长,至少十来秒不可用
哨兵模式只有一个主节点对外提供服务,没法支持很高的并发<br>
单个主节点内存也不宜设置得过大,否则会导致持久化文件过大,影响数据恢复或主从同步的效率。
<b>与主从相比,<font color="#f44336">哨兵仅解决了手动切换主从节点问题</font>,至于其他的问题,基本上仍然存在</b>
集群模式<br>
优点
<b>无中心架构</b><br>
数据按照 slot 存储分布在多个节点,节点间数据共享,可动态调整数据分布。<br>
<b>可扩展性</b>:可线性扩展到 1000 多个节点,节点可动态添加或删除<br>
<b>高可用性</b><br>
缺点<br>
如果主节点A和它的从节点A1都宕机了,那么该集群就无法再提供服务了
发布订阅<br>
组成<br>
频道(channel)<br>
发送者(publisher)<br>
订阅者(subscriber)
实现场景<br>
两种发布/订阅模式<br>
基于频道(Channel)的发布/订阅<br>
SUBSCRIBE:频道订阅<br>
UNSUBSCRIBE:退订频道<br>
PUBLISH:向频道发送消息<br>
基于模式(pattern)的发布/订阅<br>
图解<br>
通配
PSUBSCRIBE:模式订阅<br>
PUBLISH:模式发布<br>
PUNSUBSCRIBE:退订模式<br>
PUBSUB NUMSUB:查看频道的订阅者数量<br>
注意事项<br>
客户端需要及时消费和处理消息<br>
客户端需要支持重连<br>
不建议用于消息可靠性要求高的场景中<br>
布隆过滤器<br>
特性<br>
Bloom命令<br>
底层原理<br>
最佳hash函数数量与错误率的关系<br>
所需存储空间与错误率及容量关系<br>
扩容<br>
扩容逻辑<br>
优缺点<br>
优点
适合大数据场景<br>
节省空间<br>
数据保密<br>
缺点<br>
误判<br>
不可删除<br>
空间利用率不高<br>
容量满时误报率增加<br>
不支持计数<br>
查询速度受错误率影响<br>
分布式锁<br>
分布式锁需要考虑的问题
可重入
锁超时 - 防死锁
防误删
可续期
常见分布式锁方案对比
分布式锁需满足四个条件<br>
Redis分布式锁实现<br>
setnx和expire实现<br>
问题
会出现死锁
锁过期、释放别人的锁<br>
锁被别人释放怎么办?
锁过期时间不好评估怎么办? <font color="#f44336">Redisson</font>
解决方案<br>
<b>死锁</b>:设置过期时间<br>
<b>过期时间评估不好,锁提前过期</b>:守护线程,自动续期<br>
<b>锁被别人释放</b>:锁写入唯一标识,释放锁先检查标识,再释放;<br>
Redlock(红锁)<br>
「主从发生切换」时,这个分布锁会依旧安全吗?<br>
红锁实现
总结<br>
答疑<br>
为什么要在多个实例上加锁?<br>
为什么大多数加锁成功,才算成功?
为什么步骤 3 加锁成功后,还要计算加锁的累计耗时?<br>
为什么释放锁,要操作所有节点?<br>
基于 Zookeeper 的锁安全吗?<br>
Redisson分布式锁<br>
加锁机制<br>
加锁流程<br>
加锁 Lua<br>
<b><font color="#f44336">其中lock()默认是30秒的生存时间</font></b>
锁互斥<br>
watch dog:看门狗自动延期机制<br>
官方介绍<br>
lockWatchdogTimeout(监控锁的看门狗超时,单位:毫秒),默认值:30000<br>
看门狗的时间可以自定义设置<br>
释放锁机制
解锁流程图
广播解锁消息有什么用?<br>
缺点<br>
主从节点下异步同步数据导致锁丢失问题<br>
解决方案:redLock<br>
如何使用<br>
特别注意
线程模型及框架原理
Redis启动流程<br>
阶段<br>
阶段一:基本初始化<br>
阶段二:检查哨兵模式,并检查是否要执行 RDB 检测或 AOF 检测<br>
阶段三:运行参数解析<br>
阶段四:初始化 server<br>
阶段五:执行事件驱动框架<br>
Reactor 模型
两个“三”<br>
三类事件类型<br>
连接事件<br>
写事件<br>
读事件<br>
三个关键角色<br>
reactor<br>
acceptor<br>
handler<br>
事件类型与关键角色 交互关系<br>
客户端和服务器端在交互过程中,<b>不同类请求</b>和 <b>Reactor 模型事件</b>的对应关系
事件驱动框架<br>
事件驱动框架包括了两部分<br>
1、事件初始化<br>
2、事件捕获、分发和处理主循环<br>
事件驱动框架的基本执行过程<br>
Reactor 模型的基本工作机制
Redis 对 Reactor 模型的实现<br>
事件的数据结构定义<br>
以 aeFileEvent 为例<br>
负责事件和 handler 注册的 <b>aeCreateFileEvent </b>函数<br>
initServer过程中调用aeCreateFileEvent
aeCreateFileEvent 如何实现事件和处理函数的注册?<br>
主循环<b>aeMain</b>函数<br>
负责事件<b>捕获与分发</b>的 <b>aeProcessEvents </b>函数<br>
情况一:既没有时间事件,也没有网络事件
情况三:只有普通的时间事件
情况二:有 IO 事件或者有需要紧急处理的时间事件
<b><font color="#f44336">aeApiPoll </font></b>是如何捕获事件呢?<br>
流程图
总结<br>
首先,需要先实现一个<b><font color="#f44336">主循环函数(对应 aeMain)</font></b>,负责一直运行框架。<br>
其次,需<b><font color="#f44336">要编写事件注册函数(对应 aeCreateFileEvent),用来注册监听的事件和事件对应的处理函数</font></b>。只有对事件和处理函数进行了注册,才能在事件发生时调用相应的函数进行处理。
最后,<b><font color="#f44336">需要编写事件监听、分发函数(对应 aeProcessEvents),负责调用操作系统底层函数来捕获网络连接、读、写事件,并分发给不同处理函数进一步处理</font><font color="#000000">。</font></b>
Redis中的事件驱动框架
主循环体结构:<b>aeEventLoop</b>
事件类型
IO 事件<br>
时间事件
aeFiredEvent - <font color="#f44336">并非专门的事件类型</font><br>
源码
aeEventLoop结构 初始化<br>
初始化时间<br>
Redis server启动时initServer()函数中执行<br>
aeCreateEventLoop 函数调用<br>
函数执行步骤<br>
第一步<br>
1、<font color="#f44336"><b>aeCreateEventLoop</b></font>()会创建一个 aeEventLoop 结构体类型的变量 eventLoop
2、给 eventLoop 的成员变量<b><font color="#f44336">分配内存空间</font></b>
3、给 eventLoop 的<b><font color="#f44336">成员变量赋初始值</font></b>
第二步<br>
1、aeCreateEventLoop 函数会调用 aeApiCreate 函数
aeApiCreate 函数就会<b><font color="#f44336">调用 epoll_create 创建 epoll 实例</font></b>,同时会<b><font color="#f44336">创建 epoll_event 结构的数组</font></b>,<b>数组大小等于参数 setsize</b>
通过<b>aeApiState</b>持有<b>epoll</b>对象
第三步
把所有网络 IO 事件对应文件描述符的掩码,初始化为 <b>AE_NONE</b>
两个关键点<br>
IO 事件数组大小就等于setsize,决定了和 Redis server 连接的客户端数量
通过 aeApiCreate 函数创建 epoll_event 结构数组<br>
一个亮点<br>
将fd作为aeFileEvent数组的下标<br>
eventLoop中的aeFileEvent数组持有全部已连接IO时间的fd信息
IO 事件处理<br>
IO 事件分类<br>
可读事件<br>
可写事件<br>
屏障事件<br>
四个组成部分<br>
套接字<br>
I/O多路复用程序<br>
文件事件分派器<br>
事件处理器<br>
<b>aeFileEvent </b>结构体<br>
IO 事件创建:aeCreateFileEvent<br>
其中fd参数
流程<br>
1、获取该描述符关联的 IO 事件
2、调用 <b>aeApiAddEvent </b>函数,添加要监听的事件<br>
<b>首先</b>,获取<font color="#f44336"><b>aeApiState</b></font>变量<b><font color="#f44336">state</font></b><br>
<b>其次</b>,对于要执行的<b><font color="#f44336">操作类型</font></b>的设置
添加操作:EPOLL_CTL_ADD
修改操作:EPOLL_CTL_MOD
<b>最后</b>,创建 <b><font color="#f44336">epoll_event 类型变量</font></b> ee<br>
调用 <b><font color="#f44336">epoll_ctl </font></b>创建监听事件<br>
读写事件创建并注册回调函数<br>
最开始创建的监听事件
1、为服务端server.port端口添加连接事件监听<br>
目的:接受客户端连接
2、注册acceptTcpHandler函数<br>
客户端连接server时,触发<b><font color="#f44336">acceptCommonHandler</font></b>函数<br>
createClient函数调用aeCreateFileEvent,对已连接套接字上(客户端socket fd),<b><font color="#f44336">创建监听事件</font></b>,类型为 <font color="#f44336"><b>AE_READABLE</b></font><br>
注册回调函数是<font color="#f44336"><b>readQueryFromClient</b></font>
读事件处理<br>
流程
写事件处理<br>
主循环<b><font color="#f44336">aeMain</font></b>每次调用 aeProcessEvents 函数前,都会调用 <font color="#f44336"><b>beforeSleep </b></font>函数
<b><font color="#f44336">beforeSleep </font></b>函数调用的 <font color="#f44336"><b>handleClientsWithPendingWrites </b></font>函数<br>
<b><font color="#f44336">aeMain </font></b>函数还会循环调用 aeProcessEvents 函数,来检测已触发的事件(检测<b><font color="#f44336">fired</font></b>列表)
<b>aeProcessEvents</b>伪代码(<b>在aeMain函数中循环调用</b>)<br>
<b><font color="#f44336">aeApiPoll</font></b>函数<br>
源码
processFileEvent函数<br>
源码
IO事件运行图<br>
IO 事件和相应套接字、回调函数的对应关系<br>
注意<br>
<b><font color="#f44336">无论客户端发送的请求是读或写操作</font></b>,对于 server 来说,都是要读取客户端的请求并解析处理。所以,server 在客户端的已连接套接字上<b><font color="#f44336">注册的是可读事件</font></b>。<br>
<b><font color="#f44336">当实例需要向客户端写回数据时</font></b>,实例会在事件驱动框架中<b><font color="#f44336">注册可写事件</font></b>,并<b><font color="#f44336">使用 sendReplyToClient 作为回调函数</font></b>,将缓冲区中数据写回客户端。
Redis多线程<br>
多线程演变<br>
Redis 6.0 之前单线程指的是 Redis 只有一个线程干活么?<br>
单线程事件循环<br>
Redis 3.0前多线程<br>
Redis 4.0多线程<br>
Redis 6.0多线程<br>
一、多线程启动流程<br>
默认关闭多线程<br>
main 入口函数<br>
1、主线程初始化<br>
aeCreateEventLoop 处理<br>
绑定监听服务端口<br>
注册事件回调函数<br>
调用 aeApiAddEvent
2、io 线程启动<br>
源码 InitServerLast->initThreadedIO -> IOThreadMain<br>
IOThreadMain流程图<br>
二、主线程事件循环<br>
aeProcessEvents源码<br>
事件循环处理1:新连接到达<br>
acceptTcpHandler源码<br>
事件循环处理2:用户命令请求到达<br>
readQueryFromClient 代码<br>
推迟客户端的读写操作
四个条件<br>
条件一:全局变量 server 的 io_threads_active 值为 1<br>
条件二:全局变量 server 的 io_threads_do_read 值为 1<br>
条件三:ProcessingEventsWhileBlocked 变量值为 0<br>
条件四:客户端现有标识不能有 CLIENT_MASTER、CLIENT_SLAVE 和 CLIENT_PENDING_READ。
事件循环处理3:epoll_wait 前进行任务处理<br>
beforeSleep<br>
三、主线程 && io 线程处理读请求<br>
主线程分配任务<br>
handleClientsWithPendingReadsUsingThreads源码<br>
主要逻辑分成四步<br>
1、判断是否激活IO多线程、读多线程<br>
2、遍历clients_pending_read列表,并逐个分配到IO线程及主线程<br>
通知IO线程执行<br>
IO子线程处理流程<br>
3、主线程执行io_threads_list 数组 0 号列表元素<br>
读请求处理:readQueryFromClient->processInputBuffer<br>
processCommandAndResetClient(只有主线程能执行)
getCommand(只有主线程能执行)<br>
addReplyBulk(只有主线程能执行)<br>
主线程等待所有 IO 线程完成待读客户端的处理<br>
4、<b><font color="#f44336">主线程</font></b>调用 processCommandAndResetClient 函数执行命<br>
执行过程
写处理结果到发送缓存区<br>
prepareClientToWrite<br>
_addReplyToBuffer<br>
四、主线程 && io 线程配合处理写请求<br>
主线程分配任务<br>
IOThreadMain<br>
写请求处理-writeToClient<br>
不开启多线程写操作的情况<br>
总结
工作过程<br>
总结来说<br>
缺陷<br>
主线程是在处理读、写任务队列的时候还要等待其它的 io 线程处理完才能进入下一步
对多核的利用率并不算高<br>
多线程的无锁设计<br>
redis快?<br>
四个主要原因
C 语言实现<br>
纯内存 I/O<br>
I/O 多路复用
单线程模型<br>
为何选择单线程<br>
避免过多的上下文切换开销<br>
避免同步机制的开销<br>
简单可维护<br>
redis调优
slowlog:慢查询日志<br>
慢查询命令<br>
redis-benchmark<br>
RDB 文件分析
Redis性能问题排查<br>
耗时的操作
如何检测Redis是否变慢?<br>
过期key操作<br>
大量数据插入<br>
大 Key 问题<br>
分析大key<br>
拆分大key<br>
将大 Key 进行分割,拆成几个小的 key-value,使用 multiGet 获取值<br>
内存碎片<br>
查看内存碎片<br>
处理内存碎片<br>
NVM<br>
Redis配置<br>
内存
过期时间的设置<br>
业务层面<br>
0 条评论
下一页