• 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设计与实现》

  • 相关阅读:
    Android关机流程源码分析
    开关机画面
    android camera
    深入分析AIDL原理
    VMware 虚拟机里连不上网的三种解决方案
    查看Linux中自带的jdk ,设置JAVA_HOME
    Hadoop三种模式安装教程(详解)
    Java ArrayList遍历删除问题
    idea git的使用(四)git建立分支与合并分支
    SpringBoot非官方教程 | 终章:文章汇总
  • 原文地址:https://www.cnblogs.com/shoulinniao/p/13879116.html
Copyright © 2020-2023  润新知