Redis网络模型总结

Redis网络模型总结

在这里插入图片描述

ae.c文件判断当前操作系统是否支持ae_evport,如果支持则引入ae_evport.c;如果不支持,则判断是否支持ae_poll,如果支持则引入ae_poll;以此类推,如果都不支持,则引入ae_select.c。

不同操作系统支持不同的IO多路复用实现方案,所以需要引入不同的 .c 文件

  1. epoll:linux
  2. evport:Solaris
  3. kqueue:Unix、Macos
  4. select:基本上所有操作系统都支持

redis实现这四个 .c 文件时,都给它们暴露了统一的 API 接口。即都暴露了如:aeApiCreate(aeEventLoop *)aeApiResize(aeEventLoop *, int)aeApiFree(aeEventLoop *)aeApiAddEvent(aeEventLoop *, int, int)……等接口。

在这里插入图片描述

流程如下:

  1. 创建serverSocket,并得到对应的FD(ssfd),serverSocket用来监听客户端的连接请求。
  2. 将ssfd注册到红黑树中,用来监听serverSocket对应的FD。
  3. 客户端发起连接请求时,serverSocket会产生可读事件。
  4. 服务端接收到可读事件后先判断是不是ssfd可读,如果是则说明这是一个客户端连接请求,从而调用accept()方法获取客户端的socket,并将客户端的FD注册到红黑树中。
  5. 之后服务端接收到可读事件后,有可能是ssfd产生的,也有可能是客户端fd产生的。如果是ssfd产生的读请求,说明有新的客户端连接请求,如果是客户端fd产生的读请求,说明是客户端发起的读写数据请求,redis解析请求,并返回响应结果。

Redis源码解读

1. redis服务启动入口

在这里插入图片描述

2. 初始化服务

在这里插入图片描述

  1. aeCreateEventLoop:创建事件循环(创建epoll实例,也就是红黑树+事件就绪列表),底层会调用aeApiCreate方法;
  2. 创建serverSocket,并得到对应的FD(ssfd),通过TCP端口监听客户端连接请求;
  3. createSocketAcceptHandler(&server.ipfd, acceptHandler):注册FD(也就是刚刚创建的ssfd),内部会调用aeApiAddEvent方法将FD注册到红黑树中。这里注册的是serverSocket对应的FD,用来监听客户端连接请求;一旦ssfd上发生了可读事件,就会去调用对应的连接处理器acceptHandler来处理连接请求;
  4. aeSetBeforeSleepProc(server.el, beforeSleep):调用epoll_wait之前的准备工作;

3. 开始监听事件循环(监听epoll实例)

在这里插入图片描述

  1. initServer方法结束后,已经完成了epoll_wait之前的准备工作;

  2. aeMain方法开始监听initServer中创建的epoll实例;

  3. aeMain方法底层会去通过while循环调用aeProcessEvents方法,aeProcessEvents方法底层会调用aeApiPoll(epoll_wait),用来等待注册的FD就绪。

    所以说aeMain方法本质上还是循环调用epoll_wait方法等待FD就绪

  4. 调用aeApiPoll方法之前会去调用beforeSleep方法,这个方法是用来将过期的key进行删除的

  5. aeApiPoll方法返回numEvents,numEvents代表就绪的FD数量,然后在for循环中遍历就绪的FD列表,并调用对应的处理器

    比如serverSocket对应的ssfd对应的处理器为acceptHandler(连接处理器)

    在这里插入图片描述

    可以看到连接处理器中先接受客户端的连接socket并关联对应的fd;
    接着调用connSetReadHandler方法注册客户端的FD,底层通过调用aeApiAddEvent方法实现;注册的是客户端FD的读事件(注意这里的读事件并不是代表客户端只能向Redis发送读请求;而是代表客户端可以发送读请求也可以发送写请求,Redis服务端获取到可读事件后,去获取客户端的读写请求,进而执行对应的读写请求)
    一旦客户端FD发送了可读事件,就会去调用FD绑定的读处理器readQueryFromClient去处理客户端的读写请求。

至此流程如下:
在这里插入图片描述

