15.服务器

服务器

1. 命令请求的执行过程

1.1 发送命令请求

下图是客户端接收并发送命令请求的过程。

客户端接收并发送命令请求的过程

1.2 读取命令请求

当客户端与服务器之间的连接套接字因为客户端的写入变得可读时,服务器会调用命令请求处理器执行以下操作:

  1. 读取套接字中协议格式的命令请求,并将其保存到客户端状态的输入缓冲区里。
  2. 对输入缓冲区的命令请求分析,提取命令请求中包含的命令参数,命令参数个数,然后分别将参数、参数个数保存到客户端状态的argv、argc属性里。
  3. 调用命令执行器,执行客户端指定的命令。

1.3 命令执行器(1):查找命令实现

命令执行器首先根据客户端状态的argv[0]参数,在命令表中查找参数指定的命令,并将找到的命令保存到客户端状态的cmd属性里。

命令表字典的值是一个个的redisCommand结构,每个redisCommand结构记录了一个Redis命令的实现信息。

下图是redisCommand结构的主要属性。

redisCommand结构的主要属性

下图是sflags属性的标识。

sflags属性的标识

下图是命令表示例。

命令表示例

查找后,命令表会返回对应的redisCommand结构,客户端状态的cmd指针会指向这个redisCommand结构。

设置客户端状态的cmd指针

命令表使用大小写无关的查找算法,SET、set、Set都是同一个命令。

1.4 命令执行器(2):执行预备操作

在真正执行命令之前,程序还需要执行一些预备操作,保证命令可以正确、顺利地执行,这些操作包括:

预备操作

以上只列出了服务器在单机模式下执行命令的检查操作,当服务器在复制、集群模式下执行命令时,预备操作还会多一些。

1.5 命令执行器(3):调用命令的实现函数

执行命令后,命令回复会被保存到客户端状态的输出缓冲区中,之后,实现函数还会为客户端的套接字关联命令回复处理器,这个处理器负责将命令回复返回给客户端。

1.6 命令执行器(4):执行后续工作

执行完函数后,服务器会做一些后续操作:

命令执行后续操作

以上操作执行完,服务器就可以继续从文件事件处理器取出并处理下一个命令请求。

1.7 将命令回复发送给客户端

当客户端套接字变为可写状态时,服务器会执行命令回复处理器,将保存在客户端输出缓冲区中的命令回复发送给客户端。

当命令回复发送完毕后,回复处理器会清空客户端状态的输出缓冲区,为处理下一个命令请求做好准备。

1.8 客户端接收并打印命令回复

下图是客户端接收并打印命令回复的过程。

客户端接收并打印命令回复的过程

2. serverCron函数

struct redisServer{
    // ...

    // 保存秒级精度的系统当前UNIX时间戳
    time_t unixtime;

    // 保存毫秒级精度的系统当前UNIX时间戳
    long long mstime;

    // 默认每10秒更新一次的时钟缓存,用于计算键的空转时长。
    unsigned lruclock:22;

    // 上一次进行抽样的时间
    long long ops_sec_last_sample_time;

    // 上一次抽样时,服务器已执行命令的数量
    long long ops_sec_last_sample_ops;

    // 数组中的每个项都记录了一次抽样结果
    // REDIS_OPS_SEC_SAMPLES默认值为16,环形数组
    long long ops_sec_samples[REDIS_OPS_SEC_SAMPLES];

    // ops_sec_samples数组的索引值
    // 每次抽样后将值自增1
    // 在值等于16时重置为0
    // 让ops_sec_samples数组构成一个环形数组。
    int ops_sec_idx;

    // 已使用内存峰值
    size_t stat_peak_memory;

    // 关闭服务器的标识
    // 值为1时,关闭服务器
    // 值为0时,不做动作
    int shutdown_asap;

    // 如果值为1,表示有BGREWRITEAOF命令被延迟了
    int aof_rewrite_scheduled;

    // 记录执行BGSAVE命令的子进程的ID
    // 如果服务器没有在执行BGSAVE,那么这个属性的值为-1
    pid_t rdb_child_pid;

    // 记录执行BGREWRITEAOF命令的子进程的ID
    // 如果服务器没有在执行BGREWRITEAOF,那么这个属性的值为-1
    pid_t aof_child_pid;

