Redis使用及源码剖析-13.Redis客户端-2021-1-27

2022-02-22 13:42:37 浏览数 (1)

文章目录

  • 前言
  • 一、客户端结构体简介
    • 1.套接字描述符
    • 2.名字
    • 3.标志
    • 4.输入缓冲区
    • 5.命令和命令参数
    • 6.命令的实现函数
    • 7.输出缓冲区
    • 8.身份认证
    • 9.时间
    • 10.完整结构体
  • 二、客户端创建与关闭
    • 1.普通客户端创建
    • 2.普通客户端关闭
    • 3.lua脚本的伪客户端
    • 4.aof文件的伪客户端
  • 总结

前言

Redis服务端是典型的一对多程序,可以为多个客户端提供服务,Redis服务端结构体中的clients链表中保存了所有的客户端信息,如下所示:

代码语言:javascript复制
struct redisServer {
    // 一个链表,保存了所有客户端状态结构
    list *clients;              /* List of active clients */
};

本文对Redis的客户端相关内容进行了简要介绍。

一、客户端结构体简介

1.套接字描述符

客户端的fd属性记录了客户端的套接字描述符,如下所示:

代码语言:javascript复制
/* With multiplexing we need to take per-client state.
 * Clients are taken in a liked list.
 *
 * 因为 I/O 复用的缘故,需要为每个客户端维持一个状态。
 *
 * 多个客户端状态被服务器用链表连接起来。
 */
typedef struct redisClient {

    // 套接字描述符
    int fd;
} redisClient;

fd除了可以说客户端的已连接套接字外,还可以是-1,此时表明是一个伪客户端。伪客户端表明命令来自AOF文件或者lua脚本。

2.名字

默认情况下连接到服务器的客户端是没有名字的,可以通过client setname命令可以为客户端设置一个名字,设置的名字保存在redisclient的name成员中,如下所示:

代码语言:javascript复制
typedef struct redisClient {
    // 客户端的名字
    robj *name;             /* As set by CLIENT SETNAME */
} redisClient;

3.标志

redisClient 的flags成员记录了客户端的角色和状态,如下所示:

代码语言:javascript复制
typedef struct redisClient {
     // 客户端状态标志
    int flags;              /* REDIS_SLAVE | REDIS_MONITOR | REDIS_MULTI ... */
} redisClient;

flags属性的可能取值如下所示:

4.输入缓冲区

redisClient中的输入缓冲区保存客户端发送的命令请求,最大不能超过1GB,如下所示:

代码语言:javascript复制
typedef struct redisClient {
    // 查询缓冲区
    sds querybuf;
} redisClient;

5.命令和命令参数

命令保存到querybuf后,服务端会对命令进行解析,将命令参数和参数个数保存到argv成员和argc成员中,如下所示:

代码语言:javascript复制
typedef struct redisClient {
    // 参数数量
    int argc;

    // 参数对象数组
    robj **argv;
} redisClient;

6.命令的实现函数

服务端将参数及个数放入argv和argc成员以后,argv[0]即为命令名称,服务端会根据命令表查找命令的实现函数,命令表如下所示:

代码语言:javascript复制
/*
 * Redis 命令
 */
struct redisCommand {

    // 命令名字
    char *name;

    // 实现函数
    redisCommandProc *proc;

    // 参数个数
    int arity;

    // 字符串表示的 FLAG
    char *sflags; /* Flags as string representation, one char per flag. */

    // 实际 FLAG
    int flags;    /* The actual flags, obtained from the 'sflags' field. */

    /* Use a function to determine keys arguments in a command line.
     * Used for Redis Cluster redirect. */
    // 从命令中判断命令的键参数。在 Redis 集群转向时使用。
    redisGetKeysProc *getkeys_proc;

    /* What keys should be loaded in background when calling this command? */
    // 指定哪些参数是 key
    int firstkey; /* The first argument that's a key (0 = no keys) */
    int lastkey;  /* The last argument that's a key */
    int keystep;  /* The step between first and last key */

    // 统计信息
    // microseconds 记录了命令执行耗费的总毫微秒数
    // calls 是命令被执行的总次数
    long long microseconds, calls;
};
typedef struct redisClient {
    // 记录被客户端执行的命令
    struct redisCommand *cmd;
} redisClient;

7.输出缓冲区

命令执行完毕后,执行结果会存放在输出缓冲区中。输出缓冲区分为固定缓冲区和可变缓冲区,固定缓冲区用于保存长度较短的回复,可变缓冲区用于回复较长的回复,是一个链表对象,将回复放在链表的字符串中。缓冲区定义如下:

代码语言:javascript复制
typedef struct redisClient {
    /* 固定缓冲区 */
    // 回复偏移量
    int bufpos;
    // 回复缓冲区
    char buf[REDIS_REPLY_CHUNK_BYTES];

    // 可变缓冲区
    list *reply;
} redisClient;

8.身份认证

redisclient的authenticated成员用于记录客户端是否通过了身份验证,如下所示:

代码语言:javascript复制
typedef struct redisClient {
    // 当 server.requirepass 不为 NULL 时
    // 代表认证的状态
    // 0 代表未认证, 1 代表已认证
    int authenticated;      /* when requirepass is non-NULL */
} redisClient;

当服务端启动了身份认证后,如果客户端的authenticated属性是0,则除了身份认证命令auth以外,其余命令服务器均会拒绝执行。

9.时间

客户端记录了几个和时间有关的属性,如下所示:

代码语言:javascript复制
typedef struct redisClient {
    // 创建客户端的时间
    time_t ctime;           /* Client creation time */
    // 客户端最后一次和服务器互动的时间
    time_t lastinteraction; /* time of the last interaction, used for timeout */
    // 客户端的输出缓冲区超过软性限制的时间
    time_t obuf_soft_limit_reached_time;
} redisClient;

