一篇带你彻底搞明白Redis事件驱动

前言

Redis 是一个具有多种数据结构,基于内存的数据库,对数据的操作都是在内存中完成的,因此读写速度非常快,非常适合用于缓存、分布式锁等场景。

我们都知道 Redis 处理请求是单线程的,但是它的吞吐量却可以达到 10W/S,除了基于内存操作之外,还有一个很重要的特性,就是 Redis 基于事件的驱动模型。

IO 多路复用

IO 多路复用是操作系统提供给用户使用的一种网络连接处理方式。它可以通过一个线程处理大量客户端的 Socket 请求。

在介绍 IO 多路复用之前,我们先来看看其余的几种网络处理模型。

单线程网络处理模型

我们使用单线程来处理网络连接。当有连接进来时,线程会被唤醒处理请求,在进行读写操作时,线程会被阻塞,直到有数据可读可写,期间线程不能做任何事。

举个例子:我们去银行办事,柜台中只有一个工作人员,每次办事都需要填写表格。当第一个人来到的时候,在填写表格期间,后面又有人来到了,但是服务人员只能等第一个人填好表格,并服务完成之后,才能继续服务第二个人。

多线程网络处理模型

在单线程网络模型中,因为只有一个线程在处理请求,一旦陷入阻塞状态,后面的请求就会无法及时处理。那么我们可以来一个请求就创建一个线程,用创建的子线程去处理请求。任务处理完成后,我们再将线程释放。

举个例子:每来一个客户,我们就新招一个工作人员,然后让这个工作人员去处理新客户的业务。这样的话,不管来多少个客户,我们都可以同时去处理这些客户的业务,处理完之后,我们再将这些人解雇。(有点像外包)

但是这个模型有很多缺点:

  1. 银行的资源是有限的,不可能一直招人。当发现资源不足,无法再继续招人时,银行就破产了。
  2. 招人和解雇是需要花费时间和资源的,成本较高。在招人期间也无法服务客户。

线程池网络处理模型

在多线程模型中,我们每次招人做完事情之后就将它解雇了,在招人和解雇都需要花费大量的时间,成本很高。那么我们是否可以先招几个员工,然后就一直用这几个员工进行服务就可以了。对应起来就是,先创建多个线程,然后循环使用这些线程去处理网络请求。而这些线程组合起来就叫线程池。

EPOLL

Epoll 是 Linux 上对 IO 多路复用的一种实现,Mac OS 上是 Kqueue,它们本质上都是 IO 多路复用的不同实现方式。我们今天就拿 Epoll 作为例子进行讲解。

Epoll 是一个事件驱动的 IO 多路复用。

比如说:我去服装店定制一套西装,但是我不知道什么时候制作完成,我就跟老板说,制作完成之后打电话告诉我。西装制作完成是我关心的事件,老板就是事件通知器,当这个事件完成之后,老板就会打电话通知我。

在 Epoll 中,Epoll 就是事件通知器,可以向 Epoll 注册我们感兴趣的事件。

在服务端,启动一个服务器需要指定端口,我们会监听这个指定的端口。当监听这个端口的时候会产生一个 sfd,这个 sfd 就是网络监听的文件描述符。我们就可以将这个 sfd 注册进 Epoll,并告诉 Epoll,如果这个 sfd 发生了可读事件之后,来通知我。

客户端连接服务端的这个端口,Epoll 会检测到这个 sfd 发生了事件,并且在缓冲区可读的情况下,通知用户线程事件发生。我们就知道,sfd 发生了一个网络连接事件,我们就可以接收这个网络连接。

接收网络连接时,又会产生一个 cfd,表示客户端与服务器建立连接的文件描述符。我们可以将 cfd 注册可读事件到 epoll 中,当客户端发送的数据到达缓冲区时,Epoll 就会通知我们可读,我们就可以去读取数据。

与上面网络模型的不同之处在于:Epoll 会在缓存区有数据可读的情况才会通知用户线程,而在此期间,用户线程可以去做其他事件。这就避免造成用户线程读取数据,数据还未到达而无法做其他事情的情况。

Redis 事件循环

在 Redis 中,底层就是使用 IO 多路复用处理网络请求。并且创建一个 EventLoop 对象专门处理事件。

上图是 EventLoop 与 Epoll 的对应关系。往 Epoll 注册指定文件描述符的事件之前,会在 EventLoop 上记录该文件描述符监听的事件和事件通知时需要执行的函数。

当 Epoll 发现 sofd 上有可读事件发生,会通知给用户线程。用户线程可以拿到 sofd 在 EventLoop 上找到相应的记录,拿到记录上的函数,执行该函数。

启动事件循环

服务器启动

在服务端启动的时候,会绑定端口,并监听端口是否有连接进来。这个过程会完成以下几个步骤:

  1. 监听端口之后,会返回一个 sofd,表示这个监听端口的文件描述符
  2. 使用文件描述符在 EventLoop 中注册事件,并设置回调函数为 tcpAcceptHandler 。当有事件发生时,会调用这个函数。
  3. 使用文件描述符注册可读事件到 Epoll 中。

客户端连接

客户端要访问服务器,首先要跟客户端进行连接,使用 TCP 与服务器建立连接。这个过程会完成以下几个步骤

  1. 客户端与服务器完成 TCP 三次握手之后,Epoll 会检测到 sofd 上发生了可读事件。

  2. Epoll 会通知用户线程,该 sofd 发生了可读事件。

  3. 用户线程接收到该事件后,会通过 sofd 从EventLoop 中找到对应注册的事件,然后执行事件中的函数 tcpAcceptHandler 处理 TCP 连接。

  4. tcpAcceptHandler 执行过程:

    1. 通过 accept 函数接收连接,并返回 cfd。
    2. 使用 cfd 在 EventLoop 中注册读事件,并设置回调函数为 readQueryFromClient。
    3. 使用 cfd 在 Epoll 中注册读事件。

执行完这个过程后,就完成了客户端连接,并等待客户端发送请求。

客户端发送请求

客户端连接上服务器之后,接着就要发送请求,让服务器执行命令,返回数据。这个过程会完成以下几个步骤:

  1. 客户端通过连接发送请求,数据到达缓冲区。

  2. Epoll 检测到缓冲区有数据,通知用户线程 cfd 发生了可读事件。

  3. 用户线程通过 cfd 从 EventLoop 中拿到对应注册的事件,然后执行 readQueryFromClient 函数,处理此次请求。

  4. readQueryFromClient

    1. 调用 read 系统函数读取数据
    2. 解析命令
    3. 执行命令
    4. 返回响应

总结

Redis 通过 IO 多路复用技术,使用单线程就可以获取很高的并发访问。同时通过事件循环,将对应的功能都分散到不同的函数中,实现了高内聚。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值