第9章 数据库
9.1服务器中的数据库
每个redisDb结构代表一个数据库
struct redisServer{
//一个数组保存着服务器中所有数据
redis *db;
//服务器的数据库数量
int num;
// 记录RDB保存条件的数组
saveparam *saveparams;
// 修改计数器,上一次成功执行SAVE或BGSAVE后数据库状态的修改次数
long long dirty;
// 上一次成功执行SAVE或BGSAVE的时间,UNIX时间戳
time_t lastsave;
};
9.2切换数据库
//选择数据库
select 2
//提示所用目标数据库
redis-cli
9.3数据库键空间
Redis是一个键值对数据库服务器
typedef struct redisDb{
//
// 数据库键空间,保存数据库中的所有键值对
dict *dict;
// 过期字典,保存全部有过期时间数据库键的过期时间
dict *expires;
//
}redisDb;
键空间的键也就是数据库的键,每个键都是一个字符串对象
键空间的值也就是数据库的值,每个值可以是五大基本对象类型
9.3.1添加新键
添加一个新建值到数据库,实际上就是将一个新键值对添加到键空间字典里面,其中键为字符串对象,键值为五大基本类型。
如 set hset 命令
9.3.2删除键
删除数据库中的一个键,实际上是删除键空间里面所对应的键值对象,如,del
9.3.3更新键
实际上就是对键空间里面键所对应的值进行更新
9.3.4对键取值
对一个数据库键取值,实际上就是在键空间中取出键所对应的值对象,根据值对象的类型不同,具体取值方法也会有所不同。
9.3.5读写键空间时的维护操作
- 读取一个键后,服务器会根据键是否存在去更新键空间命中或不命中次数,这两个值可在INFO stats命令的keyspace_hits和keyspace_misses属性中查看
- 读取一个键后,服务器会更新键的LRU时间
- 若服务器读取一个键时发现该键已过期,则会先删除该键,然后执行其他操作
- 若有客户端使用 WATCH命令监视某个键,则服务器在对被监视的键进行修改后,会将该键标记为“脏”,从而让事务程序注意到该键被修改过
- 服务器每修改一个键后,都会对“脏”键计数器的值+1,该计数器会触发服务器的持久化及复制操作
- 若服务器开启了数据库通知功能,则在对键进行修改后,服务器将按配置发送相应的数据库通知
9.4 设置键的生存时间或过期时间
通过expire命令或则pexpire命令,客户端可以以秒或者毫秒精度为数据库中的某个键设置生存时间
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pQguCHY4-1646303373361)(C:\Users\崔常菲\AppData\Roaming\Typora\typora-user-images\image-20211126155418565.png)]
9.4.1设置过期时间
expire key ttl //ttl秒
pexpire key ttl //ttl毫秒
expireat key timestamp //时间戳
EXPIRE <key> <ttl> 将键key的生存时间设置为ttl秒
PEXPIRE <key> <ttl> 将键key的生存时间设置为ttl毫秒
EXPIREAT <key> <timestamp> 将键key的过期时间设置为timestamp指定的秒数时间戳
PEXPIREAT <key> <timestamp> 将键key的过期时间设置为timestamp指定的毫秒数时间戳
以上命令都是通过转换成expireat命令执行的。
9.4.2保存过期时间
redis结构的expires字典保存了数据库中的所有键的过期时间,我们称这个字典为过期字典,过期字典键为一个指针,过期字典的值为 long long类型的整数:
typedef struct redisDb{
//过期子字典,保存着键的过期时间
dict *expires;
}
9.4.3移除过期时间
persist命令可以移除一个键的过期时间
persist 命令就是pexpireat命令的反操作:persist命令在过期字典中查找给定的键,并解除键和值在过期字典中的关联
9.4.4计算返回剩余生存时间
TTL命令以秒PTTL以毫秒为单位返回键的剩余生存时间
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GYMlxkcn-1646303373362)(C:\Users\崔常菲\AppData\Roaming\Typora\typora-user-images\image-20211126161159387.png)]
这两个命令都是计算过期时间和当前时间之间的差来实现的
9.4.5过期键的判定
- 检查是否过期字典,有,则获得键的过期时间
- 检查当前unix时间戳是否大于键的过期时间,是,则过期9.5
9.5过期键的删除策略
9.5.1定时删除
在设置键的过期时间同时,创建一个定时器,让定时器在键的过期时间来临时,立即对键的删除操作
对内存最友好,可以保证过期键会尽可能快的被删除,释放过期键所占用的内存。缺点则是对cpu时间最不友好
9.5.2惰性删除
放任键过期不管,从键空间获取键时,都检查取得键是否过期,如果过期则删除
对cpu时间最友好,程序只会在取出键时,才对键进行过期检查,缺点时对内存不友好
9.5.3定期删除
每个一段时间,程序进行一次检查,删除过期键
9.6redis的过期键删除策略
redis实际使用的时惰性删除和定期删除两种策略:通过配合使用两种删除策略,服务器可以很好的合理使用菜谱时间和避免浪费内存之间取得平衡。
9.6.1惰性删除策略的实现
- 如果输入键已经过期,那么函数将输入键从数据库中删除
- 如果输入键未过期,那么函数不做动作。
9.6.2定期删除策略的实现
Redis服务器周期性执行以下过期键删除操作
·从一定数量数据库中取一定数量随机键进行检查,并删除其中的过期键
·记录检查进度,并在下次检查时接着上次的进度继续进行检查
·随着以上操作循环执行,服务器中所有数据库都会被检查一遍,然后再次开始新一轮检查工作
9.7AOF、RDB和复制功能对过期键的处理
9.7.1生成RDB文件
在执行SAVE命令或者BGSAVE命令创建一个新的RDB文件时,程序会对数据库中的键进行检查,已过期的键不会被保存到新创建的RDB文件。
数据库中包含的过期键不会对生成新的RDB文件造成影响
9.7.2载入RDB文件
在启动Redis服务器时,如果服务器开启了RDB功能,那么服务器将对RDB文件进行载入:
-
若服务器以主服务器模式运行**,过期键不会被载入到数据库**
-
若服务器以从服务器模式运行,所有键,不论是否过期,都被载入到数据库
**注,**主从服务器进行数据同步时,从服务器数据库会被清空,故,从服务器载入过期键不会对数据一致性产生影响
9.7.3AOF文件写入
当服务器以AOF持久化模式运行时,数据库中的某关键已经过期,但他还没有被惰性删除或者定期删除,那么AOF文件不会因为这个过期键而产生任何影响。
- 从数据库中删除message键
- 追加一条DEL message命令到AOF
- 向执行GET命令的客户端返回回复
9.7.4AOF重写
在AOF重写过程中,程序会对数据库中的键进行检查,已过期的键不会被保存到重写后的AOF文件。
因此,数据库中包含过期键不会对AOF重写造成影响
9.7.5复制
当服务器在复制模式下,从服务器的过期键删除动作由主服务器控制:
- 主服务器在删除一个过期键之后,会显示地向所有从服务器发送一个DEL命令,告知从服务器删除这个过期键
- 从服务器在执行客户端发送的读命令时,即使碰到过期键也不会将过期键删除,而是继续想出来未过期的键一样处理过期键
9.8数据库通知
可以让客户端通过订阅给定的频道或者模式,来获取数据库中键的变化,以及数据库中命令的执行情况。
Redis客户端订阅命令:SUBSCRIBE _ _keyspace@【数据库编号】_ _ :【命令或者key名】
,
比如说订阅0号数据库的message这个key的命令就是:SUBSCRIBE _ _keyspace@0_ _:message
9.8.1发送通知
当一个redis命令需要发送数据库通知的时候会调用该函数,实际实现步骤:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-76Y5d0l7-1646303373362)(C:\Users\崔常菲\AppData\Roaming\Typora\typora-user-images\image-20211126203740484.png)]
- 判断操作类型是否是notify-keyspace-events配置的允许发送的内容,如果不是则直接返回
- 判断服务器是否允许发送键空间通知,如果允许则会发送事件通知
订阅事件其实本质就是通过PUBLISH命令来执行的
第10章RDB持久化
1、RDB全称redis database,在指定的时间间隔内将内存中的数据集快照写入磁盘,也就是行话讲的Snapshot快照,它恢复时 直接将快照文件直接读到内存里;
2、Redis会单独创建(fork)一个子进程来进行持久化,会先将数据写入到一个临时文件中,主进程是不进行任何IO操作的,它就确保了极高的性能;如果需要进行大规模数据的恢复,且对于数据恢复的完整性不是非常敏感,那RDB方式比AOF方式更加的高效。RDB的缺点是最后一次持久化后的数据可能丢失。
10.1RDB文件的创建与载入
save 和 bgsave可以用于生成RDB文件
- 如果服务器开启了AOF持久化功能,那么服务器会优先使用AOF文件来还原数据库状态
- 只有在AOF持久化功能处于关闭状态时,服务器才会使用RDB文件来还原数据库状态。
10.1.1save命令执行时的服务器状态
redis服务器会被阻塞,所以当save命令正在执行时,客户端发送所有命令会被拒绝
10.1.2bgsave命令执行时的服务器状态
bgsave命令的保存工作室友子进程进行的,所以在子进程创建RDB文件的过程中,redis服务器仍然可以继续处理客户端的请求。bgsave执行期间,save命令会被服务器拒绝。同时bgrewriteaof和bgsave不能同时执行
10.1.3RDB文件载入时的服务器状态
服务器在载入RDB文件期间,会一直处于阻塞状态,知道载入工作执行完成为止。
10.2自动间接性保存
在redis.conf文件中
只要满足以上任意条件即可, eg: 服务器在900S内至少进行了1次修改。
10.2.1设置保存条件
服务器启动时,用户可以通过指定配置文件或者传入启动参数的方式设置save选项,如果用户没有主动设置save选项,那么服务器回味save选项设置默认条件。
10.2.2dirty计数器和lastsave
dirty计数器记录距离上一次成果执行save命令或者bgsave命令之后,服务器对数据库状态进行了多少次修改
lastsave属性是一个时间戳,记录了了服务器上一次成功执行save命令或者bgsave命令的时间
10.2.3检查保存条件是否满足
redis服务器周期性操作函数serverCron默认每个100毫秒就会执行一次,该函数用于对正在运行服务器进行维护,一项工作就是检查save选项所设置的保存条件是否已经满足。如果满足的话,就执行BGSAVE命令,这里用到了dirty计数器属性和lastsave属性:
10.3RDB文件结构
文件结构:
开头部分时REDIS部分,该部分为5字节,保存着“REDIS”五个字符,快速检查所在如的文件是否RDB文件。
db_version长度时4字节,记录了RDB文件的版本号
databases部分包含着零个或任意多个数据库,以及各个数据库中的键值对数据
check_sum 是一个8字节长的无符号整数,保存一个校验和
10.3.1databases
可以保存二年一多个非空数据库
数据库结构
selectdb为1字节,让程序知道接下来督导一个数据库号码
db_number保存着一个数据库号码,根据号码大小不同,可以是1、2、5字节
key_value_pairs部分保存了数据库中的所有键值对数据
10.3.2key_value_pairs
RDB文件中每个key_value_pairs部分都保存了一个或以上数量的键值对,如果键值对带有过期时间的话,那么键值对的过期时间也会被保存在内。
由type、key、value组成,不带过期时间的组成:
带过期时间的键值对:
10.3.3value的编码
value保存了一个值对象,每个值对象的类型都有与之对应的type记录,根据类型的不同,value部分的结构、长度也会不同。
- 字符串对象
type为string类型,那么value就是保存一个字符串对象
保存字符串方式:
长度<20字节,原样保存
长度>20字节,压缩保存
- 列表对象
- 集合对象
- 哈希表对象
- 有序集合对象
10.4分析RDB文件
od RDB文件,可以分析Redis服务器长生的RDB文件,该命令可以用给定的格式转存dunp
10.4.1不包含任何键值对的RDB文件
REDIS + 版本号+EOF+八字节校验和
第11章AOF持久化
AOF(Apend only fiel)AOF持久化是通过保存Redis服务器所执行的写命令来记录数据库状态。被写入AOF文件的所有命令都是以Redis的命令请求协议格式来保存的,Redis命令请求协议是纯文本格式。
服务器在启动的时候,通过载入和执行 AOF文件中保存的命令来还原服务器关闭之前的数据库状态。
11.1AOF持久化的实现
可以分别实现追加、文件写入、文件同步三个步骤
11.1.1命令追加
当AOF持久化功能处于打开状态时,服务器在执行完一个写命令之后,会以协议格式将执行的写命令追加到服务器状态的aof_buf缓冲区的末尾:
11.1.2AOF文件的写入与同步
在AOF持久化的过程中,其实上是分成两个部分:
- WRITE(写入):根据条件,将 aof_buf 中的缓存写入到 AOF 文件。
- SAVE(同步):根据条件,调用 fsync 或 fdatasync 函数,将 AOF 文件保存到磁盘中。
服务器在处理文件时间时可能会执行写命令,把一些内容追加到aof缓冲区,所以在服务器每结束一次事件循环(负责节后客户端的命令请求,以及向客户端发送命令回复)之前,都会调用flushAppendOnlyFile函数,考虑是否把aof缓冲区的内容写入和保存到AOF文件中。
AOF持久化的效率和安全
appendfsync为always时,每个循环都要将aof_buf缓冲区中的所有内容都写入到AOF_buf中,效率最低,安全性最高
appendfsync为everysec时,每个循环都要将aof_buf缓冲区中的所有内容都写入到AOF_buf中.everysec模式足够快,最多只丢失一秒钟数据
appendfsync为no时,每个循环都要将aof_buf缓冲区中的所有内容都写入到AOF_buf中,类似和everysec类似,使用该模式将丢失上次同步AOF文件之后的所有写命令数据。
11.2AOF文件的载入和数据还原
Redis读取AOF文件并还原数据库状态的详细步骤如下:
- 创建一个不带网络连接的伪客户端(fake client),伪客户端执行命令的效果和嗲网络连接的客户端执行命令的效果完全一样
- 从AOF文件中分析并读取一条写命令
- 使用伪客户端执行被读出的写命令
- 一直执行步骤2和步骤3,直到AOF文件中的所有写满了都被处理完毕为止。
11.3AOF重写
随着服务器运行时间越长,AOF文件中的内容会越来越多,文件的体积也会变得越来越大,为了解读额AOF文件体积膨胀问题,提出了AOF文件重写(rewrite)功能。
11.3.1AOF文件重写的实现
AOF重写实现的特点:
- 不需要对现有的AOF文件进行任何读取、分析或者写入操作
- 直接从redis数据库的键值对生成对应的redis命令(比如说之前的操作是三个命令添加了10个值,而这种方式可以直接通过一条命令去生成这个键值对的最终状态)
- 为了防止客户端输入缓冲区溢出,如果元素超过redis.h/REDIS_AOF_REWRITE_ITEMS_FER_CMD配置的常量值,重写程序就会用多条命令来记录键的值
11.3.2AOF后台重写
Redis将AOF重写程序妨碍子进程里执行的目的:
- 重写期间,服务器父进程可以继续处理命令请求
- 子进程带有服务器的数据副本,使用子进程而不是线程,可以避免使用锁的情况下,保证数据的安全性。
子进程执行AOF重写期间,服务器进程需要执行以下三个工作:
- 执行客户端来的命令
- 将执行后的写命令追加到AOF缓冲区
- 将执行后的写命令追加到AOF重写缓冲区
当子进程完成AOF重写工作之后,他会向父进程发送一个信号,父进程在接到该信号之后,会调用一个信号处理函数,并执行以工作:
- 将AOF重写缓冲区中的所有内容写入到新AOF文件中,这时新AOF文件所保存的数据库状态江湖服务器当前数据库一致
- 从创建子进程开始,服务器执行的所有命令都会被记录到AOF重写缓冲区里面
第12章事件
文件事件:redis服务器通过套接字与客户端进行连接,而文件时间就是服务器对套接字操作的抽象。
时间事件:Redis服务器中的一些操作需要对给定时间点执行,而时间事件就是服务器对这类定时操作的抽象。
12.1文件事件
- 文件处理器使用IO多路复用程序来同时监听多个套接字,并通过套接字目前执行的任务来为套接字关联不同的事件处理器
- 监听套接字准备好执行连接应答、读取、写入、关闭等操作时,文件处理器会调用套接字之前关联好的事件处理器来处理这些事件。
12.1.1文件事件处理器构成
文件处理器分成以下四个部分:
文件事件处理器
- 套接字(并发出现)
- I/O多路复用程序(将所有产生事件的套接字都放在一个队列,通过队列,有序同步的每次一个套接字的方式向文件事件分派器传送套接字)
- 文件事件分派器(当上一个套接字产生的事件被处理完毕之后,I/O多路复用程序才会向文件事件分派器传送下一个套接字,根据套接字产生的事件类型,调用相应的事件处理器)
12.1.2IO多路复用程序的实现
每个I/O多路复用函数库在Redis源码中都对应一个单独的文件,共四个:ae_epoll.c ae_evport.c ae_kqueue.c ae_select.c,实现了相同的API。程序在编译的过程中会自动选择性能最高的I/O多路复用函数库作为Redis的I/O多路复用程序的底层实现。
12.2.3事件类型
- I/O多路复用程序可以监听多个套接字ae.h/AE_READABLE和ae.h/AE_WRITABLE事件
- 套接字变得可读,套接字产生AE_READABLE事件
- 套接字变得可写,套接字产生AE_WRITABLE事件
12.1.4文件处理器
- 连接应答处理器
用于连接服务器监听套接字进行应答
- 命令请求处理器
负责从套接字中国读入客户端发送的买了请求内容
- 命令回复处理器
负责将服务器执行命令后得到的命令回复通过套接字返回给客户端
- 完整客户端服务器连接示例
12.2时间事件
定时事件:指定时间后执行一次
周期性事件:每隔指定事件执行一次
一个事件主要由以下三个属性组成:
id:服务器为时间事件创建的全局唯一ID号
when:记录了事件到达的时间
timeproc:时间事件处理器
12.2.1实现
服务器将所有时间时间放在一个无序链表中,每当时间事件事件处理器运行时,就遍历整个链表,查找所有一道道的事件事件,并调用相应的事件处理器。
12.2.2serverCron函数
实现对自身资源和状态进行检查和调整,从而确保服务器可以长期、稳定地运行,主要包括:
12.3事件第调度和执行
- 服务器有两种事件:文件事件和时间事件,具体的函数是ae.c/aeProcessEvents
原则
- 两个事件的处理同步、有序、原子地执行。服务器不会中途中断事件处理,也不会抢占。文件事件和时间事件都会尽可能减少程序的阻塞事件,有需要时主动让出执行权,降低事件饥饿的可能
- 比如说写入操作,一次性要写很多超过了预设常量。命令回复处理器会主动用break跳出写入循环,将余下的数据留到下次再写
- 时间事件如果很耗时,比如说持久化rdb和aof,会将其放在子线程或者子进程执行
时间事件在文件之后执行,所以一般实际处理的事件比预设要晚。因为时间事件该执行了,但是文件事件正在进行中,就需要等其结束
第13章客户端
redis服务器是典型地一对多服务器,一个服务器可以与多个客户端建立网络连接
为了和这些客户端建立相应地结构,这个结构保存了客户端当前地状态信息,以及执行相关功能需要用到的数据结构,其中包括:
- 客户端的套接字描述符
- 客户端名字
- 客户端标志值
- 指向客户端正在使用的数据库指针和数据库号码
- 当前执行命令的个数
- 输入输出缓冲区
- 复制状态信息和数据结构
- 客户端执行brpop,blpop使用的数据结构
- 客户端事务状态和watch命令使用到的数据结构
- 客户端的身份验证标志
13.1客户端属性
客户端状态包含的属性
- 通用属性
- 和特定功能相关的属性
13.1.1套接字描述符
客户端fd属性记录了客户端正在使用的套接字描述符
struct redisServer {
一个链表,保存了所有客户端状态
list *clients;
};
- 伪客户端的fd属性为-1,伪客户端处理的命令请求来源于AOF文件或者Lua脚本,而不是网络,所以这种这种客户端不需要台阶子连接,也不需要记录套接字描述符
- 普通客户端fd属性值大于-1的整数
client list //该命令可以列出所有连接到服务器的普通客户端
13.1.2名字
默认情况下没有名字
client setname //可以为客户端设置一个名字
13.1.3标志
typedef struct redisClient {
// ...
int flags;
// ...
} redisClient;
flags = <flag1> | <flag2> | ...
客户端flags记录了客户端的角色,以及客户端目前所处的状态,可以是单个状态,也可以是多个状态取或连接
- 主从复制的时候REDIS_MASTER是主服务器,REDIS_SLAVE就是从
- REDIS_PRE_PSYNC说明客户端版本低不能使用psync与从服务器同步
- REDIS_LUA_CLIENT处理lua脚本的伪客户端
- REDIS_MONITOR正在执行monitor命令
- ·REDIS_UNIX_SOCKET使用unix套接字连接客户端。
- REDIS_BLOCKED表示客户端被brpop或者blpop阻塞
- REDIS_UNBLOCKED表示已经从阻塞出来了
- REDIS_MULTI表示客户端正在执行事务
13.1.4输入缓冲区
用于保存客户端发送的命令请求。
typedef struct redisClient {
// ...
sds querybuf;
// ...
} redisClient;
13.1.5命令与命令参数
typedef struct redisClient {
// ...
robj **argv;
int argc;
// ...
} redisClient;
argv 是一个数组,每个项都是一个字符串对象,argv[0]是要执行的命令,后面是其他项是命令的参数
argc负责记录argv的长度;
13.1.6命令的实现函数
当服务器从协议内容中分析并得出argv和argc属性值之后,服务器根据项argv[0]的值,在命令表中查找所对应的命令实现函数。
typedef struct redisClient {
// ...
struct redisCommand *cmd;
// ...
} redisClient;
13.1.7输出缓冲区
执行命令所得的命令回复都要保存在客户端状态的输出缓冲区里面,每个客户端都有两个输出缓冲区可用。一个缓冲区的大小是固定的,另一个缓冲区的大小是可变的。
- 固定大小缓冲区,保存长度比较小的回复,如OK
- 可变大小缓冲区用于保存哪些长度比较大的回复;
typedef struct redisClient {
// ...
char buf[REDIS_REPLY_CHUNK_BYTES];
int bufpos;
// ...
} redisClient;
- 可变缓冲区大小,可以通过链表把字符串对象串联起来。
13.1.8身份验证
客户端状态authenticated属性用于记录客户端是否通过了身份验证,0失败,1成功
typedef struct redisClient {
// ...
int authenticated;
// ...
} redisClient;
13.1.9时间
ctime 记录了创建客户端的时间,这个时间可以用来计算客户端与服务器已经连接了多少秒
lastinteraction 记录了客户端与服务器最后一次互动时间
obuf_soft_limit_reached_time 用来计算客户端的空转时间
typedef struct redisClient {
// ...
time_t ctime;
time_t lastinteraction;
time_t obuf_soft_limit_reached_time;
// ...
} redisClient;
13.2客户端的创建与关闭
13.2.1创建普通客户端
- 如果是普通网络连接就会创建普通客户端。
- connect就会调用事件处理器来完成客户端状态创建,并且加入到服务器的链表尾
13.2.2关闭普通客户端
关闭原因:
- 客户端进程退出或者被杀死
- 客户端向服务器发送了带有不符合协议格式的命令
- 客户端成了client kill目标对象
- 用户为客户端设置了timeout配置选项,当客户端空转时间超过timeout选项设置时,客户端将被关闭
- 客户端发送的命令请求大小超过了输入缓冲区的限制大小
- 如果要发送给客户端的命令回复的大小超过了输出缓冲区的限制大小
硬件限制:如果缓冲区的大小超过了影响限制所设置的大小,那么服务器立即关闭客户端
**软性限制:**如果输出缓冲区的大小超过了软性限制所设置的大小,但是还没超过硬性限制,会记录这个时间到obuf_soft_limit_reached_time,设定时长,如果缓冲超过限制而且位置超过时长那么就关闭客户端。
13.2.3Lua脚本的伪客户端
伪客户端在服务器运行的整个生命周期,只有服务器被关闭时,客户端才会被关闭
struct redisServer {
// ...
redisClient *lua_client;
// ...
};
13.2.4AOF的伪客户端
服务器在载入AOF文件时,会创建用于执行AOF文件包含的redis命令的伪客户端
第14章服务器
14.1命令请求的过程
eg:set key value
需要以下操作:
- 客户端向服务器发送命令请求 set key value
- 服务器接收并处理客户端发来的命令指令 set key value,在数据库中进行设置操作,并查收命令回复ok
- 服务器将命令回复ok发送给客户端
- 客户端接收服务器返回的命令回复ok
14.1.1发送命令请求
命令发送请求流程如下:
14.1.2读取命令请求
- 读取套接字中协议格式的命令请求,并将其保存客户端状态的输入缓冲区里面
- 对输入缓冲区中的命令请求进行分析,提取命令请求中包含的命令参数,以及命令参数的个数,然后保存在agrc和agrv中
- 调用命令执行器,执行客户端指定的命令。
14.1.3命令执行器:查找命令实现
命令执行器要做的第一件事就是根据客户端状态的argv参数,在命令表中查找参数所指定的命令,并将找到的保存到客户端状态的cmd属性里面。
命令表时一个字典,字典的键就是一个个命令名字,如set、get、del等等
flags标识
14.1.4命令执行器
服务器已将执行命令所需的命令实现函数、参数、参数个数都收集齐了,程序还需要一些预备操作,从而确保命令都可以i正确、顺利的被执行,主要包括:
- 检查客户端状态CMD指针是否指向null,若是,那么说明用户输入的命令名字找不到相应的命令实现
- 根据客户端的cmd属性指向的rediscommand结构的arity属性,检查给定参数个数是否正确
- 检查客户端是否通过了身份验证
- 检查服务器是否打开了maxmermory功能,若打开,先检查服务器的内存占用情况,并在有需要时进行内存回收,从而接下来的命令可以顺利执行
- 如果服务器上一次执行bgsave命令出错,并且服务器打开了stop-write-on-bgsave-error功能,而且服务器将要执行一个写命令,那么服务器将拒绝执行这个命令
14.1.5命令执行器:调用命令的实现函数
在之前的操作中,服务器已经把执行命令保存到客户端状态中,所以执行命令操作其实只是通过指针进行一次函数调用即可。被调用的函数实现指定操作,并产生对应的命令回复,这些回复保存在客户端状态的输出(固定)缓冲区中。
14.1.6命令执行器:执行后续工作
执行完实现函数后,服务器还需要执行一些后续工作
- 若开启了慢查询功能,那么曼茶叙日志没看hi检查是否需要伪刚刚执行完的命令请求添加一条新的慢查询日志
- 根据刚刚执行命令所耗费的时长,更新被执行命令的rediscommand结构
- 若开启了AOF持久化,那么AOF持久化模块会将刚刚齿形的命令请求写入到AOF缓冲区里
- 如果其他从服务器正在复制当前这个服务器,那么服务器会将刚刚执行命令传播给所有从服务器
14.1.7将命令回复发送给客户端
命令实现函数会见命令回复保存到而护短的输出缓存区里面,并为客户端的套接字关联命令回复处理器,将保存的客户端输出缓冲区回复发送给客户端。
14.1.8客户端接收并打印命令回复
14.2serverCron函数
该函数每个100毫秒执行一次,复制关联服务器的资源,并保持服务器自身的良好运转。
14.2.1更新服务器时间缓存
struct redisServer{
time_t unixtime; 秒级时间戳
long long mstime; 毫秒级时间戳
};
该函数没100毫秒一次更新unixtime属性和msime,精度不高
14.2.2更新LRU时钟
struct redisServer{
unsigned lruclock:22;//用于计算键的空转时长
};
lruclock记录空转时间=lurclock属性家去对象lru属性记录时间
struct redisServer{
unsigned lru:22;//用于计算键的空转时长
}robj;
lur保存了对象最后一次被命令访问的时间
14.2.3更新服务器每秒执行命令次数
trackOperationsPerSecond函数会根据上次抽样时间和服务器的当前时间以及记录的上一次抽样的以执行命令数量和服务器昂前的已执行命令数量,计算出两次trackOperationsPerSecond调用之间,服务器平均一毫秒处理了多少个命令请求。
14.2.4更新服务器内存峰值记录
stat_peak_memory属性记录了服务器的内存峰值大小,每次执行serverCrond都会与stat_peak_memory保存的是指比较,若大,则记录。
14.2.5处理sigterm
启动服务器时。redis回味服务器进程sigterm信号关联处理器sigterHandler函数,这个信号处理负责在服务器街道sigterm信号,打开服务器的shutdown_asap标识。
如果服务器已接到sigterm信号就立即关闭,那就没办法执行持久化操作了
14.2.6管理客户端资源
对客户端进行两个检查:
- 如果连接超时,则释放这个客户端
- 如果客户端在执行上一次执行命令请求后,输入缓冲区的大小超过了一定的长度,那么程序会释放客户端当前的输入缓冲区,并重新创建默认大小的输入缓冲区
14.2.7管理数据库资源
serverCron函数中调用的databasesCorn函数对服务器中的一部分数据库进行检查
14.2.8执行被延迟的BGREWRITEAOF
在服务器执行bgsave命令期间,如果客户端向服务器发来BGREWRITEAOF命令,那么服务器会将BGREWRITEAOF命令的执行时间延迟到bgsave命令之后
14.3初始化服务器
一个redis服务器从启动到能够接收客户端的命令请求,需要经过一些列的初始化和设置的过程。
14.3.1初始化服务器状态结构
initServerConfig函数设置服务器状态属性
- 设置服务器的运行ID
- 设置服务器默认的运行频率
- 设置服务器的默认配置文件路径
- 设置服务器的运行架构
- 设置服务器的默认端口
- 设置服务器的默认RDB持久化条件和AOF持久化条件
- 初始化服务器的LRU时钟
- 创建命令表
14.3.2载入配置选项
在启动服务器时,用户可以通过给定的配置参数或者指定配置文件来修改服务器的默认配置:
redis-server -p 10086
redis-server redis.conf
databases 32 //数据库为32
rdbcompression no //关闭RDB文件的压缩功能
14.3.3初始化服务器数据结构
在之前执行initServerconfig函数初始化server状态时,程序值创建了命令表一个树结构,不过除了命令表之外
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yw4l5f5k-1646303373368)(C:\Users\崔常菲\AppData\Roaming\Typora\typora-user-images\image-20211127165750448.png)]
14.3.4还原数据库状态
在完成了对服务器状态server变量的初始化之后。服务器载入RDB文件或者AOF文件,并根据文件记录的内容还原服务器的数据库状态。
若开了AOF持久化功能,服务器率先使用AOF文件来还原数据库状态。
14.3.5执行时间循环
在初始化的最后一步,服务器会打印以下日志,并开始执行服务器事件循环。