10.完整结构体

完整的redisclient结构体定义如下:

代码语言:javascript复制
/* With multiplexing we need to take per-client state.
 * Clients are taken in a liked list.
 *
 * 因为 I/O 复用的缘故,需要为每个客户端维持一个状态。
 *
 * 多个客户端状态被服务器用链表连接起来。
 */
typedef struct redisClient {

    // 套接字描述符
    int fd;

    // 当前正在使用的数据库
    redisDb *db;

    // 当前正在使用的数据库的 id (号码)
    int dictid;

    // 客户端的名字
    robj *name;             /* As set by CLIENT SETNAME */

    // 查询缓冲区
    sds querybuf;

    // 查询缓冲区长度峰值
    size_t querybuf_peak;   /* Recent (100ms or more) peak of querybuf size */

    // 参数数量
    int argc;

    // 参数对象数组
    robj **argv;

    // 记录被客户端执行的命令
    struct redisCommand *cmd, *lastcmd;

    // 请求的类型:内联命令还是多条命令
    int reqtype;

    // 剩余未读取的命令内容数量
    int multibulklen;       /* number of multi bulk arguments left to read */

    // 命令内容的长度
    long bulklen;           /* length of bulk argument in multi bulk request */

    // 回复链表
    list *reply;

    // 回复链表中对象的总大小
    unsigned long reply_bytes; /* Tot bytes of objects in reply list */

    // 已发送字节,处理 short write 用
    int sentlen;            /* Amount of bytes already sent in the current
                               buffer or object being sent. */

    // 创建客户端的时间
    time_t ctime;           /* Client creation time */

    // 客户端最后一次和服务器互动的时间
    time_t lastinteraction; /* time of the last interaction, used for timeout */

    // 客户端的输出缓冲区超过软性限制的时间
    time_t obuf_soft_limit_reached_time;

    // 客户端状态标志
    int flags;              /* REDIS_SLAVE | REDIS_MONITOR | REDIS_MULTI ... */

    // 当 server.requirepass 不为 NULL 时
    // 代表认证的状态
    // 0 代表未认证, 1 代表已认证
    int authenticated;      /* when requirepass is non-NULL */

    // 复制状态
    int replstate;          /* replication state if this is a slave */
    // 用于保存主服务器传来的 RDB 文件的文件描述符
    int repldbfd;           /* replication DB file descriptor */

    // 读取主服务器传来的 RDB 文件的偏移量
    off_t repldboff;        /* replication DB file offset */
    // 主服务器传来的 RDB 文件的大小
    off_t repldbsize;       /* replication DB file size */
    
    sds replpreamble;       /* replication DB preamble. */

    // 主服务器的复制偏移量
    long long reploff;      /* replication offset if this is our master */
    // 从服务器最后一次发送 REPLCONF ACK 时的偏移量
    long long repl_ack_off; /* replication ack offset, if this is a slave */
    // 从服务器最后一次发送 REPLCONF ACK 的时间
    long long repl_ack_time;/* replication ack time, if this is a slave */
    // 主服务器的 master run ID
    // 保存在客户端,用于执行部分重同步
    char replrunid[REDIS_RUN_ID_SIZE 1]; /* master run id if this is a master */
    // 从服务器的监听端口号
    int slave_listening_port; /* As configured with: SLAVECONF listening-port */

    // 事务状态
    multiState mstate;      /* MULTI/EXEC state */

    // 阻塞类型
    int btype;              /* Type of blocking op if REDIS_BLOCKED. */
    // 阻塞状态
    blockingState bpop;     /* blocking state */

    // 最后被写入的全局复制偏移量
    long long woff;         /* Last write global replication offset. */

    // 被监视的键
    list *watched_keys;     /* Keys WATCHED for MULTI/EXEC CAS */

    // 这个字典记录了客户端所有订阅的频道
    // 键为频道名字,值为 NULL
    // 也即是,一个频道的集合
    dict *pubsub_channels;  /* channels a client is interested in (SUBSCRIBE) */

    // 链表,包含多个 pubsubPattern 结构
    // 记录了所有订阅频道的客户端的信息
    // 新 pubsubPattern 结构总是被添加到表尾
    list *pubsub_patterns;  /* patterns a client is interested in (SUBSCRIBE) */
    sds peerid;             /* Cached peer ID. */

    /* Response buffer */
    // 回复偏移量
    int bufpos;
    // 回复缓冲区
    char buf[REDIS_REPLY_CHUNK_BYTES];

} redisClient;

二、客户端创建与关闭

1.普通客户端创建

当有一个新的客户端连接到服务端时,服务端就会创建一个redisclient对象,并添加到redisserver的clients链表中(见前言)。

2.普通客户端关闭

当客户端退出或者被杀死、客户端发送的命令格式错误、客户端输出缓冲区大小超限时,客户端就会被关闭,对应的redisclient对象从链表中被移除。

3.lua脚本的伪客户端

服务器在初始化时会创建负责执行lua脚本命令的伪客户端,伪客户端保存在redisserver的lua_client中,如下所示:

代码语言:javascript复制
typedef struct redisClient {
    // 复制执行 Lua 脚本中的 Redis 命令的伪客户端
    redisClient *lua_client;   /* The "fake client" to query Redis from Lua */
} redisClient;

这个客户端在服务器运行时一直存在,服务器退出时才关闭。

4.aof文件的伪客户端

服务器在载入aof文件时,会创建执行aof命令的伪客户端,载入完成后立即关闭。

总结

本文对redis客户端结构的主要成员做了简要介绍,如有不当,请多多指正。

0 人点赞