Redis Redis原理

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 Java对象形式展示Redis数据结构

Redis运行机制

Redis server初始化
  1. 实例化redisServer对象
  2. 根据用户指定参数和配置文件初始化redisServer对象属性
  3. 初始化redisServer对象其他属性
  4. 创建常量:“OK"字符串和"0”-"9999"字符串
  5. 为serverCron创建时间事件
  6. 加载持久化文件(AOF或RDB)
  7. 开始执行时间事件

第六步加载持久化文件流程图

Created with Raphaël 2.3.0 redis启动 是否开启aof? 是否存在aof文件 重播aof文件是否成功 启动成功 启动失败 是否存在rdb文件 加载rdb文件是否成功 yes no yes no yes no yes no yes no
Redis client发送请求
  1. Redis client将操作指令根据RESP协议进行封装
  2. Redis client调用write将消息写入操作系统内核的send buffer
  3. 操作系统内核将消息发送至网卡
  4. 网卡通过网际路由发送至服务器网卡
Redis server接收请求
  1. 服务器网卡接收到消息
  2. 操作系统内核将消息放至recv buffer
  3. Redis server调用read将消息保存至redisClient.querybuf(输入缓冲区)
  4. Redis server解析请求内容,保存至redisClient.argv(操作命令与操作命令参数数组)redisClient.argc(argv长度)
  5. 调用操作命令执行器
Redis server处理请求
  1. 根据操作命令去命令表查询操作命令对应的命令函数(redisCommand)
  2. 根据redisClient.argc和redisCommand.arity验证操作命令参数个数是否正确
  3. 根据redisClient.authenticated验证客户端是否通过身份验证
  4. 调用命令函数
  5. 将处理结果保存至redisClient.buf(输出缓冲区)
  6. 进行后续处理(慢查询日志 & redisCommand计数+1 & AOF & 同步)
  7. 将处理结果发送给Redis client

Redis其他维护工作
读取一个键时,判断如果已过期,则删除
读取一个键后,更新命中或不命中次数
读取一个键后,更新LRU时间
修改一个键时,判断是否有客户端watch这个键,有则设为脏
修改一个键后,redisServer.dirty+1
修改一个键后,同步给其他节点

Redis事件

文件事件

套接字:socket
IO多路复用程序:Redis底层使用epoll
文件事件分派器:事件执行者
事件处理器:连接应答处理器、命令请求处理器和命令回复处理器

文件事件处理流程

  1. 套接字准备好执行连接应答、写入、读取、关闭等操作时,会产生一个文件事件
  2. IO多路复用程序监听多个套接字,把产生事件的套接字放在一个队列里
  3. IO多路复用程序有序推送套接字给文件事件分派器
  4. 文件事件分派器根据事件类型,选择事件处理器调用函数
时间事件serverCron函数
  1. 更新时间戳redisServer.unixtime和redisServer.mstime
  2. 更新LRU时钟redisServer.lruclock
  3. 更新Redis server每秒执行命令次数redisServer.ops_sec_samples
  4. 更新Redis server内存峰值记录redisServer.stat_peak_memory
  5. 处理SIGTERM信号,接收到SIGTERM信号把redisServer.shutdown_asap设置为1
  6. 检查客户端资源
  7. 检查数据库资源
  8. 检查持久化操作运行状态
  9. 如果开启AOF持久化,将AOF缓冲区内容写入AOF
  10. redisServer.cronloops+1

第七步检查数据库资源补充
检查数据库资源主要就是根据删除策略删除部分过期的键。

删除策略
定时删除:设置键过期时间的同时创建一个定时器,由Redis时间事件触发删除(该时间事件并不是serverCron)
惰性删除:访问一个键的时候判断是否过期,过期则删除
定期删除:这种策略是以上两种策略的折中方案,可以手动配置执行时长和频率(默认扫描超时时间25ms)

Redis使用的删除策略
惰性删除+定期删除

定期删除实现原理
每次检查一个数据库的键,若过期则删除,用一个全局变量记录检查到第几个数据库,下次执行定期删除继续检查后一个数据库,直至所有数据库全部检查完,全局变量置为0

redis内存不足时处理策略
noeviction:停止写服务,可执行读请求和del请求(默认策略)
volatile-lru:删除设置了过期时间的key(最少使用次数的key)
volatile-ttl:删除设置了过期时间的key(寿命最短的key)
volatile-random:删除设置了过期时间的key(随机的key)
allkeys-lru:删除最少使用次数的key
allkeys-random:删除寿命最短的key
volatile-xxx:删除设置了过期时间的key
allkeys-xxx:删除所有key

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文件,然后再结束进程

bgsave执行流程

  1. 父进程根据redisServer.rdb_child_pid判断是否有正在执行的子进程,有就直接返回
  2. fork子进程,fork过程中父进程阻塞
  3. fork完成,父进程继续处理请求,子进程创建RDB文件(全量数据)
  4. 子进程发送信号给父进程
  5. serverCron函数识别信号并替换RDB文件

