REDIS
多机数据库
复制
- 旧版复制
- RDB全量复制(从服务器向主服务器发送SYNC指令,主服务器生成RDB镜象同步至从服务器)
- 命令增量同步
- 新版复制(REDIS2.8)
- 完整重同步
- 与旧版全量相同
- 部分重同步
- 基于OFFSET实现,主从服务器各维护各自的OFFSET,不一致时进行同步
- 队列每一个OFFSET对应一个字节
- 复制积压缓冲区
- 缓存一定长度的指令(队列,默认大小1MB)
- 当从服务器的OFFSET在缓冲区里,则从缓冲队列中取数据,否则执行全量同步
- 执行步骤
- 设置主服务器IP与端口
- SLAVEOF
- 与主服务器建立SOCKET连接
- 发送PING指令
- 如果等待主服务器的PONG回应出现了超时或异常,则断开连接并重新创建SOCKET
- 如果收到PONG则继续
- 进行连接AUTH验证
- 发送从服务器监听PORT号
- 用于主服务器打印INFO REPLICATION
- 执行PSYNC
- 全量
- 部分
- 开始命令传播
- 心跳检测
- 主服务器每秒发送REPLCONF ACK指令
- 检测连接状态
- 记录上一次收珐从服务器回应到现在的时间
- 在从服务器数目小于指定配置值或有从服务器上一次REPLCONF ACK的回应时间到现在时间大于指定配置时间时终止客户端的REDIS的写操作
- 检测同步丢失
- 当从服务器REPLCONF ACK所回应的OFFSET与主服务器的OFFSET不一致时,说明同步有丢包,此时主服务器进行补发数据
- 如果丢失过多在积压缓冲区中找不到需要同步的指令,此时该怎么办?
Sentinel
- 启动步骤
- 初始化服务器
- 支持指令与正常服务器不同
- 不载入RDB、AOF文件
- 初始化Sentinel状态
- sentinelState结构体初始化
- Sentinel master属性
- master为dict,value为sentinelRedisInstance结构体指针,包括服务器name ip port config_epoch ,判断多少秒无响应即认定为下线的down_after_period, 判断下线所需投票数量quorum
- master节点的设置是在配置文件存放
- 向主服务器建立连接
- 命令连接
- 向服务器发送指令
- 订阅连接
- 传递SENTINELHELLO
- 获取主服务器信息
- 每秒发送一条INFO指令,获取MASTER及其SLAVE信息
- 获取从服务器信息
- 每10秒发送一条INFO指令,获取SLAVE信息及对应MASTER信息
- sentinel hello
- 通过指令连接发送sentinel hello
- 通过订阅连接接收HELLO信息
- 其它sentinel可通过订阅的频道获取源SENTINEL HELLO信息里的主服务器信息以及源SENTINEL本身的服务器信息
- sentinel之间通过HELLO频道的信息,建立SENTINEL之间的命令连接
- 主服务器FAILOVER
- 检测主观下线状态
- 每秒发送PING指令,若SENTINEL服务器在down_after_milliseconds内没有收到PONG、MASTERDOWN、LOADING回复,则认定服务器下线,置flags的SRI_S_DOWN标志位为1
- 检测客观下线状态
- 主观检测下线后,向其它sentinel询问is-master-down-by-addr获取其它sentinel是否认为主服务器已下线,如果数量足够,则置SRI_O_DOWN标志位为1
- 选举领头Sentinel(该Sentinel对下线的主服务器进行操作)
- failover
- 向选中的从服务器发送SLAVEOF no one
- 定时向其发送INFO指令查询其是否变为MASTER
- 变为MASTER后,向其它SLAVE 发送新的SLAVEOF指令
- 将旧的已经上线的MASTER设为SLAVE
集群
- 节点
- 通过CLUSTER MEET命令添加节点到另一个节点
- A节点添加B节点,在A节点创建新的clusterNode结构体
- A节点向B节点发送MEET指令,B节点创建A节点的clusterNode结构体,回应PONG
- A节点向B节发送PING,B节点回应PONG
- A节点通过Gossip协议向集群广播B节点的信息,其它节点与B节点握手
- 集群数据结构
- clusterNode
- 保存节点创建时间、名字、配置纪元configEpoch、IP、port,对应clusterLink链接
- clusterState
- 集群所处状态(上下线)、myself(clusterNode指针)、集群配置纪元currentEpoch、nodes(指向集群各节的字典)、size(集群当前分配槽位数)
- clusterLink
- 创建时间、socket fd、发送缓冲区、接收缓冲区、node(指向对应clusterNode,1 个clusterNode对应1个clusterNode)
- 槽指派
- 所有SLOT均指派完毕后,cluster state转变为OK
- clusterNode结构体
- slots数组保存当前NODE负责哪些SLOT(BITARRAY)
- 本机NODE会广播到集群其它节点,每台NODE均会保存所有NODE处理SLOT
- clusterState slot数组保存各slot到clusterNode的映射,客户端访问任意KEY可由该数组获取存在哪台NODE,当KEY不在本机时,向客户端发送MOVED回应错误指示正确NODE
- clusterState slots_to_keys使用跳表保存《键,SLOT》其中SCORE为SLOT值,通过该数据结构可以快速获取某个SLOT所具有的键数量。
- 迁移
- 由redis-trib管理软件完成
- 迁移实现原理
- 1.CLUSTER SETSLOT IMPORTING 设置目标服务器的clusterState结构体的clusterNode* importing_slots_from[16384]数组对应SLOT为源泉NODE地址
- 2.CLUSTER SETSLOT MIGRATING 设置源服务器的clusterState结构体的clusterNode* migrating_slots_to[16384]数组对应SLOT为目标NODE地址
- 3.CLUSTER GETKEYSINSLOT 获取对应SLOT的最多count个的键名
- 4.对于每一键名,redis-trib向源服务器发送MIGRATE指令迁移指定KEY
- 5.所有KEY迁移完成后,将任意一节点发送CLUSTER SETSLOT通知迁移SLOT转移
- ASK错误(客户端访问正在迁移的键)
- 如果键在源服务器则直接返回键VALUE
- 如果键不在该服务器上,源服务器会查看该键KEY对应SLOT是否在迁移,如果不在迁移,则MOVED;如果在迁移,返回ASK错误(包含目标服务器的SLOT IP PORT)
- 客户端与目标服务器握手
- 客户端发送ASKING指令指示客户端是被迁移转过来的(如果不发送ASKING指令直接GETKEY,由于迁移还未完成,SLOT信息配置仍指示记录在源服务器,则会回应MOVED)
- 客户端向目标服务器发送GETKEY
- 目标服务器回应对应指令
- 复制与故障转移
- 从节点复制
- 向从节点发送CLUSTER REPLICATE 指令,收到后
- 设置clusterNode 的slaveof结构体指向主节点
- 设置clusterState.myself.flags属性为REDIS_NODE_SLAVE
- 向集群广播该信息,集群其它节点会更新对应主节点的clusterNode结构体的numslaves与slaves列表
- 故障转移
- 检测故障
- 本节点PING目标节点不通,在clusterNode中标记目标节点疑似下线PFAIL
- 当某节点收到半数以上的PFAIL信息,则标记目标节点FAIL,同时向集群广播FAIL,其余节点也标记为FAIL
- 实现转移(主节点失效,从节点失效不考虑)
- 从所有从节点中依据算法选中一个节点(Raft)
- 配置纪元加1
- 失效主节点的从节点(发现主节点失效的节点)向集群发送CLUSTERMSG_TYPE_FAILOVER_AUTH_REQUEST消息
- 具有槽位分配的主节点在没有投票(每个配置纪元只能投一票)时,回应节点CLUSTERMSG_TYPE_FAILOVER_AUTH_ACK消息
- 参与选举的从节点对ACK信息进行计数,选举超过半数N/2+1票的节点作为胜出。若一轮投票下来没有超过半数的节点,则重新选举
- 执行SLAVEOF noone指令
- 撤销失效主节点的槽指派并指向自己
- 向集群PONG一条消息标识其已经成功变为主节点
单机数据库
redisServer结构体
- db数组
- db大小默认16
redisClient结构体
- 通过改变db指针值切换当前redisClient所指向的db
redis键空间
- 采用dict字典存储
过期键
- 存储
- 删除策略
- 定时删除
- 懒删除
- 定期删除
- RDB
- 生成时只存储未过期键
- 加载
- 主服务器只加载未过期
- 从服务器加载所有键
- AOF
- 只会在惰性删除与定期删除时才追加DEL日志
- 重写时不会将已过期的键写入AOF日志(AOF增量日志,这里是否有问题?)
- 复制
- 在复制模式下,主服务器向从服务器同步DEL指令,从服务器不主动动删除键,如果客户端向从服务器请求键,会拿到该键的值
通知
- keyspace
- keyevent
RDB
- 在AOF关闭时采用RDB恢复
- SAVE
- 阻塞
- BGSAVE
- 创建新的进程保存
- 如果正在执行BGREWRITEAOF,则忽略BGSAVE;反之,BGREWRITEAOF会放在BGSAVE之后
- 可设置多少秒内有多少次修改即触发BGSAVE,借助redisServer->dirty计数器实现修改计数
- serverCron()每次检查BGSAVE触发条件是否满足,满足即立即执行
- RDB文件结构
AOF
- 实现方式:在aof_buf缓冲区追加写指令,根据appendfsync设置flush磁盘
- appendfsync
- always
- everysec
- no
- 注:fdatasync()相比fsync()只刷写文件数据,不改写文件元数据如文件大小,时间
- AOF恢复
- 建立cleintId为-1的客户端,读取AOF日志文件模拟客户端恢复
- AOF重写
- 目的:缩减AOF文件日志大小,减少恢复时间
- 方式:遍历数据库所有键,对每个键生成独立的set语句指令(如果指令大小超过了客户端输入缓冲区,则进行指令拆分)
- (注意这里的重写是对全部的键进行重写,需要访问所有键,并不是 RDB AOF混合持久化)
- AOF后台重写
- 创建新的子进程,将AOF缓冲区的内容写入AOF文件
- AOF重写缓冲区
- 解决AOF缓冲区向AOF文件写入时,有新的写指令写入,导致AOF文件与实际库数据不一致
- 在AOF缓冲区写向AOF文件写入的子进程写入完毕后,将重写缓冲区的内容写入AOF文件
事件
- 文件事件
- 采用多路复用I/O模型通知文件事件分派器
- 可在编译时选择使用select epoll evport kqueue,默认为epoll
- 包含AE_READABLE和AE_WRITEABLE两种客户端事件,如果SOCKET同时具有READ和WRITE事件,则先处理READ事件
- network.c包含连接应答处理器、命令请求处理器、信念回复处理器
- 时间事件
- 定时事件:某时刻需要完成的事件
- (目前没有使用定时事件)
- 定期事件:每隔一段时间执行的事件
- 运行方式:维护一个无序链表,其中每一项元素包含事件ID,预计执行时间戳,回调函数
- serverCron()函数会定期地将运行需求插入该链表
Client
- 属性(保存在服务器端的属性)
- fd(-1为伪客户端)
- name(标识客户端名称,由客户端执行命令设置)
- 标志FLAG(记录角色与状态)
- 输入缓冲区
- argc argv(解析命令参数后)
- 输出缓冲区
- 固定大小(用于回复短信息)
- 使用redisClient->buf
- 可变大小(用于回复较长的信息)
- 使用redisClient->reply,其中reply为list*类型,指向字符链表
- 身份认证标志位
- 时间
- 连接时间
- 上一次交互时间
- 输出缓冲区第一次到达软性限制时间
- 客户端的创建、关闭
- 创建:将redisClient结构体记录在redisServer->clients链表中
- 关闭客户端场景
- 客户端进程退出
- 客户端命令不符协议
- 客户端成为client kill目标
- 客户端空转超过一定timeout时间(在客户端是REDIS_MASTER REDIS_SLAVE时,若被BLPOP指令阻塞,或正在执行订阅指令时,忽略timeout)
- 命令长度超出输入缓冲区
- 超出输出缓冲区大小
- 超过硬性限制大小直接关闭客户端
- 超时软性限制大小软性限制且没超过硬性限制,如果连续超出一定时间则关闭客户端
Server
- 执行命令流程
- 读入querybuf并将命令解析结果放入argc argv
- 预备工作
- 执行命令
- 后续工作
- 慢查询日志记录
- 更新命令时间与计数值
- AOF日志记录
- 主从复制
- 结果返回客户端
- serverCron()
- 更新redisServer->unixtime mstime时间戳
- 更瓣LRUCLOCK,用于计算对象的空转时间
- 更新每秒执行命令次数
- 更新内存峰值
- 处理SIGTERM信号
- 管理客户端资源
- 超时终止客户端
- 检查输入缓冲区,如果上一次命令执行后,输入缓冲区大小超过一定长度,则重新分配内存
- 管理服务器资源(过期键处理,字典收缩处理)
- 执行BGREWRITEAOF
- 如果客户端发送了BGREWRITEAOF,如果此时服务器正在执行BGSAVE,则将aof_rewite_scheduled置位
- 检查持久化
- redisServer->rdb_child_pid redisServer->aof_child_pid 分别存放RDB与AOF持久化的子进程ID
- 当二者ID只要有一个为-1时执行一次wait3(),
- 如果有信号到来,表示RDB或AOF文件生成完毕,则进行RDB或AOF文件的替换
- 没有信号的到来,则不执行任何动作
- 当二者都均为-1时
- 如果aof_rewrite_scheduled为1,则执行BGREWRITEAOF
- 在没有其它持久化进行的情况下,如果自动保存条件满足,则执行BGSAVE
- 在没有其它持久化进行的情况下,如果AOF重写条件满足,则执行BGREWRITEAOF
- 将AOF缓冲区内容写入AOF文件
- 关闭由于输出缓冲区超限的客户端
- 增加cronloops计数值
- 初始化流程
- initServerConfig()初始化服务器状态结构
- 载入配置文件选项
- 初始化服务器数据结构
- 还原数据库状态(RDB AOF)
- 开启事件循环
IOTHREADS REDIS6.0
操作I/O多线程 核心仍然单线程
5种基本数据类型
string
list
set
hash
zset
- ziplist
- 采用字典与跳表同时存储
- 用空间换时间,兼顾ZRANK指令与直接获取值的需求
每种数据类型各使用了哪些数据结构
在什么条件下使用的数据结构会转化,如何调囊参数
内存回收
- 基于引用计数,通过客户端指令对引用计数进行设置
对象共享
- 对于相同的值仅存一份数据,仅限于INT值,默认值为0-9999
- 引出问题:由于内存共享,相同值的OBJECT共享了同一个引用计数
对象空转时长
- 存储对象的lru,用于LRU淘汰标识