1. 单线程模型
单线程模型执行流程:
- 阻塞等待连接事件并处理Socket读事件
- 解析命令
- 执行命令
- 处理Socket写事件
- 继续循环1步骤
单线程模型指的是2-3步骤只会由单线程同步执行
线程处理情况:
- 网络IO:
- 6.0之前:单线程处理Sokcet的读写
- 6.0+:多线程处理Socket读取和Socket写入响应
- IO多路复用:IO多路复用在2.0引入,单一主线程通过多路复用管理所有客户端连接,并把就绪的Socket给后续流程进行读写
- lazy free:4.0引入,使用异步惰性删除机制释放内存,避免主线程阻塞
- 删除大键:unlink key,异步删除大量元素的hash或list
- 清空数据库:flushdb/flushall async,异步清空整个数据库或所有数据库
- 内存到达上限淘汰:配置
lazyfree-lazy-eviction yes,内存达到maxmemory时淘汰键值对 - 删除过期键:配置
lazyfree-lazy-expire=yes,删除过期key
网络IO多线程实现方式:使用两组生产者+消费者模型实现
- 第一组:
- 生产者:主线程,通过IO多路复用获取可读Socket后,把Socket分配给IO线程池
- 消费者:IO线程池,读取被分配的Socket,解析协议,获取完整命令信息
- 第二组:
- 生产者:IO线程池,各个IO线程把完整命令信息放进全局等待队列
- 消费者:主线程,读取全局等待队列消息,读取后单线程处理命令
读请求和写请求堆积场景:
- 堆积原因:
- 读请求:
- 写入BigKey:一次性写入大量数据或复杂结构
- 主线程阻塞:慢查询或LUA脚本执行时间过长
- 突发请求:突发大量请求
- 写请求:
- 客户端接收缓慢:响应时客户端处理缓慢
- 频繁monitor命令:monitor命令会输出大量数据,挤满输出缓冲区
- 获取大key数据:获取key的数据体积庞大
- 输出缓冲区过小:未设置合理缓冲区大
- 读请求:
- 处理方式:
- 强制关闭连接:输入或输出区挤满后会关闭对应客户端连接
- 预防措施:
- 读请求:
- 避免BigKey:设计优化数据结构,避免单键值过大或集合元素过多
- 启用异步线程:启用相关异步线程,减小主线程性能压力
- 请求控制:避免死循环或频繁调用
- 写请求:
- 合理设置输出缓冲区:响应时客户端处理缓慢
- 避免monitor命令:高流量时期慎用
- 避免大key数据:避免查询大key或一次性返回过多数
- 读请求:
lazy free的核心思想是将耗时且可能阻塞Redis主线程的内存释放操作,交给后台特定的线程异步完成,确保主线程的高性能
lazy free核心实现:
- 数据结构:等待任务队列
- 处理流程:
- 投递任务:主线程需要释放大量内存时,创建任务并投递到对应的任务队列
- 监听消费:专门的bio_lazy_free线程持续监听队列进行消费,根据任务类型进行响应操
- 缺陷:
- 延时性:内存不会立即释放,若内存紧张时异步释放可能跟不上新增数据速度
- 额外消耗:另开线程会额外占用CPU和IO资源
Redis的所有操作都在内存中,一般性能瓶颈在于网络IO和内存带宽,单线程执行命令可以带来以下好处:
- 避免锁竞争:简化了数据结构的实现,彻底消除了线程间竞争锁带来的性能损耗
- 降低上下文切换:减少了多线程环境下CPU间切换带来的开销,提供CPU缓存命中率
- 原子性保证:所有命令执行都是顺序且原子性的,无需考虑并发问题
- 代码简洁和可维护性:避免了多线程编程带来的复杂性
2. IO多路复用
Redis通常部署在Linux上,使用的IO多路复用器是epoll,相对select/poll性能更出众
核心数据结构:
- 红黑树(RB-Tree):
- 数据结构:平衡二叉树
- 节点信息:包含要监控的文件描述符(FD)和事件类型
- 优势:红黑树的插入、删除、查找时间复杂度都是O(logn)
- 就绪链表:
- 数据结构:双向链表
- 节点信息:存放已就绪的FD
- 优势:查询就绪Socket时,只需要检查该链表,无需遍历所有FD,时间复杂度接近O(1)
高效的核心机制:
- 网卡收到数据后,数据被拷贝到接收缓冲区
- 处理完数据包后,触发对应注册FD的回调函数
- 回调函数在红黑树中查找对应节点,并将节点添加到就绪链表
- 主线程阻塞读取就绪链表的Socket
FD信息生命周期:
- accept建立新连接后把FD插入红黑树中
- FD注册回调函数
- 当触发下列操作时,删除红黑树中的FD:
- 客户端主动断开:当客户端调用close()或终止进程时,Redis清理连接资源
- 服务器主动关闭:空闲超时、释放资源或强制关闭连接时触发
- 解析读取Socket失败:协议解析错误、认证失败时
3. 指令处理
3.1 指令解析和执行
统一使用RESP(Redis Serialization Protocol)协议解析参数,随后根据首位参数从命令表中获取对应的实现函数
指令执行:
- 普通指令:
- 支持指令:非事务指令和LUA脚本指令
- 执行方式:立即执行client->cmd->proc(client)
- 错误处理:命令失败立即返回
- 网络开销:一次网络开销
- 事务指令:
- 支持指令:MULTI和EXEC指令
- 特殊处理:MULTI指令开启事务后,中途指令仅查询校验,通过后入队
- 执行方式:接收到EXEC指令后顺序执行队列指令
- 错误处理:事务入队错误时放弃整个事务,运行错误继续执行后续指令
- 网络开销:和指令数量成正比
- LUA脚本:
- 支持指令:EVAL指令
- 特殊处理:需要额外解析语法和内部基础执行指令
- 执行方式:额外声明参数存储结果,执行指令同普通指令
- 错误处理:脚本命令失败后终止,但已执行指令不回滚
- 网络开销:一次网络开销
总流程示意图:
3.2 指令发送和重定向
在Redis中,主节点接收处理读写指令,从节点只接收读指令,接收写指令将会返回对应错误信息
实现方式:
- 主从模式:
- 客户端连接节点:分别配置主从节点,主节点配置在首位
- 写指令处理:必须发给主节点;若发送到从节点会返回仅读错误
- 关键机制与细节:从节点只读,无自动故障转
- 哨兵模式:
- 客户端连接节点:最少可配置一个哨兵,从哨兵获取当前主从节点信息
- 补充:最好配置所有哨兵地址,无论哪个哨兵宕机都可获取主从节点信息
- 写指令处理:应发给主节点;若发给从节点会触发重定向
- 关键机制与细节:哨兵监控主从节点状态,重定向后获取新的主从节点信息
- 重定向信息:-MASTERDOWN,主节点下线但未完成切换时返回,此时没有节点可以处理写指令
- 客户端连接节点:最少可配置一个哨兵,从哨兵获取当前主从节点信息
- 集群模式:
- 客户端连接节点:最少可配置一个任意节点,从节点获取集群信息,客户端需支持集群协议
- 补充:最好配置所有节点地址,这样无论哪个节点宕机都可获取集群信息
- 写指令处理:必须发送给key对应的哈希槽,若发送错误会触发重定向
- 关键机制与细节:数据分片在16384个槽,客户端缓存槽位映射
- 重定向类型:
- MOVED重定向:写指令发送给错误节点,客户端收到后会更新本地槽位映射
- ASK重定向:集群正在分片,会和新的目标节点建立临时连接并先后发送ASKING和原写指令,但不会更新本地槽位映射
- 客户端连接节点:最少可配置一个任意节点,从节点获取集群信息,客户端需支持集群协议
4. 部署方式
部署方式:
- 单机模式:
- 机器要求:1台
- 特点:部署简单
- 适用场景:开发测试或小型应用
- 主从模式:
- 机器要求:2台
- 特点:读写分离,但需手动故障转移
- 适用场景:读多写少,对高可用要求不高的业务
- 哨兵模式:
- 机器要求:3台,生产最好5台(主从+3台哨兵)
- 特点:主从模式基础上引入哨兵,支持自动故障转移
- 适用场景:支持高可用,但数据量没达到要求分片的业务
- 集群模式:
- 机器要求:6台(3主3从)
- 特点:使用哈希槽数据分片存储,自动故障转移
- 适用场景:大规模数据、高并发和高可用的业务
从性价比角度考虑:
- 单机模式:开发测试
- 主从模式:满足大部分业务
- 哨兵模式:不推荐,机器少了和主从模式拉不开差距,机器多了不如集群模式
- 集群模式:性能要求较高业务
5. 数据同步
数据同步是主从节点间的数据一致性保证,同步机制和部署方式无关
核心概念:
- 复制ID:
- 存储位置:主、从节点
- 作用:标识当前主从数据版本
- 特点:主节点重启或晋升后会生成新ID,并保存上一个复制ID
- 复制偏移量:
- 存储位置:主、从节点
- 作用:判断主从数据一致性
- 特点:主节点偏移量随发送字节增长,从节点偏移量随接收字节增长
- 复制积压缓冲区:
- 存储位置:主节点
- 作用:缓存近期写指令,方便从节点同步近期部分数据
- 特点:固定大小环形队列(默认1MB),写满后新数据会覆盖旧数据
- 复制缓冲区:
- 存储位置:主节点,每个从节点都会维护一个
- 作用:全量同步期间临时存储新的写指令
- 特点:仅在全量同步时使用,完成全量同步后将会把积累的指令发送给从节点
数据同步核心机制:数据同步机制由Redis自动管理,非显式选择增量或全量同步
- 触发机制:从节点发送PSYNC请求,主节点对比复制ID和复制偏移量
- 增量同步:
- 条件:主从节点的复制ID相同,且复制偏移量在复制积压缓冲区范围内
- 同步内容:复制积压缓冲区中在复制偏移量之后的所有写指令
- 全量同步:
- 条件:满足其一即可触发
- 从节点首次连接主节点
- 主从节点的复制ID不同
- 复制偏移量未在复制积压缓冲区范围内
- 同步内容
- RDB快照文件:为数据差异的从节点执行bgsave生成RDB快照并直接发送
- 增量写指令:生成RDB快照期间,主节点接收写指令后放入从节点对应的复制缓冲区,发送完RDB快照再把复制缓冲区的写指令发送给从节点
- 条件:满足其一即可触发
6. 持久化
持久化是节点数据安全可靠的保证
持久化和数据同步差异:
- 持久化机制:专注于单个节点的数据持久化,偏重数据安全可靠
- 数据同步机制:专注于主从节点间的数据同步,偏重数据一致性
核心概念:
- Copy-on-Write(COW,写时复制):RDB模式下,生成RDB文件时主进程和子进程会共享内存数据页,主进程接收写指令执行完后的结果子进程可以立即感知
- AOF缓冲区:主进程接收到写指令后写入缓冲区,AOF持久化时把缓冲区的指令写入AOF文件
- AOF重写缓冲区:在进行AOF重写时,写指令会被AOF重写缓冲区,当完成AOF重写后把AOF重写缓冲区指令追加到新的AOF文件末尾
实现机制:
- 全量模式(RDB):特定时间点生成整个数据集的二进制快照保存信息
- 触发机制:
- 自动触发:配置save规则,如save 900 1为900秒内有1个键被更改则触发
- 手动触发:执行save(阻塞主进程,慎用)或bgsave(异步子进程,推荐)指令
- 优点:
- 文件体积小:二进制文件紧凑,便于传输和备份
- 恢复速度快:直接载入文件,是和灾难恢复和快速重启
- 对主进程影响较小:持久化由子进程完成,fork时可能阻塞
- 缺点:
- 数据丢失:快照之间相隔较久,会因宕机丢失
- fork阻塞:数据集很大时,fork子进程耗时较长,导致内存占用上升
- 触发机制:
- 增量模式(AOF):记录每一个写指令到日志文件
- 工作流程:
- 写指令追加到AOF缓冲区
- 根据配置策略将缓冲区数据同步到磁盘
- always:每次写命令都同步,数据安全型,性能最低
- everysec(默认):每秒同步一次,数据安全和性能平衡型
- no:由操作系统决定同步时机,性能型,但会丢失较多数
- 优点:
- 数据可靠性高:使用默认策略,通常最多丢失1秒数据
- 可读性相对高:AOF文件以文本格式记录,便于人工查看
- 缺点:
- 文件体积较大:记录每条指令,即使重写后仍比RDB文件大
- 数据恢复速度较慢:需要逐条执行指令,数据集大时恢复耗时
- 写入性能受策略影响:always策略对性能影响较大
- AOF重写(rewriting):fork子进程,根据当前数据库状态生成新的最小命令集AOF文件
- 目的:解决AOF文件膨胀问题
- 触发:
- 自动触发:根据时间配置
auto-aof-rewrite-percentage和根据大小配置auto-aof-rewrite-min-size - 手动触发:发送bgrewriteaof指令触发
- 自动触发:根据时间配置
- 工作流程:
- 混合模式(RDB+AOF):结合RDB和AOF的优点
- 工作原理:
- 触发AOF重写时,不再生成纯命令格式的AOF文件
- 子进程先以RDB格式将内存中全量数据写入新AOF文件
- 将重写缓冲区的增量写命令(AOF格式)追加到AOF文件末尾
- 最终生成的AOF文件以RDB为头,AOF为尾的混合体
- 优点:
- 快速恢复:恢复时先加载RDB部分快速恢复大部分数据,再执行增量AOF命令
- 数据丢失少:增量部分保证了两次RDB快照之间的数据安全性,丢失风险极低
- 特点:
- 混合模式在AOF重写时生效,正常的AOF流程不变
- 新的AOF文件人工可读性变差
- 生成RDB快照时依然会有COW机制保证主子进程内存数据一致
- AOF重写时的数据一致性保证:
- AOF文件情况:AOF重写时会有两个AOF文件,原AOF文件A,新AOF文件B
- AOF缓冲区:服务于原AOF文件A,并按策略持久化到原AOF文件A,以保证重写时宕机使用原AOF文件A恢复时不会丢失数
- AOF重写缓冲区:临时记录增量写指令,服务于新AOF文件B,以保证完成重写后使用新AOF文件B替换原AOF文件A的数据完整性
- 工作原理:
7. 高可用
Redis的哨兵模式和集群模式可支持高可用
7.1 哨兵高可用
哨兵模式高可用理论最佳是5台机器:1主1从3哨兵
推荐哨兵数量:大于等于3且为奇数
核心概念:
- 投票请求:
SENTINEL is-master-down-by-addr命令 - 主观下线:哨兵通过向主节点发送PING命令,若在
down-after-milliseconds(默认30秒)内未收到回复,该单个哨兵会标记主节点主观下线 - 客观下线:当某个哨兵认为主节点主观下线后,会向哨兵集群发送投票,若赞成票≥quorum值,主节点标记为客观下线
- quorum值:判断主节点是否客观下线所需的最小哨兵同一数量,通常为
N/2 + 1 - 任期号(epoch):单调递增的计数器,代表哨兵当前选举轮次
- 候选者:当哨兵把主节点标注为主观下线后,会向其它哨兵发送投票请求,只要发送了投票请求,即当作获得候选者资格
哨兵leader选举:
- 必要性:主节点被判定为客观下线后,需要有一个leader来负责执行故障转移
- leader有效性:每次需要故障转移时都会重新选举出新的leader,好处有二:
- 负载均衡:每次故障转移都使用不同的哨兵当leader,避免单一哨兵处于高负载状态成为瓶颈
- 简化设计:哨兵本就用于监控主从节点,如果又要维护哨兵中leader的身份,得不偿失
- leader诞生条件:两个条件都须满足
- 超过半数:获得赞成票>总哨兵数/2
- quorum值:获得票数≥quorum值
- Raft算法:触发主观下线后,用以从哨兵中选举出leader
- 投票判定:
- 先到先得,先接收到A哨兵的投票请求,就会把票投给A哨兵
- 若同时到达,则随机投给其中一个
- 选举轮次失败:当同一个epoch投票未产生leader,各个哨兵会等待随机时间,随后发起新一轮选举,直到成功选举
- 算法流程:
- 哨兵检测到主节点主观下线,向其它哨兵发送投票请求,同时投自己一票,成为候选者
- 哨兵接收到其它哨兵的投票请求时,根据投票判定判断谁能当leader
- 当满足leader诞生条件时当选新的leader
- 投票判定:
哨兵变动:
- 启动哨兵:通过读取配置文件获得初始主节点信息,并从主节点获取所有从节点信息,并持久化到本地配置文件
- 新增哨兵:配置自动发现并监控指定主节点,其它哨兵通过主节点得知新哨兵节点
- 删除哨兵:哨兵通过PING命令探测其它哨兵是否下线,若下线总有效节点减少
哨兵leader推举主节点条件:
- 排除不健康的节点:过滤掉本身已经下线或经常断线的从节点
- 优先级:检查从节点的
replica-priority配置值,数值越小,优先级越高 - 复制偏移量:若优先级相同,选择复制偏移量最大的从节点(偏移量越说明数据越新,和原主节点差异最小)
- 运行ID(Run ID):以上条件都相同,选择运行ID最小的,Run ID是启动时自动生成的唯一标识
故障转移流程:
- 筛选主节点:根据推举主节点条件筛选出最适合当选新主节点的从节点
- 推举主节点:
- 哨兵leader向从节点发送
SLAVEOF NO ONE命令 - 从节点停止从旧主节点同步数据,将自己转变为主节点
- 哨兵leader以每秒1次的频率发送INFO命令,直到确认其转变为主节点
- 哨兵leader向从节点发送
- 配置从节点:
- 一旦确定新主节点,会向其它从节点发送
SLAVEOF <ip> <port>命令 - 接收此命令的从节点解除和旧主节点的数据同步关系,开始从新主节点同步数据
- 监控从节点正确切换为和新主节点同步数据
- 一旦确定新主节点,会向其它从节点发送
- 降级旧主节点:如果旧主节点重新上线,向其发送
SLAVEOF <ip> <port>命令,旧主节点降级为从节点并从新主节点同步数据 - 更新配置和通知:
1.通过发布/订阅机制向所有监听其频道的客户端和哨兵发送切换主节点消息,以方便客户端和其它哨兵更新主从信息
2. 哨兵把主从节点信息持久化到自己的配置文件中,以方便重启后依然知道主从节点
7.2 集群高可用
集群模式理论高可用机器要求是至少3主3从:3主为满足选举的最小节点,3从则是每个主节点最少的备份节点
相比哨兵模式,其不仅支持高可用,还支持高扩展
核心概念:
- 集群心跳超时配置:
cluster-node-timeout,默认15秒 - 心跳信息:会向集群内所有主从节点发送PING请求,所有节点收到后响应PONG,用于检测集群节点是否存活
- 周期性随机选择:每个节点每秒执行10次定时任务(每检测100ms一次),每次从集群列表挑选5个节点,并向该5个节点中最久没发送PING请求或最早收到PONG回复的节点发送一条PING消息
- 智能补充检测:每100ms扫描本地节点列表,若某节点最后一次收到PONG回复时间间隔超过心跳超时配置的一半,立即发送一条PING消息
- 主观下线(PFAIL):若节点A向节点B发送了PING消息,节点B在心跳超时配置内未响应,则节点A标记节点B为PFAIL(Possible Failure)
- 参与PFAIL标记的节点:所有主、从节点
- 能被标记为PFAIL节点:所有主、从节点
- 客观下线(FAIL):当节点A标记节点B为PFAIL,会向其它节点传播,若集群内其它节点在一定时间内都将节点B标记为PFAIL,则节点B被所有节点标记为FAIL
- 参与FAIL标记的节点:所有主节点
- 能被标记为FAIL节点:所有主、从节点
- 被标记后果:主节点被标记为FAIL触发故障转移;从节点被标记减少其主节点副本冗余数量
- 能否恢复正常:节点PING/PONG响应正常且是从节点
- epoch:集群模式下有两种:
- currentEpoch:集群全局选举轮次,每个节点都有,在故障转移发起选举和收到更大epoch时增加或更新,最终趋于一致
- configEpoch:主节点独立维护,不同主节点值会不一样,在主节点槽所有权变更时修改为currentEpoch相同值
节点变动:
- 新增节点:执行CLUSTER MEET命令和集群内任一一个现有节点建立连接
- 主节点:需要进行分片,将哈希槽从其他主节点迁移到新的主节点,需要借助工具
- 从节点:指定进行数据同步的主节点,从主节点拉取同步数据
- 删除节点:执行CLUSTER DEL-NODE删除节点
- 主节点:需要完成槽迁移,使用
redis-cli --cluster rebalance自动平哈希槽 - 从节点:直接删除即可
- 主节点:需要完成槽迁移,使用
- 节点持久化:节点会使用Gossip协议自动拉取集群内所有节点信息,并持久化到配置文件中
从节点晋升主节点条件判断顺序:
- 筛选健康从节点:若从节点和主节点的断开时间过长,超过了
cluster-node-timeout*cluster-slave-validity-factor阈值,将会失去参选资格 - 优先级:比较从节点的配置
slave-priority,值越小,优先级越高 - 复制偏移量:比较从节点的复制偏移量,偏移量越大,数据越新,优先级越高
- 选举轮次(epoch):以上条件相同,选举轮次越大,优先级越高
- 节点运行ID(Run ID):以上条件相同,运行ID越小,优先级越高
故障转移流程:
- 从节点发起投票请求:当从节点对应的主节点下线后,从节点会判断自己是否有选举资格,有则将currentEpoch+1,并向集群所有主节点广播
- 主节点投票:使用晋升条件依次判断,投给最合适的从节点
- 投票裁决:
- 成功当选:从节点收到超过半数主节点的赞成票
N/2+1 - 投票失败:若一个epoch中无法诞生新主节点,重新进入下一轮投票
- 成功当选:从节点收到超过半数主节点的赞成票
- 从节点晋升主节点:从节点执行
SLAVEOF NO ONE命令,转变为主节点 - 接管哈希槽:将旧主节点的哈希槽指派给自己
- 广播通知状态更新:向集群广播一条PONG消息,其它节点接收到后会更新本地集群状态信息,消息内容:
- 通知自己晋升为主节点
- 负责的哈希槽映射关系
- 当前主从最新的configEpoch
- 处理请求:最终开始处理自己负责的哈希槽相关指令

674

被折叠的 条评论
为什么被折叠?



