redis源码之客户端请求处理(二)

简介

redis的设计是事件驱动模型,对于客户端连接的处理主要是对两个事件的处理:

  • 服务端的监听端口注册了可读事件处理函数acceptTcpHandler,用于处理客户端请求建立tcp连接;该函数会接收客户端请求并生成一个网络连接句柄,然后封装成一个client对象。
  • 在服务端接收客户端请求命令后,服务端通过epoll系统调用设置这个tcp连接句柄的可读事件处理函数为readQueryFromClient;readQueryFromClient会解析客户端的命令,并调用命令对应的处理函数进行处理。

tcp连接的封装

connection对象

// 将网络连接句柄封装成connection对象
struct connection {
    ConnectionType *type;
    ConnectionState state;
    short int flags;
    short int refs;
    int last_errno;
    void *private_data;
    ConnectionCallbackFunc conn_handler;
    ConnectionCallbackFunc write_handler;
    ConnectionCallbackFunc read_handler;
	   // 网络连接句柄
    int fd;
};

// 使用网络句柄fd封装一个conn对象
connection *connCreateAcceptedSocket(int fd) {
    connection *conn = connCreateSocket();
    conn->fd = fd;
    conn->state = CONN_STATE_ACCEPTING;
    return conn;
}


// 客户端connetion对象的type接口的实现
ConnectionType CT_Socket = {
    .ae_handler = connSocketEventHandler,
    .close = connSocketClose,
    .write = connSocketWrite,
    .read = connSocketRead,
    .accept = connSocketAccept,
    .connect = connSocketConnect,
    .set_write_handler = connSocketSetWriteHandler,
    .set_read_handler = connSocketSetReadHandler,
    .get_last_error = connSocketGetLastError,
    .blocking_connect = connSocketBlockingConnect,
    .sync_write = connSocketSyncWrite,
    .sync_read = connSocketSyncRead,
    .sync_readline = connSocketSyncReadLine,
    .get_type = connSocketGetType
};


// 设置conn->fd的可读事件处理函数为func
static int connSocketSetReadHandler(connection *conn, ConnectionCallbackFunc func) {
    if (func == conn->read_handler) return C_OK;

    conn->read_handler = func;
    if (!conn->read_handler)
        aeDeleteFileEvent(server.el,conn->fd,AE_READABLE);
    else
        if (aeCreateFileEvent(server.el,conn->fd,
                    AE_READABLE,conn->type->ae_handler,conn) == AE_ERR) return C_ERR;
    return C_OK;
}

client对象


// 将connection封装成client对象
typedef struct client {
    uint64_t id;            /* Client incremental unique ID. */
    connection *conn;
    int resp;               /* RESP protocol version. Can be 2 or 3. */
    redisDb *db;            /* Pointer to currently SELECTed DB. */
    robj *name;             /* As set by CLIENT SETNAME. */
    // 存放从客户端读取的数据
    sds querybuf;           /* Buffer we use to accumulate client queries. */
    // 下一个要处理的命令的第一个字符
    size_t qb_pos;          /* The position we have read in querybuf. */
    sds pending_querybuf;   /* If this client is flagged as master, this buffer
                               represents the yet not applied portion of the
                               replication stream that we are receiving from
                               the master. */
    size_t querybuf_peak;   /* Recent (100ms or more) peak of querybuf size. */
	   // argv数组长度
    int argc;               /* Num of arguments of current command. */
	   // 待处理的命令
    robj **argv;            /* Arguments of current command. */
    ...
}

// 使用conn封装一个client对象, 设置网络连接句柄的可读事件处理函数伟readQueryFromClient
client *createClient(connection *conn) {
    client *c = zmalloc(sizeof(client));

    /* passing NULL as conn it is possible to create a non connected client.
     * This is useful since all the commands needs to be executed
     * in the context of a client. When commands are executed in other
     * contexts (for instance a Lua script) we need a non connected client. */
    if (conn) {
        connNonBlock(conn);
        connEnableTcpNoDelay(conn);
        if (server.tcpkeepalive)
            connKeepAlive(conn,server.tcpkeepalive);
        connSetReadHandler(conn, readQueryFromClient);
        connSetPrivateData(conn, c);
    }

    selectDb(c,0);
    ...	
}

客户端与服务器的交互

服务端监听端口的事件处理函数注册

int main(int argc, char **argv)  server.c
  void initServer(void) server.c
    // 设置tcp服务监听端口的读事件处理函数为acceptTcpHandler
    int createSocketAcceptHandler(socketFds *sfd, aeFileProc *accept_handler) 
	     // 接受客户端的请求,并且将网络连接进行多层封装,最后封装成client对象
      // 并且设置客户端网络连接可读事件处理函数为readQueryFromClient
	     void acceptTcpHandler(aeEventLoop *el, int fd, void *privdata, int mask)
  • 服务器监听端口的可读事件处理函数为acceptTcpHandler,所以有新的客户端的连接请求的时候会调用改函数处理

