redisearch白皮书_客户端

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淘汰标识