    // serverCron函数的运行次数计数器
    // serverCron函数每执行一次,这个属性的值就增加1
    int cronloops;

    //...
}

2.1 更新服务器时间缓存

每次获取系统的当前时间都需要执行一次系统调用,为了减少系统调用的执行次数,unixtime、mstime被用作当前时间的缓存。

serverCron默认以每100毫秒一次更新unixtime、mstime属性,所以这两个属性记录的时间准确度不高:

Redis对时间的不同精确度策略

2.2 更新LRU时钟

服务器状态中的lruclock属性保存了服务器的LRU时钟,也是服务器时间缓存的一种。

每个Redis对象都会有一个lru属性,保存对象最后一次被命令访问的时间。

typedef struct redisObject{
    // ...
    unsigned lru:22;

    // ...
} robj;

程序会用服务器的lruclock记录的时间减去对象的lru记录的时间,得到对象的空转时间。

serverCron默认以每10s一次的频率更新lruclock属性的值。

lruclock时钟的当前值可以通过INFO server命令的lru_clock域查看。

2.3 更新服务器每秒执行命令次数

serverCron函数中的trackOperationsPerSecond函数以每100毫秒一次的频率执行,这个函数的功能是以抽样计算的方式,估算并记录服务器在最近一秒钟处理的命令请求数量,这个值可以通过INFO status命令的instantaneous_ops_per_sec域查看。

trackOperationsPerSecond函数和服务器状态中4个ops_sec_开头的属性有关。

trackOperationsPerSecond函数每次运行,都会根据ops_sec_last_sample_time记录的上一次抽样时间和服务器的当前时间,以及ops_sec_last_sample_ops
记录的上一次抽样的已执行命令数量和服务器当前的已执行命令数量,计算出两次trackOperationsPerSecond调用之间,服务器平均每一毫秒处理了多少个命令请求,然后将这个平均值乘以1000
,就得到了服务器在一秒内能处理多少个命令请求的估值值,这个估计值会被作为一个新的数组项放进ops_sec_samples环形数组里。

当客户端调用INFO命令时,服务器就会调用getOperationsPerSecond函数,根据ops_sec_samples环形数组中的抽样结果,计算出instantaneous_ops_per_sec值。

getOperationsPerSecond函数

2.4 更新服务器内存峰值记录

stat_peak_memory记录服务器的内存峰值大小。

每次serverCron执行时,程序都会查看服务器当前使用的内存数量,并与stat_peak_memory比较,如果当前使用的内存数量比stat_peak_memory
大,那么程序将当前使用的内存数量记录到stat_peak_memory属性。

INFO memory命令的used_memory_peak、used_memory_peak_human记录了服务器的内存峰值。

2.5 处理SIGTERM信号

在启动服务器时,Redis会为服务器进程的SIGTERM信号关联处理器sigtermHandler函数,这个信号处理器负责在服务器接到SIGTERM信号时,打开服务器状态的shutdown_asap标志。

sigtermHandler函数

每次serverCron函数运行时,程序都会对服务器状态的shutdown_asap属性进行检查,并根据属性的值决定是否关闭服务器。

2.6 管理客户端资源

serverCron每次执行都会调用clientsCron函数。

clientsCron函数会对一定数量的客户端进行以下两个检查:

clientsCron函数执行的检查

2.7 管理数据库资源

serverCron每次执行时都会调用databasesCron函数。这个函数会对服务器中的一部分数据库进行检查,删除其中的过期键,并在有需要时,对字典进行收缩操作。

2.8 执行被延迟的BGREWRITEAOF

在服务器执行BGSAVE期间,如果客户端向服务器发来BGREWRITEAOF命令,那么服务器会将BGREWRITEAOF命令的执行时间延迟到BGSAVE命令执行完毕后。

aof_rewrite_scheduled记录了服务器是否延迟了BGREWRITEAOF命令。

每次serverCron函数执行时,都会检查BGSAVE、BGREWRITEAOF是否正在执行,如果两个命令都没有在执行,并且aof_rewrite_scheduled的值为1,那么服务器就会执行之前被推延的BGREWRITEAOF命令。

2.9 检查持久化操作的运行状态