客户端网络连接可读事件处理函数注册

void acceptTcpHandler(aeEventLoop *el, int fd, void *privdata, int mask)
       connection *connCreateAcceptedSocket(int fd) 
		        // 创建一个空的connection对象,connetion type为CT_Socket
           // type为CT_Socket,这里定义了socket不同事件的处理函数
           connection *connCreateSocket() 
       static void acceptCommonHandler(connection *conn, int flags, char *ip)
		        // 使用conn封装一个client对象,
				     // 设置网络连接句柄的可读事件处理函数为readQueryFromClient
		        client *createClient(connection *conn) 
				       // 客户端网络连接句柄可读事件的处理函数
				       void readQueryFromClient(connection *conn) 
					     

  • 客户端网络连接句柄的可读事件处理函数为readQueryFromClient

客户端数据处理

void readQueryFromClient(connection *conn)
  static int connSocketRead(connection *conn, void *buf, size_t buf_len) 
  void processInputBuffer(client *c) 
    // 从c->querybuf取一行数据,放入c->argv
    int processInlineBuffer(client *c)
    // 处理客户端命令
	   int processCommandAndResetClient(client *c)
	     // 进行一些检查后,执行命令
	     int processCommand(client *c)
		      // 调用命令处理函数c->cmd->proc
		      void call(client *c, int flags)
			      c->cmd->proc(c)

命令和对应的处理函函数

在服务器进程初始化的时候,会调用populateCommandTable生成一个命令和处理函数的映射字典server.commands。处理客户端的命令的时候,就会在server.commands中找到对应的命令对象redisCommand,调用其proc处理客户端的命令。比如set命令的proc是setCommand。而命令行的参数已经解析放入c->argv,命令参数个数为c->argc。

// 命令处理函数,如set命令的处理函数是setCommand
typedef void redisCommandProc(client *c);

// 命令
struct redisCommand {
    // 命令名称,如set
    char *name;
	   // 命令对应的处理函数, 如setCommand
    redisCommandProc *proc;
    int arity;
    // 字符串格式的标志
    char *sflags;   /* Flags as string representation, one char per flag. */
    // 位格式的标志
    uint64_t 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. */
    redisGetKeysProc *getkeys_proc;
    /* What keys should be loaded in background when calling this command? */
    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 */
	   // 命令调用次数计数
    long long microseconds, calls, rejected_calls, failed_calls;
    int id;     /* Command ID. This is a progressive ID starting from 0 that
                   is assigned at runtime, and is used in order to check
                   ACLs. A connection is able to execute a given command if
                   the user associated to the connection has this command
                   bit set in the bitmap of allowed commands. */
};

// 命令名称和对应的处理函数
struct redisCommand redisCommandTable[] = {
     ...
	   // set命令的处理函数是setCommand
     {"set",setCommand,-3,
     "write use-memory @string",
     0,NULL,1,1,1,0,0,0},
	    ...
}

// 将redisCommandTable中的命令插入到server.command列表中
// 后面可以快速根据命令名称查找到命令对象redisCommand
// 比如根据客户端发送的"set"命令,查找到"set"命令对应的redisCommand, 然后调用redisCommand.proc处理命令
/* Populates the Redis Command Table starting from the hard coded list
 * we have on top of server.c file. */
void populateCommandTable(void) {
    int j;
    int numcommands = sizeof(redisCommandTable)/sizeof(struct redisCommand);

    for (j = 0; j < numcommands; j++) {
        struct redisCommand *c = redisCommandTable+j;
        int retval1, retval2;

        /* Translate the command string flags description into an actual
         * set of flags. */
        // 根据sflags设置flags
        if (populateCommandTableParseFlags(c,c->sflags) == C_ERR)
            serverPanic("Unsupported command flag");

        c->id = ACLGetCommandID(c->name); /* Assign the ID used for ACL. */
        // 将命令放入字典server.commands
        retval1 = dictAdd(server.commands, sdsnew(c->name), c);
        /* Populate an additional dictionary that will be unaffected
         * by rename-command statements in redis.conf. */
        retval2 = dictAdd(server.orig_commands, sdsnew(c->name), c);
        serverAssert(retval1 == DICT_OK && retval2 == DICT_OK);
    }
}

posted @ 2024-09-27 16:52  董少奇  阅读(55)  评论(0)    收藏  举报