Redis原理
Redis内存模型
redisServer
public class redisServer {
int dbnum;// 当前redis节点内数据库数量,默认16
redisDb[] db;// 数组,保存数据库信息
redisClient clients;// 链表,保存客户端信息
// serverCron函数维护的属性
Date unixtime;// 秒级别时间戳
long mstime;// 毫秒级别时间戳
Date lruclock;// LRU时钟,每十秒更新一次
long ops_sec_samples;// Redis server每秒执行命令次数
long stat_peak_memory;// Redis server内存峰值记录
int shutdown_asap;// Redis server运行状态 1关闭 0运行
int cronloops;// serverCron函数计数器
// 持久化相关
String rdb_child_pid;// 执行BGSAVE子进程ID,-1表示未执行
String aof_child_pid;// 执行BGREWRITEAOF子进程ID,-1表示未执行
long dirty;// 修改计数器
Date lastsave;// 上次BGSAVE时间
sdshdr aof_buf;// AOF缓冲区
// 慢查询相关
long slowlog_entry_id;// 下一条慢查询日志ID
Object slowlog;// 慢查询日志链表
long slowlog_log_slower_than;// 超出该属性值则为慢查询,单位微秒
long slowlog_max_len;// 慢查询日志保存数量
}
redisDb
public class redisDb{
dict dict;// 保存键值对
dict expires;// 保存设置过期时间的键和过期时间
dict watched_keys;// 保存被WATCH监视的键
}
redisClient
public class redisClient{
redisDb db;// 当前客户端正在使用的数据库
sdshdr querybuf;// 输入缓冲区
String[] argv;// 命令与命令参数数组
int argc;// argv长度
sdshdr buf;// 输出缓冲区
int bufpos;// buf已使用长度
int authenticated;// 0未通过身份验证 1通过身份验证
}
Redis数据结构
Redis运行机制
Redis server初始化
- 实例化redisServer对象
- 根据用户指定参数和配置文件初始化redisServer对象属性
- 初始化redisServer对象其他属性
- 创建常量:“OK"字符串和"1”-"10000"字符串
- 为serverCron创建时间事件
- 加载持久化文件(AOF或RDB)
- 开始执行时间事件
第六步加载持久化文件流程图
Redis client发送请求
- 将操作命令根据RESP协议进行封装
- 通过套接字发送给Redis server
Redis server接收请求
- 通过套接字接收请求内容,保存至redisClient.querybuf(输入缓冲区)
- 解析请求内容,保存至redisClient.argv(操作命令与操作命令参数数组)redisClient.argc(argv长度)
- 调用操作命令执行器
Redis server处理请求
- 根据操作命令去命令表查询操作命令对应的命令函数(redisCommand)
- 根据redisClient.argc和redisCommand.arity验证操作命令参数个数是否正确
- 根据redisClient.authenticated验证客户端是否通过身份验证
- 调用命令函数
- 将处理结果保存至redisClient.buf(输出缓冲区)
- 进行后续处理(慢查询日志 & redisCommand计数+1 & AOF & 同步)
- 将处理结果发送给Redis client
Redis事件
文件事件
套接字:socket
IO多路复用程序:Redis底层使用epoll
文件事件分派器:事件执行者
事件处理器:连接应答处理器、命令请求处理器和命令回复处理器
文件事件处理流程
- 套接字准备好执行连接应答、写入、读取、关闭等操作时,会产生一个文件事件
- IO多路复用程序监听多个套接字,把产生事件的套接字放在一个队列里
- IO多路复用程序有序推送套接字给文件事件分派器
- 文件事件分派器根据事件类型,选择事件处理器调用函数
时间事件serverCron函数
- 更新时间戳redisServer.unixtime和redisServer.mstime
- 更新LRU时钟redisServer.lruclock
- 更新Redis server每秒执行命令次数redisServer.ops_sec_samples
- 更新Redis server内存峰值记录redisServer.stat_peak_memory
- 处理SIGTERM信号,接收到SIGTERM信号把redisServer.shutdown_asap设置为1
- 检查客户端资源
- 检查数据库资源
- 检查持久化操作运行状态
- 如果开启AOF持久化,将AOF缓冲区内容写入AOF
- redisServer.cronloops+1
Redis持久化
RDB
触发条件
手动触发(save命令和bgsave命令)
自动触发(save m n)(主从复制)(shutdown命令)
- save命令会阻塞Redis server,直至RDB文件创建完成(基本弃用)
- bgsave命令会创建子进程来生成RDB文件,创建子进程过程中父进程阻塞
- save m n指m秒发生n次操作,会自动触发bgsave;根据redisServer.dirty和redisServer.lastsave属性进行判断,由时间事件serverCron负责触发
- 主从复制场景下,从节点执行全量复制,主节点会执行bgsave命令,将RDB文件发送给从节点
- shutdown命令会自动生成RDB文件,然后再结束进程
AOF
开启AOF持久化:appendonly yes
AOF执行流程分为三步:命令追加+文件写入+文件重写
- 命令追加:将执行成功的修改操作写入redisServer.aof_buf
- 文件写入:根据策略将缓冲区数据写入磁盘
always:缓冲区有数据就写入磁盘
no:等待操作系统通过write命令调用,通常为30秒一次
everysec:等待操作系统通过fsync命令调用,每秒一次(默认策略) - 文件重写:将Redis内的数据转换成命令,写入新的AOF文件
文件重写触发条件
手动触发(bgrewriteaof命令)
自动触发(AOF文件超过64MB 并且 新AOF文件大于原AOF文件)
为什么AOF最多可能丢失2秒的数据
Redis会记录上次fsync命令成功的时间,如果不到2秒,不触发fsync;如果超过2秒,则阻塞进行fsync。因此在触发fsync之前突然宕机可能会丢失2秒数据。
Redis4.0混合持久化
开启混合持久化:aof-use-rdb-preamble yes
Redis5.0默认开启混合持久化
混合持久化执行流程
- 手动/自动触发bgrewriteaof命令
- 主进程fork子进程,fork过程中主进程阻塞
- 子进程将全量数据以RDB格式写入AOF文件,主进程将操作命令写入AOF缓冲区和AOF重写缓冲区
- 子进程通知主进程,主进程将AOF重写缓冲区数据以AOF格式写入AOF文件
- 主进程将新AOF文件替换原AOF文件(AOF文件前半段是RDB格式数据,后半段是AOF格式命令)
Redis高可用
主从复制模式
优点
读写分离:主节点写,从节点读
故障恢复:主节点宕机,将从节点升级为主节点
缺点
主/从节点故障恢复需要人工干预
写操作无法负载均衡
连接建立阶段
- 从节点masterhost记录主节点ip,masterport记录主节点port
- 从节点发送slaveof命令给主节点,主节点返回OK
- 从节点与主节点建立socket连接,从节点socket用于接收RDB文件以及其他命令,主节点socket保存在redisServer.clients
- 从节点发送ping命令给主节点,主节点返回pong
- 从节点发送auth命令给主节点进行身份验证
- 从节点发送端口号给主节点,主节点后续会将数据发送至该端口
数据同步阶段
- 从节点发送psync命令给主节点,主节点判断全量复制或者部分复制
命令传播阶段
- 主节点操作命令执行成功后,发送操作命令给从节点
- 主从节点心跳机制和REPLCONF ACK机制
repl-disable-tcp-nodelay yes:合并操作命令,40ms发送一次
repl-disable-tcp-nodelay no:每次操作命令发送一次
心跳机制:主节点每10秒发送ping命令给从节点
REPLCONF ACK机制:从节点每秒发送REPLCONF ACK命令给主节点,维护offset属性
哨兵模式
优点
自动实现主节点故障恢复
缺点
从节点故障恢复仍需要人工干预
写操作无法负载均衡
实现原理
每个哨兵节点维护了三个定时任务
- 向主节点发送info命令获取最新主从结构
- 通过发布订阅获取其他哨兵节点信息
- 向其他节点发送ping命令进行心跳检测
心跳检测过程中,主节点没有回复,哨兵节点将主节点主观下线,并通过sentinel is-master-down-by-addr命令询问其他哨兵节点主节点状态,如果判断主观下线的哨兵节点到达一定数量,则对该主节点进行客观下线,开始进行选举。
选择领导者哨兵节点算法:Raft算法,先到先得。
选择主节点算法:
- 过滤掉不健康的从节点
- 选择优先级最高的从节点
- 若优先级无法区分,选择offset最大的从节点
- 若offset无法区分,选择runid最小的从节点
集群模式
优点
解决写操作无法负载均衡的问题
实现原理
集群模式将16384个槽分布在各个主节点上,数据通过数据分区方案落在各个槽中。
数据分区方案
- 哈希取余分区
- 一致性哈希分区
- 虚拟节点一致性哈希分区
集群成员
- 数据节点(主节点和从节点)
- 哨兵节点
主节点读写,从节点只负责备份数据
每个节点维护2个端口 - 普通端口:用户提供服务
- 集群端口(普通端口+10000):用于集群各个节点通信
增加节点
- 启动节点
- 节点握手
- 迁移槽
- 指定主从关系
减少节点
- 迁移槽
- 节点下线
故障转移
哨兵节点识别主节点客观下线,由其他主节点投票选一个从节点成为主节点