父进程和子进程共享内存数据,父进程修改数据会拷贝数据页(4K),在备份上进行修改
过期的键不会保存至RDB文件
主节点加载RDB文件时,过期键不会加载至内存
从节点加载RDB文件时,会全量加载,从节点不会主动删除键,主节点发送del命令才能删除

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默认开启混合持久化
混合持久化执行流程

  1. 手动/自动触发bgrewriteaof命令
  2. 主进程fork子进程,fork过程中主进程阻塞
  3. 子进程将全量数据以RDB格式写入AOF文件,主进程将操作命令写入AOF缓冲区和AOF重写缓冲区
  4. 子进程通知主进程,主进程将AOF重写缓冲区数据以AOF格式写入AOF文件
  5. 主进程将新AOF文件替换原AOF文件(AOF文件前半段是RDB格式数据,后半段是AOF格式命令)

持久化对过期键的处理
RDB持久化:已过期键不会保存至RDB文件
AOF持久化:已过期键删除后会在AOF文件追加一条del命令
AOF重写:已过期键不会写入AOF文件

Redis高可用

主从复制模式

优点
读写分离:主节点写,从节点读
故障恢复:主节点宕机,将从节点升级为主节点
缺点
主/从节点故障恢复需要人工干预
写操作无法负载均衡
连接建立阶段

  1. 从节点masterhost记录主节点ip,masterport记录主节点port
  2. 从节点发送slaveof命令给主节点,主节点返回OK
  3. 从节点与主节点建立socket连接,从节点socket用于接收RDB文件以及其他命令,主节点socket保存在redisServer.clients
  4. 从节点发送ping命令给主节点,主节点返回pong
  5. 从节点发送auth命令给主节点进行身份验证
  6. 从节点发送端口号给主节点,主节点后续会将数据发送至该端口

数据同步阶段

  • 从节点发送psync命令给主节点,主节点判断全量复制或者部分复制

命令传播阶段

  • 主节点操作命令执行成功后,发送操作命令给从节点
  • 主从节点心跳机制和REPLCONF ACK机制

repl-disable-tcp-nodelay yes:合并操作命令,40ms发送一次
repl-disable-tcp-nodelay no:每次操作命令发送一次
心跳机制:主节点每10秒发送ping命令给从节点
REPLCONF ACK机制:从节点每秒发送REPLCONF ACK命令给主节点,维护offset属性

哨兵模式

优点
自动实现主节点故障恢复
缺点
节点故障恢复仍需要人工干预
写操作无法负载均衡
实现原理
每个哨兵节点维护了三个定时任务

  1. 向主节点发送info命令获取最新主从结构
  2. 通过发布订阅获取其他哨兵节点信息
  3. 向其他节点发送ping命令进行心跳检测

心跳检测过程中,主节点没有回复,哨兵节点将主节点主观下线,并通过sentinel is-master-down-by-addr命令询问其他哨兵节点主节点状态,如果判断主观下线的哨兵节点到达一定数量,则对该主节点进行客观下线,开始进行选举。
选择领导者哨兵节点算法:Raft算法,先到先得。
选择主节点算法:

  1. 过滤掉不健康的从节点
  2. 选择优先级最高的从节点
  3. 若优先级无法区分,选择offset最大的从节点
  4. 若offset无法区分,选择runid最小的从节点

min-slaves-to-write 1:至少有1个从节点在正常工作
min-slaves-max-lag 10:10秒未收到从节点反馈,就认为其断开

集群模式

优点
解决写操作无法负载均衡的问题
实现原理
集群模式将16384个槽分布在各个主节点上,数据通过数据分区方案落在各个槽中。
数据分区策略

  • 哈希取余分区
  • 一致性哈希分区
  • 虚拟节点一致性哈希分区

集群成员

  • 数据节点(主节点和从节点)
  • 哨兵节点
    主节点读写,从节点只负责备份数据
    每个节点维护2个端口
  • 普通端口:用户提供服务
  • 集群端口(普通端口+10000):用于集群各个节点通信

增加节点

  1. 启动节点
  2. 节点握手
  3. 迁移槽
  4. 指定主从关系

减少节点

  1. 迁移槽
  2. 节点下线

迁移槽步骤(迁移工具:redis-trib)

  1. redis-trib将原节点和目标节点状态设置为过渡状态
  2. redis-trib获取槽key列表
  3. 挨个迁移key(原节点将该key数据序列化,发送至目标节点,目标节点将该key数据反序列化,原节点删除该key)该步骤是同步阻塞的

访问过渡状态节点的key

  1. 客户端向原节点发送get指令,因为key数据已迁移至目标节点,所以原节点返回moved(重定向)指令
  2. 客户端向目标节点发送asking指令,目标节点返回"OK"
  3. 客户端向目标节点发送get指令获取数据

故障转移
集群识别某个主节点下线,由其他主节点投票选一个从节点成为主节点

Redis高可用对过期键的处理
主节点恢复RDB文件:已过期键不会加载至内存
从节点恢复RDB文件:已过期键会加载至内存
主从复制模式主节点删除已过期键后会发送del命令给从节点,从节点不会主动删除过期键

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值