4. 命令请求处理器

  1. readQueryFromClient方法:
    在这里插入图片描述

这个函数的主要任务是从客户端读取查询数据,并将其存储到客户端的缓冲区中,然后解析这些数据并执行对应的命令。

  • 客户端缓冲区client *c = connGetPrivateData(conn); 获取到当前连接所对应的客户端结构体(Redis为每个客户端连接都创建了客户端结构体,用来存放客户端的各类信息,包括客户端的请求信息等等),这个结构体中有一个用于存储从客户端读取数据的缓冲区 querybuf
  • 读取数据connRead(c->conn, c->querybuf + qblen, readlen); 从客户端连接c→conn中读取数据,追加到 querybuf 的末尾(读取的数据就是客户端发起的读写请求命令,将读写请求存放到对应客户端结构体的querybuf中)。
  • 解析和处理命令*processInputBuffer(c); 解析读取到的数据,将其转化为 Redis 的命令参数数组 argv*。接着调用 processCommand(c); 方法处理这些命令。
    在这里插入图片描述
  1. processCommand方法:

这个函数负责根据客户端的命令参数,找到对应的 Redis 命令并执行。

  • 查找命令c->cmd = c->lastcmd = lookupCommand(c->argv[0]->ptr); 通过命令名称查找到相应的命令处理函数c->cmd(例如 setCommandgetCommand)。

    在这里插入图片描述

  • 执行命令c->cmd->proc(c); 执行该命令处理函数,处理逻辑会依据具体的命令类型生成对应的结果。

  • 回复结果*addReply(c, shared.pong); 将命令的执行结果*(例如 PING 命令的 PONG 响应)准备返回给客户端

  1. addReply:

这个函数将命令执行的结果写入客户端结构体client的发送缓冲区(不是直接写到客户端的socket),并在必要时将缓冲区内容写出。

  • 写入缓冲区_addReplyToBuffer(c, obj->ptr, sdslen(obj->ptr)); 尝试将结果写入客户端client的写缓冲区 c->buf
  • 处理缓冲区满的情况:如果缓冲区已满,结果数据会被追加到客户端的 c->reply 链表中,这个链表没有容量限制(可以知道c→buf以及c→reply都是客户端client的输出缓冲区)。
  • 等待写出将客户端对象 c 添加到 server.clients_pending_write 队列中(这个队列是redis中已经定义好的一个队列,用来存放那些等待输出的客户端client),等待 Redis 的事件循环将这些数据发送给客户端。

在这里插入图片描述

注意:此时等待输出的客户端对象还是存放在clients_pending_write队列中,并没有真正的输出到客户端的socket中

5. beforeSleep方法

在这里插入图片描述

  1. 创建迭代器 listiter,指向存放client对象的队列clients_pending_write列表的头部。这个列表存放的是有待写出响应数据的客户端;
  2. while (listNode *ln = listNext(&li)) 通过迭代器 li 遍历所有待写出的客户端,每个 ln 是一个链表节点,包含了一个指向客户端结构体的指针 c ;
  3. connSetWriteHandlerWithBarrier(c->conn, sendReplyToClient, ae_barrier); 为当前客户端 c 的连接设置一个写事件处理器。该函数的作用是:
    • 监听:内部调用 aeApiAddEvent(fd, WRITEABLE)告诉事件循环(epoll)需要监听客户端连接 c->conn(FD) 的写事件(即客户端 socket 变得可写)
    • 绑定处理器:将写事件处理器 sendReplyToClient 绑定到这个连接上。当写事件发生时(客户端socket变的可写时)sendReplyToClient 将被调用,以便clients_pending_write列表中客户端的响应数据写入对应客户端的 socket

完整流程如下:

在这里插入图片描述

Redis多线程模型

单线程模型的性能瓶颈

  1. 解析客户端结构体queryBuf中的数据为Redis命令;
  2. 将Redis响应数据排队依次写入对应的客户端socket;

Redis 6.0版本中引入了多线程,目的是为了提高IO读写效率。因此在解析客户端命令、写响应结果时采用了多线程。核心的命令执行、IO多路复用模块依然是由主线程执行
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值