数据库
- redisServer的db数组,每一个db数组都是一个redisDb结构,代表一个数据库
- 默认16个数据库
- 切换数据库命令 select 2
- redisDb 成为键空间
redisDb
本质就是两个字典,一个保存kv,一个保存k-过期时间
读写键空间的维护操作
- 读取一个键后,会更新键空间明智次数或者不命中次数,可以在INFO stats命令的keyspace_hits、keyspace_misses 属性查看
- 更新键的lru
- 如果发现这个键已过期,会先删除这个过期键,
- 如果有使用watch监视该键,会将该键标记为脏,
- 每次修改一个键后,都会对脏键计数器加1,会触发持久化或者复制操作。
设置过期时间
- 本质都是pexpireat命令来实现,pexpireat key time(在该时间过期)
- 保存在db 的expires字典里,key 是键的指针,value long 过期的时间
- pttl key 返回 key 毫秒级的剩余生存时间
- 过期的判断:
- 是否存在于expires字典
- 当前时间戳是否大于键的过期时间
过期策略:
- 定时删除:设置键过期时间时,创建一个定时器,到点删除,对内存优好,对cpu非常不友好
- 惰性删除:获取键时,判断是否过期,可能会导致很多键过期了不删除,对内存不友好,对cpu非常有好
- 定期删除:每隔一段时间,选取一定的键,判断是否过期,过期就删除
- 一般使用惰性删除+定期删除
定期删除的过程:
- 一定量的数据库中抽取一定数量的随机减进行检查,删除过期键
- 全局变量current_db 记录当前检查的进度,如果是10,那下次从11号数据库开始
- 全检查完后,current_db 重置为0
aof、rdb对过期键的处理
首先,aof、rdb 都不会因为过期键而造成错误的影响
rdb:
- 执行save或者bgsave时,会检查,如果已经过期的键,不会写在rdb文件中。
- 载入rdb时,也会对键进行判断,如果过去也不会载入数据库中
- 如果是从服务器,过期的键也会载入,保证和主服务器的数据一致,当主服务器删除过期键时,会主动发一条del命令,删除从服务器的键。
aof
- 当过期键被惰性删除或者定期删除后,会在aof文件里追加del命令,删除该键
- aof重写时,也会进行检查过期键,过期的键不会写入,因为重写aof的原理,后面会说
复制的时候
- 主服务器发现一个键过期后,会显式的向所有从服务器发送一条del命令
- 从服务器发现后,也不删,等主服务器,保证一致性。
rdb 持久化
- 两个命令 save,bgsave
- save 是主进程进行持久化,会阻塞服务的处理
- bgsave 是fork一个子进程进行持久化,不会阻塞服务的处理
- 因为aof更新频率更高,如果服务器开启了aof,会优先使用aof文件来恢复数据库状态。
- 执行bgsave时:
- save命令会被拒绝
- 再次的bgsave命令也会被拒绝
- bgrewriteaof 命令会被延迟到bgsave 执行完后执行。因为这俩命令都是子进程执行,如果同时fork俩子进程进行大量的io操作,会严重影响主进程性能。
bgsave 执行的条件
- 允许配置一定的条件选项,让服务每隔一段时间自动执行bgsave;比如
默认配置
save 900 1 服务在900s内,对数据库至少进行了一次修改
save 300 10 服务在300s内,对数据库至少进行了10次修改
save 60 10000 服务在60s内,对数据库至少进行了20000次修改
- 使用 saveparams 结构保存,一个秒数,一个修改数,在redisServer里,
- redisServer里还有一个dirty计数器,用来纪律上次执行完save或者bgsave后,对数据库进行了多少次修改
- redisServer里还有 一个lastsave属性,距离上一次save或者bgsave 的时间
rdb 文件格式
- 开始表示:REDIS,5字节,读到了这个,表示这是一个rdb文件
- db_version:4字节,记录了rdb文件的版本号,比如说0006代表rdb文件第六版
- databases :包含0 或任意多个数据库的数据
- EOF 常量:1字节,代表rdb文件结束
- check_num:8字节无符号整数,对上面4个部分内容计算得到的一个值,用来校验rdb文件是否损坏。
aof持久化
分为以下三步:
- 命令追加:服务器执行一个写命令后,会将命令追加到redisServer的aof_bug缓冲区末尾
- 文件写入
- 文件同步
文件的写入和同步
redis服务器就是事件循环loop,在每一次循环中sercerCron中,会根据配置来判断是否将aof_buf缓冲区的内容写入aof中,
- always:总是写入aof文件,并且同步
- everysec:写入aof文件,距离上次同步超过1s,就同步。默认配置
- no:写入aof文件,是否同步根据操作系统来决定
aof文件的载入
- 启动一个不带网络连接的伪客户端,
- 读取aof文件,伪客户端执行命令,循环直到完成。
aof文件重写原理
-
其实aof文件重写不需要读取aof文件
-
遍历数据库中的键:
- 过期的就不写了
- 用一条写命令重写,比如sadd animals k1 k2 k3
- 如果这个列表或者字典什么的太大,会用多条写命令代替,避免造成客户端输入缓冲区溢出
- 默认是64个元素
-
后台执行,避免重写时不一致,使用aof重写缓冲区
aof重写过程:
- 1.创建一个子进程,重写aof文件,并将此时的写操作记录到aof重写缓冲区中,
- 2.子进程完成重写后,会向父进程发送一个信号,此时服务阻塞,将aof重写缓冲区的内存写入的aof文件中,原子性的替换文件,完成重写。
redis 线程模型
- 基于事件驱动的reactor模型,应该也是基于epoll的
多个线程(IO Thread)各自维护一个独立的事件循环。整体模型是由 Main 线程负责接收新连接,并分发给 IO Thread 去独立处理读写事件、解析请求命令,但是具体命令的执行还是使用main 线程来执行,最后使用IO 线程回写响应给客户端。
IO线程轮训socket列表读事件,然后解析为redis命令,并把解析好的命令放到全局待执行队列,然后主线程从全局待执行队列读取命令然后具体执行命令,最后把响应结果分配到不同IO线程,由IO线程来具体执行把响应结果写回客户端。