Redis principle - Redis network model

The address of the original text is updated, and the reading effect is better!

Redis Principle - Redis Network Model | CoderMast Programming Mast icon-default.png?t=N5K3https://www.codermast.com/database/redis/redis-netword-model.html

think

Is Redis single-threaded or multi-threaded?

  • If it is only for the core business part of Redis (command processing part), it is single-threaded
  • If it is for Redis as a whole, then it is multi-threaded

During the version iteration process of Redis, multithreading support was introduced at two important time nodes:

  • Redis v4.0: Introduce multithreading to asynchronously process some time-consuming tasks, such as asynchronously delete commands unlike
  • Redis v6.0: Introduce multi-threading in the core network model to further improve the utilization of multi-core CPUs

Why does Redis choose single thread?

  • Persistence aside, Redis is a pure memory operation with a very fast execution speed. Its performance bottleneck is network latency rather than execution speed, so multithreading will not bring huge performance improvements.
  • Multi-threading can lead to excessive context switching, bringing unnecessary overhead
  • The introduction of multi-threading will face thread security issues, and it is necessary to introduce security measures such as thread locks, which will increase the complexity of implementation, and the performance will be greatly reduced

#network model

Redis improves network performance through IO multiplexing, and supports various multiplexing implementations, and encapsulates these implementations to provide a unified high-performance event library API library AE:

  • yes_epoll
  • ae_evport
  • ae_kqueue
  • ae_select (generic)

These are the four implementations in Redis, and different implementations are selected according to different operating systems.

The specific APIs mainly include the following:

  • aeApiCreate: Create a multiplexing program, such as epoll_create
  • aeApiResize
  • aeApiFree
  • aeApiAddEvent: register FD, such as epoll_ctl
  • aeApiDelEvent: delete FD
  • aeApiPoll: Wait for FD to be ready, such as epoll_wait
  • aeApiName:select、poll

Unique API in ae_evport implementation

  • aeApiLookupPending
  • aeApiAssociate

Which implementation to use can be selected in the ae.c file.

 

# Single-threaded network model

The previous network models of Redis 6 were all single-threaded, and the whole process of Redis single-threaded network model:

 

  • In aeApiPoll, it will judge whether it is readable by the client or the server , and call different processors
  • When the client Client Socket initiates a connection request, the server Server Socket is readable and triggers the connection response processor tcpAccepthandler
  • When the client Client Socket initiates a command, the client can read it and trigger the command request processor readQueryFromClient
  • When the client is writable, it is handled by the command reply handler.

core

In essence, it is  the application of IO multiplexing + event dispatching  .

The server socket continuously receives the response of the client socket, and then dispatches it to the corresponding processor for processing according to different event types.

#Multithreaded network model

Redis version 6.0 introduced multi-threading to improve the efficiency of IO read and write. Therefore,   multi-threading is used when parsing client commands and  writing response results . The core command execution and IO multiplexing modules are still executed by the main thread.

Through the analysis of the single-threaded network model, the main performance bottleneck lies in the two aspects of command read and write processing and command response output.

 

Therefore, Redis introduces multi-threading in the two parts of command read processing and command response.

Notice

Performance bottlenecks are generally the impact of IO or network requests.

#underlying implementation

  • main

// server.c
int main(
    int argc,
    char **argv
){
    // ...
    // 初始化服务
    initServer();
    // ...
    // 开始监听事件循环
    aeMain(server.el);
    // ...
}
  • initServer

void initServer(void){
    // ...
    // 内部会调用 aeApiCreate(eventLoop),类似epoll_create
    server.el= aeCreateEventLoop(server.maxclients+CONFIG_FDSET_INCR);
    //...

    // 监听TCP端口,创建ServerSocket,并得到FD
    listenToPort(server.port,&server.ipfd)
    // ...

    // 注册 连接处理器,内部会调用 aeApiAddEvent(&server.ipfd)监听FD
    createSocketAcceptHandler(&server.ipfd,acceptTcpHandler)

    // 注册 ae_api_poll 前的处理器
    aeSetBeforeSleepProc(server.el,beforeSleep);
}


  • aeMain

void aeMain(aeEventloop*eventloop){
    eventLoop->stop = 0;
    // 循环监听事件
    while (!eventLoop->stop){
        aeProcessEvents(
            eventLoop,
            AE_ALL_EVENTS | 
            AE_CALL_BEFORE_SLEEP |
            AE_CALL_AFTER_SLEEP);
    }
}
  • aeProcessEvents

int aeProcessEvents(aeEventLoop *eventLoop,int flags){
    // ... 调用前置处理器 beforesleep
    eventLoop->beforesleep(eventLoop);
    // 等待FD就绪,类似 epoll_wait
    numevents = aeApiPoll(eventLoop,tvp);

    for (j = 0; j < numevents; j ++){
        // 遍历处理就绪的 FD,调用对应的处理器
    }
}
  • acceptTcpHandler

void acceptTcpHandler( ... ){
    // ...
    // 接收 socket 连接,获取 FD 
    fd = accept(s,sa,len);

    // ... 
    // 创建 connection ,关联 fd
    connection *conn = connCreateSocket();
    conn.fd = fd;

    // ...
    // 内部调用 aeApiAddEvent(fd,READABLE)
    // 监听 socket 的FD读事件,并绑定读处理器readQueryFromClient
    connSetReadHandler(conn, readQueryFromClient);
}
  • readQueryFromClient

void readQueryFromClient(connection *conn){
    // 获取当前客户端,客户端中有缓冲区用来读和写
    client *c = connGetPrivateData(conn);
    // 获取c->querybuf缓冲区大小
    long int qblen = sdslen(c->querybuf);
    // 读取请求数据到 c->querybuf 缓冲区
    connRead(c->conn,c->Guerybuf+qblen,readlen);
    // 解析缓冲区字符串,转为Redis命令参数存入 c->argv 数组
    processInputBuffer(c);
    // ...
    // 处理 c->argv 中的命令
    processCommand(c);
}
  • processCommand

int processCommand(client *c) {
    // 根据命令名称,寻找命令对应的command,例如 setCommand
    c->cmd = c->lastcmd = lookupCommand(c->argv[0]->ptr);
    // ...
    // 执行command,得到响应结果,例如ping命令,对应pingCommand
    c->cmd->proc(c);
    // 把执行结果写出,例如ping命令,就返回"pong"给cLient
    // shared.pong是 字符串"pong"的SDS对象
    addReply(c, shared.pong);
}
  • addReply

void addReply(client *c,robj *obj) {
    // 尝试把结果写到 c-buf 客户端写缓存区
    if (_addReplyToBuffer(c,obj->ptr,sdslen(obj->ptr)) != C_OK)
    // 如果c->buf写不下,则写到 c->reply,这是一个链表,容量无上限
    _addReplyProtoToList(c,obj->ptr,sdslen(obj->ptr));
    // 将客户端添加到server.clients_pending_write这个队列,等待被写出
    listAddNodeHead(server.clients_pending_ write,c);
}

おすすめ

転載: blog.csdn.net/qq_33685334/article/details/131345683