每次serverCron函数执行时,程序都会检查rdb_child_pid、aof_child_pid,只要其中一个属性的值不为-1,程序就会执行一次wait3函数,检查子进程是否有信号发来服务器进程:

子信号发来服务器进程

rdb_child_pid、aof_child_pid都为-1,表示服务器没有进行持久化操作,在这种情况下,程序执行以下三个检查:

rdb_child_pid、aof_child_pid都为-1

2.10 将AOF缓冲区的内容写入AOF文件

如果服务器开启了AOF持久化功能,并且AOF缓冲区里还有待写入的数据,那么serverCron会调用相应的程序,将AOF缓冲区的内容写入到AOF文件里。

2.11 关闭异步客户端

服务器会关闭那些输出缓冲区大小超出限制的客户端。

2.12 增加cronloops计数器的值

cronloops记录了serverCron函数执行的次数。

3. 初始化服务器

3.1 初始化服务器状态结构

初始化服务器的第一步就是创建一个struct redisServer类型的实例变量server作为服务器的状态,并为结构中的各个属性设置默认值。

初始化server变量的工作由redis.c/initServerConfig完成。

initServerConfig

以下是initServerConfig完成的主要工作。

initServerConfig完成的主要工作

3.2 载入配置选项

服务器在用initServerConfig初始化完server变量后,就开始载入用户给定的配置参数、配置文件,并根据用户设定的配置,对server变量相关属性的值修改。

3.3 初始化服务器数据结构

下图是服务器状态包含的其他数据结构。

服务器状态包含的其他数据结构

在这时,服务器将调用initServer函数,为上面的数据结构分配内存,并在有需要时,为这些数据结构设置、关联初始化值。

服务器到现在才初始化数据结构的原因在于,服务器必须先载入用户指定的配置选项,然后才能正确地对数据结构进行初始化。

服务器选择了将server状态的初始化分为两步执行,initServerConfig主要负责初始化一般属性,initServer主要负责初始化数据结构。

除了初始化数据结构外,initServer还进行了一些非常重要的设置操作,包括:

initServer进行的其他重要设置

当initServer执行完之后,服务器将打印Redis图标、Redis版本号。

3.4 还原数据库状态

在完成对服务器状态server变量的初始化后,服务器需要载入RDB、AOF文件,并根据文件记录的内容来还原服务器的数据库状态。

根据服务器是否启用了AOF持久化功能,服务器载入数据时所使用的目标文件会有所不同:

  1. 如果服务器启用了AOF持久化功能,那么服务器使用AOF文件来还原数据库状态。
  2. 如果服务器没有启用了AOF持久化功能,那么服务器使用RDB文件来还原数据库状态。

当完成数据库状态还原后,服务器将打印出载入文件并还原数据库状态所耗费的时长。

3.5 执行事件循环

在初始化最后一步,服务器将打印以下日志:

服务器初始化最后日志

并开始执行服务器的事件循环(loop)。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 220,492评论 6 513
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 94,048评论 3 396
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 166,927评论 0 358
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 59,293评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,309评论 6 397
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 52,024评论 1 308
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,638评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,546评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 46,073评论 1 319
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,188评论 3 340
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,321评论 1 352
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,998评论 5 347
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,678评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,186评论 0 23
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,303评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,663评论 3 375
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,330评论 2 358

推荐阅读更多精彩内容

  • 命令请求的执行过程 客户端发送一个set命令到收到回复期间的步骤如下 1.客户端向服务端发生命令请求SET KEY...
    简书徐小耳阅读 254评论 0 0
  • 数据库 多数据库结构 一个Redis实例可以支持多个数据库,当客户端与服务端连接并指定到某个数据库时,两者的结构如...
    宇宙最强架构师阅读 618评论 0 3
  • 一、Redis高可用概述 在介绍Redis高可用之前,先说明一下在Redis的语境中高可用的含义。 我们知道,在w...
    空语阅读 1,597评论 0 2
  • 1.Redis特性 1)速度快:数据存放在内存上、基于C语言实现、单线程架构预防多线程竞争问题;2)基于键值对的数...
    Sponge1128阅读 621评论 0 1
  • redis为何需要持久化 redis是一个键值对的内存数据库,它将数据及状态保存在内存中,保证了对数据的最快访问速...
    大傻_df4c阅读 182评论 0 0