redis的事件处理机制
redis的事件处理机制
redis是单进程,单线程模型,与nginx的多进程不同,与golang的多协程也不同,“工作的工人”那么少,可那么为什么redis能这么快呢?
epoll多路复用
这里重点要说的就是redis的IO编程模型,首先了解下
为什么要有多路复用呢?
如果没有多路复用,一个线程只能监听一个端口的一个连接,这样这个效率比较低。当然我们有几种办法可以破除这个,一个是使用多线程模型,我们还是监听一个端口,但是一个请求进来,我们为其创建一个线程。但是这种消耗是比较大的。所以我们一直想办法,有没有办法一个线程监听多个端口,或者多个一个端口的多个连接(fd)。
这里再说说fd, 文件描述符(file descriptor)是内核为了高效管理已被打开的文件所创建的索引,其是一个非负整数(通常是小整数),用于指代被打开的文件,所有执行I/O操作(包括网络socket操作)的系统调用都通过文件描述符。每个连接请求上来,都会创建一个连接套接字,一个连接使用一个连接套接字。
对于监听端口,我们会有一个监听套接字,对应监听fd。我们所有的监听业务都是从监听这个套接字开始的。
那么如果我一个程序能同时监听多个连接套接字,是不是就很赞了。是的,这就是linux的io多路复用逻辑。但是这么多连接套接字,传递数据等是断断续续的,A连接接收一个包,B连接再接收一个包,A连接再接收一个包,B连接再接收一个包....如果我等着A连接把包都接收完再处理B,那效率是非常慢的。所以,这里我们就需要有一个通知机制,让有收到包的时候通知下处理线程。
linux的IO多路复用逻辑主要有三种:select, poll, epoll。
select
select模型监听的三个事件:读数据事件,写数据事件,异常事件。
使用select模型的步骤如下:
- 我们确定要监听的监听fd列表
- 调用select监听所有监听fd,阻塞线程。
- select只有当有事件出现并且有事件的fd已经等待完毕
- 如果是创建一个连接事件:
- 创建一个连接套接字,连接fd
- 将连接fd和监听fd集合放在一起
- 如果是一个读写事件:
- 遍历所有fd,判断是否是准备好的fd
- 如果是准备好的fd,进行业务读写逻辑
- 循环进入select。
select一次可以监听1024个文件描述符。
poll模型
poll传递给内核的是:
- 监听的fd集合
- 需要监听的事件类型
- 实际发生的事件类型
poll的模型逻辑是:
- 我们确定要监听的监听fd列表
- 调用poll监听所有监听fd,阻塞线程。
- poll只有当有事件出现才解除阻塞
- 如果是创建一个连接事件:
- 创建一个连接套接字,连接fd
- 将连接fd和监听fd集合放在一起
- 如果是一个读写事件:
- 遍历所有fd,判断是否是有读写事件的fd
- 如果fd有读写事件,进行业务读写逻辑
- 循环进入poll。
poll比select优秀在它没有了1024的限制了。但是还是有一些缺陷,就是必须要遍历所有fd。
epoll
epoll的数据结构类似poll,但是在调用epoll的时候,它不是返回发生了事件的fd个数,而是返回了所有发生的事件,这个事件中可以查出发生事件的fd。
所以epoll的逻辑模型是:
- 我们确定要监听的监听fd列表
- 调用epoll监听所有监听fd,阻塞线程。
- epoll只有当有事件出现才解除阻塞,并且返回事件列表
- 遍历事件列表:
- 如果是创建一个连接事件:
- 创建一个连接套接字,连接fd
- 将连接fd和监听fd集合放在一起,继续epoll
- 如果是一个读写事件:
- 处理这个事件
- 循环进入epoll。
说白了,epoll就是我们逻辑上能想到的最优的通知机制。一群人去排队,有多个事件发生,警察来了,那么就告诉警察有哪几个列发生了什么事件,警察一个个处理就行了。
Reactor模型
有了IO多路复用的机制,我们就可以实现一种模型,叫做Reactor模型了。Reactor模型的理论基础起源于这篇论文:
https://www.dre.vanderbilt.edu/~schmidt/PDF/reactor-siemens.pdf
里面最经典的一张图就是这个
reactor的五大角色:
- Handle(句柄或者是描述符)
- Synchronous Event Demultiplexer(同步事件分离器)
- Event Handler(事件处理器)
- Concrete Event Handler(具体事件处理器)
- Initiation Dispatcher(初始分发器)
简要来说,Reactor就是我们现在最正常理解的“事件驱动”,对,就是字面理解的那种。比如订阅一个kafka,我们会创建一个监听程序,监听kafka的某个topic,然后在监听程序中挂载几个处理不同消息的处理程序,每当有一个事件从topic进入的时候,我们就会有通过这个监听程序,通知我们的处理程序。处理程序来处理不同的消息。
这种所谓的通知机制,就叫做reactor。
这个kafka的例子,里面有一个监听事件的程序,它一定是一个同步的,一条消息来了,投递一个消息,就叫做 Synchronous Event Demultiplexer(同步事件分离器)。而这个消息,就是Handle(句柄或者是描述符)。我们需要将某个具体的事件处理函数,也就是上图的Concrete Event Handler(具体事件处理器) 挂载到监听的处理程序中。当然这里的每个Concrete Event Handler(具体事件处理器) 都必须遵照某种格式,比如定义了handle_event和get_handle接口。这种格式我们统称为Event Handler(事件处理器)。再回到监听事件,监听事件一定有一个挂载的具体map之类的结构,即哪个事件对应哪个处理程序,这个挂载的核心我们叫它Initiation Dispatcher(初始分发器)。
标准的处理流程描述如下:
当应用向Initiation Dispatcher注册Concrete Event Handler时,应用会标识出该事件处理器希望Initiation Dispatcher在某种类型的事件发生发生时向其通知,事件与handle关联
Initiation Dispatcher要求注册在其上面的Concrete Event Handler传递内部关联的handle,该handle会向操作系统标识
当所有的Concrete Event Handler都注册到 Initiation Dispatcher上后,应用会调用handle_events方法来启动Initiation Dispatcher的事件循环,这时Initiation Dispatcher会将每个Concrete Event Handler关联的handle合并,并使用Synchronous Event Demultiplexer来等待这些handle上事件的发生
当与某个事件源对应的handle变为ready时,Synchronous Event Demultiplexer便会通知 Initiation Dispatcher。比如tcp的socket变为ready for reading
Initiation Dispatcher会触发事件处理器的回调方法。当事件发生时, Initiation Dispatcher会将被一个“key”(表示一个激活的handle)定位和分发给特定的Event Handler的回调方法
Initiation Dispatcher调用特定的Concrete Event Handler的回调方法来响应其关联的handle上发生的事件
在这五种角色中,
其中的 Initiation Dispatcher(初始分发器) 是最重要的,我们也称其为Reactor。它本身定义了一些规范,同时提供了Handler的一些注册机制。
而Synchronous Event Demultiplexer(同步事件分离器) 在IO场景下,一般是由操作系统底层实现的,就是说操作系统底层必须能有这个能力,才能基于这个能力实现Reactor模型。在我们这个场景下,就是前面提到的linux的多路复用机制。
Handle(句柄或者是描述符)在IO场景下就是IO网络连接的fd。
而Event Handler(事件处理器) 和 Concrete Event Handler(具体事件处理器) 在IO场景下分为三种处理事件:连接事件,写事件,读事件。对于连接事件的处理器,我们称之为acceptor,读/写事件的处理器,我们统称为handler。
所以在IO场景下,Reactor 我们需要实现的三个关键角色为:reactor、acceptor、handler。
Redis的实现
在redis中,下面一张图就能说明其实现逻辑。
在redis中,有个reactor(叫做aeMain)接收客户端的redis请求。而在这个reactor中除了监听连接事件acceptor之外,还可以动态注册各种handler (aeCreateFileEvent)。当一个客户端请求进入的时候,调用 aeProcessEvents 来分发事件。
这个逻辑就很清晰了吧。整个就是redis的事件处理机制。
参考
https://cloud.tencent.com/developer/article/1420724
https://www.youtube.com/watch?v=PyiPA_liKKo
http://hxz.ink/2021/09/11/reactor-pattern/
https://time.geekbang.org/column/article/408491
redis的事件处理机制的更多相关文章
- 深入redis内部--事件处理机制
1. redis事件的定义 /* State of an event based program */ typedef struct aeEventLoop { int maxfd; /* highe ...
- Redis 缓存失效机制
Redis缓存失效的故事要从EXPIRE这个命令说起,EXPIRE允许用户为某个key指定超时时间,当超过这个时间之后key对应的值会被清除,这篇文章主要在分析Redis源码的基础上站在Redis设计 ...
- Redis的事件机制
目录 一.Redis的运行过程 二.事件数据结构 2.1 文件事件数据结构 2.2 事件事件数据结构 3.3 事件循环 三.事件的注册过程 3.1 文件事件的注册过程 3.2 时间事件的注册过程 四. ...
- java 事件处理机制:按下上下左右键控制小球的运动
/** * 加深对事件处理机制的理解 * 通过上下左右键来控制一个小球的位置 */package com.test3;import java.awt.*;import javax.swing.*;im ...
- Android事件处理机制
包括监听和回调两种机制. 1. 基于监听的事件处理: 事件监听包含三类对象,事件源,事件,事件监听器.Android的事件处理机制是一种委派式(Delegation)事件处理方式:普通组件(事件源)将 ...
- Android的两种事件处理机制
UI编程通常都会伴随事件处理,Android也不例外,它提供了两种方式的事件处理:基于回调的事件处理和基于监听器的事件处理. 对于基于监听器的事件处理而言,主要就是为Android界面组件绑定特定的事 ...
- Android的Touch事件处理机制
Android的Touch事件处理机制比较复杂,特别是在考虑了多点触摸以及事件拦截之后. Android的Touch事件处理分3个层面:Activity层,ViewGroup层,View层. 首先说一 ...
- IOS事件处理机制(关于触发者和响应者的确认)
事件处理机制 在iOS中发生触摸后,事件会加入到UIApplication事件队列(在这个系列关于iOS开发的第一篇文章中我们分析iOS程序原理的时候就说过程序运行后UIApplication会循环监 ...
- Java Swing事件处理机制
Java Swing的事件处理机制 Swing GUI启动后,Java虚拟机就启动三个线程,分别为主线程,事件派发线程(也是事件处理线程)和系统工具包线程. 主线程 :负责创建并显示该程序的初始界面: ...
随机推荐
- 组合&反射&面向对象内置函数
内容概要 组合 反射 面向对象的内置函数 异常 内容详细 一.组合 组合:在对象中定义一个属性,属性的值是另一个对象 除了继承父类的方法,这是获取另一个类中属性的另一种方式 如果想给学生对象添加课程属 ...
- soc AXI接口术语和特性
AXI接口术语和特性 1.outstanding 2.interleaving 3.out-of-oder 4.写数据可以优先于写地址 5.大小端 小端:低地址数据放在总线bus的低位. 大端:低地址 ...
- GAN实战笔记——第三章第一个GAN模型:生成手写数字
第一个GAN模型-生成手写数字 一.GAN的基础:对抗训练 形式上,生成器和判别器由可微函数表示如神经网络,他们都有自己的代价函数.这两个网络是利用判别器的损失记性反向传播训练.判别器努力使真实样本输 ...
- VUE项目二级菜单刷新时404 nginx
原因:vue项目的路径时虚拟路径,并不存在,所以用nginx去请求请求不到,所以vue项目做了两部调整: 1.所有的请求后端接口地址前端写死 2.nginx里把所有的接口转发后端删掉,并添加以下内容 ...
- python中try...excpet多种使用方法
1 print('\n欢迎使用除法计算器!\n') 2 3 while True: 4 try: 5 x = input('请你输入被除数:') 6 y = input('请你输入除数:') 7 z ...
- java运行原理、静态代理和动态代理区分
1.java的编译和运行原理: ■ 编译:将源文件 .java 文件,通过编译器(javac 命令) 编译成 字节码文件 .class 文件. ■ 运行,通过类加载器(以二进制流形式)把字节码加载进J ...
- Clickhouse 分布式表&本地表
CK 分布式表和本地表 ck的表分为两种: 分布式表 一个逻辑上的表, 可以理解为数据库中的视图, 一般查询都查询分布式表. 分布式表引擎会将我们的查询请求路由本地表进行查询, 然后进行汇总最终返回给 ...
- List分组和排序
1 //分组 2 var ll= lst.GroupBy(x => new { x.Id, x.Name }).Select(x => new 3 { 4 Key = x.Key, 5 I ...
- Markdown语法浅学
typora语法使用 1.字体 *斜体*,_斜体_ **粗体** ***加粗斜体*** ~~删除线~~ <u>下划线</u> ***分割线 , --- 2.标题 # 一级标题 ...
- 计算机网络实验 Labexercise1-1 Protocol Layers(Wireshark抓包与协议分析实验)
计算机网络实验 Labexercise1-1 Protocol Layers(Wireshark抓包与协议分析实验) 前言:本博客包含Windows10下安装wget.Wireshark. 一些有用的 ...