Redis(八)——客户端与服务器

一、客户端的属性

typedef struct redisClient{
    ...
... }redisClient;

1.套接字描述符

int fd;

标识客户端和伪客户端

伪客户端:fd = -1,两种情况,载入AOF文件并还原数据库状态;执行Lua脚本中包含的Redis命令。

客户端:fd > -1

2.名字

robj* name;

默认无名,指针指向空,通过【client list】看名字,通过【client setname】设置名字

3.标志

int flags;

标志客户端所处的状态,主从复制操作、

4.输入缓冲区

sds querybuf;

不可超过1G,否则关闭客户端,

5.命令和命令参数

robj** argv; int argc;

argv指向一个数组,每个项都是字符串对象,argc则表示数组长度

例如argv[0]="set",argv[1]="name",argv[2]="shoulinniao",argc=3;

6.命令的实现函数

struct redisCommand* cmd;

argv[0]是一个命令,在命令字典里找到,再将cmd指向这个命令的redisCommand,然后根据这个redisCommand以及argv、argc中的信息调用命令实现函数,执行命令。

7.输出缓冲区

执行命令后所得的回复会放在输出缓冲区,有2个输出缓冲区。

固定大小缓冲区:

char buf[ REDIS_REPLY_CHUNK_BYTES ];//16*1024=16KB

int bufpos;//保存已使用的字节数量

大小可变缓冲区:buf用完就启用这个

list* reply;//拉出一条有3个字符串对象的链表

8.身份验证

int authenticated;//0表示未通过验证,1表示通过验证

【auth ***】命令验证身份,状态为0时只能用这个命令。

9.时间

time_t ctime;//创建客户端的时间

time_t lastinteraction;//客户端和服务器的最后一次交互时间

time_t obuf_soft_limit_reached_time;//距离lastinteraction过了多少秒

二、客户端的创建与关闭

1.创建普通客户端

客户端使用connect函数连接到服务器时,服务器就会调用连接事件处理器,为客户端创建相应的客户端状态,并将这个客户端状态添加到服务器状态结构clients链表的末尾。

2.关闭普通客户端

  • 客户端进程退出或者被杀死
  • 客户端向服务器发送不符合协议格式的命令,被服务器关闭
  • 设置timeout超时配置选项
  • 客户端请求命令超过输入缓冲区的限制(1GB)  或 发给客户端的回复命令超过输出缓冲区大小(硬性限制:超过限制,马上被关闭;软性限制:超过软性限制但是不超过硬性限制,太久就被关闭,不久恢复就不会被关闭)

3.Lua脚本的伪客户端

struct redisServer{
    //...
    redisClient* lua_client;
    //...
};

服务器会在初始化时创建负责执行Lua脚本中包含Redis命令的伪客户端,并将这个伪客户端关联在服务器的lua_client属性中。

4.AOF文件的伪客户端

载入AOF文件时,服务器会创建伪客户端执行AOF文件中的Redis命令,载入完成后关闭。

三、命令请求的执行过程

1.发送命令请求

用户-----键入命令----->客户端-----将命令转换为协议格式----->服务器

2.读取命令请求

  • 读取协议格式的命令请求,并保存到输入缓冲区
  • 对输入缓冲区的命令进行分析,提取参数和个数保存到argv和argc里
  • 调用命令执行器执行客户端指定的命令

3.命令执行器

(1)根据argv[0]参数在命令表(一个字典)里查找命令,并将找到的命令保存到cmd属性里(cmd指向一个redisCommand)

(2)执行前检查。cmd指向是否为null?不为null的话redisCommand参数个数是否正确?是否通过身份验证?还有一些内存占用、持久化、订阅、Lua脚本、监视器等情况。

(3)调用命令的实现函数。根据指向的redisCommand执行操作,将产生的回复 保存在 输出缓冲区里,再关联命令回复处理器将 命令回复 返回给客户端。

(4)执行后续操作。若开启慢日志查询功能则补充日志;根据执行时长更新redisCommand结构的milliseconds属性,calls+1;如果开启AOF持久化功能,补充命令到AOF缓冲区;如果有从服务器正在复制,则将刚刚执行的命令传给所有从服务器。

4.命令回复处理器将 命令回复 返回给客户端并清除输出缓冲区

5.客户端接收回复并打印

服务器-----协议格式的命令回复----->客户端-----解析成人类看得懂的格式并打印----->用户

四、服务器的serverCron函数

默认每100ms执行一次,看看它干了啥

1.更新服务器时间缓存

struct redisServer{
    //..
    time_t unixtime;//秒级精度当前时间戳
    long long mstime;//毫秒级精度当前时间戳
    //..      
};

每100ms更新一次时间,精度不高。

  • 对于打印日志、更新服务器LRU时钟、决定是否执行持久化任务、计算服务器上线时间这类对时间精度要求不高的操作,将就着用;
  • 对于键过期、添加慢查询日志这类要求高精度时间的操作,服务器还是会再更新一次系统时间;

2.更新LRU时钟

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

4.更新服务器内存峰值记录

5.处理SIGTERM信号,即判断是否关闭服务器,关闭前执行RDB持久化操作

6.管理客户端资源,释放连接超时的客户端,释放超过一定长度的输入缓冲区并重建

7.删除过期键,必要时收缩字典

8.执行被延迟的BGrewriteAOF,该命令是因为服务器正在执行BGsave操作才会被延迟,记录一个标识(int aof_rewrite_scheduled),通过判断标识看有没有被延迟的BGrewriteAOF需要执行。

9.检查持久化的运行状态

10.将AOF缓冲区写入AOF文件

11.记录serverCron函数执行次数的 cronloops变量+1,用于模N实现“每执行N次serverCron函数就执行一次指定代码”的功能。

五、初始化服务器

服务器从启动到能够接受客户端请求,有一系列的操作

1.初始化服务器状态结构,默认各种选项,如运行ID、默认运行频率、端口号、默认文件路径、持久化条件、命令表等

2.载入配置选项,改掉某些默认值

3.初始化服务器数据结构,目前只造了一张命令表,还有其他数据结构需要初始化。

  • 保存客户端信息的server.clients链表
  • 包含服务器所有数据库的server.db数组
  • 保存频道订阅信息的server.pubsub_channels字典,保存模式订阅信息的server.pubsub_patterns链表
  • 用于执行Lua脚本的server.lua环境
  • 用于保存慢查询日志的server.slowlog属性

初始化这些数据结构需要先载入配置选项,可能有些数据结构被改掉了。

在对数据初始化的过程中,还干了一些其他设置操作,设置信号处理器、创建共享对象等

4.通过AOF或RDB文件回复数据库

5.执行事件循环,打印出日志【The server is now ready to accept connections on port 6379】即可以接受客户端的请求了。


参考&引用

《redis设计与实现》

原文地址:https://www.cnblogs.com/shoulinniao/p/13879116.html