Redis 事件驱动框架(ae.c/ae.h)深度解析

之前咱们用 “超市收银员” 的例子,简单看懂了 ae 模块是 Redis 的 “多任务神器”。现在咱们再往深走一层,不用复杂代码,只拆它的 “核心运作逻辑”—— 搞懂它怎么做到 “一个收银员(单线程)高效处理一堆顾客(多请求)”。

一、先明确:ae 模块到底要解决什么问题?

Redis 刚诞生时,有个关键需求:用单线程处理成千上万的客户端连接。如果用 “一个连接派一个线程” 的方式,线程多了会互相抢资源(比如内存、CPU),反而变慢。

ae 模块的作用,就是给单线程装上 “三头六臂”—— 让它能同时 “监听” 很多连接,哪个连接有请求就处理哪个,还能按时做定时任务(比如清理过期数据)。

二、ae 模块的 “三大核心零件”

就像一台机器有几个关键零件,ae 模块也有三个 “缺一不可” 的核心组件,咱们一个个看:

1. 零件 1:事件循环(aeEventLoop)——“工作总调度台”

你可以把它想象成收银员的 “工作台”,上面放着所有要处理的任务清单。源码里它是个结构体(简化后):

struct aeEventLoop {int maxfd;                  // 目前最多有多少个“顾客呼叫铃”(最大文件描述符)aeFileEvent \*events;        // 所有“呼叫铃”的清单(文件事件列表)aeTimeEvent \*timeEventHead; // 所有“定时提醒”的清单(时间事件链表)int stop;                   // 下班开关(1=停止,0=工作)};

关键作用

  • 管理所有 “待处理任务”(不管是客户端请求,还是定时任务);

  • 控制 “工作流程”:先看有没有呼叫铃响,再看有没有定时提醒到点,循环往复。

2. 零件 2:文件事件(aeFileEvent)——“顾客呼叫铃”

每个客户端连接(比如你用命令行连 Redis),在 ae 模块里都对应一个 “呼叫铃”—— 这就是文件事件。

简化结构体:

struct aeFileEvent {int mask;                   // 铃的类型(读铃/写铃,比如客户端发请求是“读铃”)aeFileProc \*rfileProc;      // 读铃响了要做的事(比如接收客户端发的命令)aeFileProc \*wfileProc;      // 写铃响了要做的事(比如给客户端返回结果)};

举个例子

当你在命令行输入 set name zhangsan 时:

  1. 你的客户端连接会触发 “读铃”(mask = 读事件);

  2. ae 模块会调用 rfileProc 函数,把你输入的命令接收到 Redis 里。

3. 零件 3:时间事件(aeTimeEvent)——“超市定时提醒”

Redis 里需要 “到点自动做” 的事,比如 “每 10 秒清理一次过期数据”“每天凌晨 3 点做 RDB 备份”,都靠时间事件实现。

简化结构体:

struct aeTimeEvent {long long id;               // 提醒的唯一编号(避免混乱)long long when\_sec;         // 到点时间(秒)long long when\_ms;          // 到点时间(毫秒,更精确)aeTimeProc \*timeProc;       // 到点要做的事(比如清理过期数据的函数)    struct aeTimeEvent \*next;   // 下一个提醒(链表结构,按时间排序)};

关键特点

  • 所有时间事件按 “到点时间” 排序,就像你的闹钟按时间先后排好;

  • 每次事件循环,只需要检查 “最早到点” 的提醒(链表头),不用遍历所有,效率高。

三、ae 模块的 “工作流程”:单线程怎么高效干活?

咱们用 “超市收银员” 的场景,再细化一遍 ae 模块的 “一天工作”—— 对应源码里的 aeMain 函数(事件循环主函数):

步骤 1:开店前准备(初始化事件循环)

  • 收银员先摆好 “工作台”(创建 aeEventLoop 实例);

  • 把超市大门的 “呼叫铃” 装上(注册监听端口的文件事件,比如 Redis 默认的 6379 端口);

  • 设好 “定时提醒”(比如 “每 10 秒查一次过期商品”)。

步骤 2:开始营业(进入事件循环)

收银员坐在工作台前,循环做以下 3 件事:

① 看 “最早的定时提醒还有多久到点”

比如现在有两个提醒:“10 秒后清理过期商品”“1 小时后备份”,那最早的是 10 秒后。

  • 这个时间决定了 “收银员最多等多久”—— 如果 10 秒内没人按呼叫铃,就等 10 秒后处理定时任务;如果期间有人按铃,就立刻处理。
② 等 “呼叫铃响”(监听文件事件)

收银员调用 aeApiPoll 函数(底层是 epoll/kqueue 等系统工具),“监听” 所有呼叫铃:

  • 如果没人按铃,就等第一步算好的时间(比如 10 秒);

  • 如果有人按铃(比如客户端发请求),就立刻知道是哪个铃响了(哪个客户端)、是读铃还是写铃。

③ 处理 “响了的铃” 和 “到点的提醒”
  • 先处理所有 “响了的呼叫铃”(文件事件):比如客户 A 要结账(读事件),就收他的钱;客户 B 要拿商品(写事件),就给他拿。

  • 再处理 “到点的定时提醒”(时间事件):比如 10 秒到了,就去清理过期商品。

步骤 3:关店(停止事件循环)

当 Redis 收到 “shutdown” 命令时,把 aeEventLoopstop 设为 1,事件循环结束,收银员下班。

四、ae 模块的 “性能秘密”:为什么单线程能处理万级连接?

关键靠底层的 “IO 多路复用” 技术 —— 对应源码里的 aeApi* 系列函数(比如 Linux 下的 ae_epoll.c)。

咱们用 “超市” 类比,解释这个技术:

  • 普通方式(没有 IO 多路复用):一个顾客一个收银员,1000 个顾客要 1000 个收银员,成本高还乱;

  • IO 多路复用方式:一个收银员守着所有顾客的 “呼叫铃”,哪个响了处理哪个 —— 相当于 “一个人同时监听很多连接”,不用开多线程。

Redis 会根据操作系统自动选最优的 “监听工具”:

  • Linux 用 epoll(最常用,效率最高,支持万级连接);

  • BSD 用 kqueue(和 epoll 类似,高效);

  • Windows 用 select(效率低,支持连接少,所以 Redis 在 Windows 上很少用)。

五、动手看源码:怎么快速找到 ae 模块的核心?

如果你想自己翻源码,不用全看,找这几个关键地方就行:

  1. ae.c 的 aeCreateEventLoop 函数:看事件循环怎么初始化;

  2. ae.c 的 aeProcessEvents 函数:看一次事件循环的完整流程(对应咱们说的步骤 2);

  3. ae.c 的 aeCreateTimeEvent 函数:看定时任务怎么创建(比如 server.c 里会调用它注册清理过期数据的任务);

  4. ae_epoll.c(Linux 下):看 epoll 怎么实现 “监听多个连接”(不用看懂所有代码,看 aeApiAddEvent 怎么加事件、aeApiPoll 怎么等事件)。

总结

ae 模块其实就是 Redis 的 “多任务调度中心”:

  • 靠 “事件循环” 统管所有任务;

  • 靠 “文件事件” 处理客户端连接;

  • 靠 “时间事件” 处理定时任务;

  • 靠 “IO 多路复用” 让单线程能高效处理万级连接。

理解了 ae 模块,你就懂了 Redis 单线程高性能的核心 —— 不是靠 “多线程堆资源”,而是靠 “聪明的任务调度”。

如果还想再深入,比如看 “epoll 具体怎么监听连接”,或者 “时间事件怎么精确到毫秒”,咱们可以继续拆!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/bicheng/98216.shtml
繁体地址,请注明出处:http://hk.pswp.cn/bicheng/98216.shtml
英文地址,请注明出处:http://en.pswp.cn/bicheng/98216.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

[能源化工] 面向锂电池RUL预测的开源项目全景速览

锂离子电池是新能源汽车、储能系统及便携式电子设备的核心能源部件,其剩余使用寿命(Remaining Useful Life,RUL)的准确预测直接关系到设备运行安全、维护成本优化和能源效率提升。RUL预测算法能够提前量化电池剩余可用时间&#x…

PEFT QLora Deepspeed Zero Stage 3 Offload Trainning

使用 accelerate deepspeed zero stage 3 offload 进行 sft trainning 的自动设备映射: GPU 训练计算 CPU 存储 run_peft_qlora_deepspeed_stage3.sh #!/bin/bashexport MAX_JOBS4 export OMP_NUM_THREADS4 export disable_exllamaTrue export CUDA_VISIBLE_DEVICES0,1 expor…

JAVA上门家政维修服务系统源码微信小程序+微信公众号+APP+H5

一、功能介绍用户端:精准分类、支持家政、维修、万能服务、一口价、报价、线上、各类家政服务、优惠专区、师傅入驻、商家入驻、我的需求、补费明细、我的投诉;师傅端:接单池、消息通知、接单管理、今日订单、师傅入驻、我的钱包、实名认证&a…

GCKontrol对嵌入式设备FPGA设计流程的高效优化

1 前言FPGA(Field-Programmable Gate Array,现场可编程逻辑门阵列)是一种可编程的半导体器件,因其硬件可重构性、硬件并行计算能力、低延迟和实时性的优势,广泛应用于数字电路设计、原型验证和系统加速等领域。但开发…

DBAPI免费版对比apiSQL免费版

DBAPI简介 零代码开发api服务,只需编写sql,就可以生成http api服务。支持api动态创建,兼容多种数据库。 适用于BI报表、数据可视化大屏的后端接口快速开发。 旨在为企业数据服务的发布提供完整解决方案 一、DBAPI免费版本支持1个数据源连接支…

CTFHub SSRF通关笔记8:数字IP Bypass 原理详解与渗透实战

目录 一、SSRF 二、数字IP原理 1、IP多进制 (1)十进制整数格式 (Dword / 长整数格式) (2)八进制格式 (Octal IP) (3)十六进制格式 (Hex IP) 2、SSRF绕过 三、渗透实战 1、打开靶场 2、尝试127.0.…

C++中双引号和单引号的区别(全面分析)

我在刷算法题的时候经常遇到,用了 出现警告或者使用" "直接报错,尤其是在字符串部分(py玩家后遗症/(ㄒoㄒ)/~~)在详细了解后总结一下加强记忆。 总的来说在 C 中,双引号 "" 和单引号 是完全不同…

Ubuntu20.04仿真 |iris四旋翼添加云台相机详述

申明: 1、本人使用的是Ubuntu20.04ros1gazeboxtdronepx4的仿真组合 2、为了使传感器模型和飞机模型解耦合,实现不同平台对传感器可直接调用,本系列博文涉及的所有传感器均不直接添加在相应平台的sdf当中,而是通过编写xxx_joint.…

《人工智能AI之机器学习基石》系列 第 16 篇:关联规则与数据挖掘——“啤酒与尿布”传奇背后的增长秘密

《人工智能AI之机器学习基石》⑯ 专栏核心理念: 用通俗语言讲清楚机器学习的核心原理,强调“洞察+ 技术理解 + 应用连接”,构建一个完整的、富有启发性的知识体系。 引言:藏在购物车里的“读心术” 朋友们,欢迎回到我们的AI基石之旅。 在过去的两次探索中,我们深入…

Spring Boot 的自动配置原理

Spring Boot 的自动配置是其 "约定大于配置" 理念的核心实现,它能自动配置 Spring 应用所需的各种组件,大幅减少手动配置。下面从核心注解、加载流程、条件过滤等方面详细讲解其原理,并结合关键源码说明。一、自动配置的入口&#…

谷歌云平台(Google Cloud Platform, GCP)介绍(全球领先的云计算服务平台,为企业和开发者提供包括计算、存储、数据分析、人工智能、机器学习、网络和安全等在内的全面云服务)

文章目录**1. GCP的核心优势****1.1 全球领先的基础设施****1.2 强大的数据分析和人工智能能力****1.3 卓越的安全性和合规性****1.4 灵活的定价模式****2. GCP的主要服务****2.1 计算服务****2.2 存储和数据库****2.3 网络服务****2.4 人工智能与大数据****2.5 安全与管理工具…

RISC-V异常机制和异常定位

不少人在调试RISC-V core时,面对异常的出现不知所措,不知道如何定位代码问题。这里将从RISC-V异常机制以及几个异常实例学习下。 1 异常机制 1.1 什么是异常 异常是软件程序员不得不要深入了解的,首先在学习异常机制前,对异常要…

c++中导出函数调用约定为__stdcall类型函数并指定导出函数名称

开发环境在Visual studio 2022版本下,为防止编译器重命名函数名称(会加上8等等乱七八糟的东西),我们对函数名称进行指定:一、新建.def文件,名称须与dll名称相同,并放在与cpp文件相同文件夹下&am…

Vision Transformer (ViT) :Transformer在computer vision领域的应用(二)

METHOD,论文主要部分 In model design we follow the original Transformer (Vaswani et al., 2017) as closely as possible. An advantage of this intentionally simple setup is that scalable NLP Transformer architectures – and their efficient implementations –…

AI 论文周报丨红队测试语言模型/多视角 3D 点追踪方法/蛋白质表示学习框架/密码学漏洞检测新框架……

近年来,已有若干方法尝试从单目视频实现 3D 点跟踪,然而由于在遮挡和复杂运动等挑战性场景中难以准确估计 3D 信息,这些方法的性能仍难以满足实际应用对高精度与鲁棒性的要求。 基于此,苏黎世联邦理工学院、卡内基梅隆大学联合提出…

STM32 通过USB的Mass Storage Class读写挂载的SD卡出现卡死问题

问题描述:使用stm32cubemx生成的sdio和usb Mass Storage Class的代码后,在USB_DEVICE\App\usbd_storage_if.c文件里面的接口调用以下函数出现卡死问题: SD_Driver.disk_initialize(0); SD_Driver.disk_read(lun, buf, blk_addr, blk_len) SD_…

Go语言中 error 接口与自定义错误类型的深入解析

在 Go 语言开发中,我们经常需要处理各种错误情况。Go 语言通过 error 接口提供了一套简洁而强大的错误处理机制。然而,当涉及到自定义错误类型时,许多开发者会遇到一些令人困惑的问题。本文将通过一个实际案例来深入探讨这个问题。 问题背景 …

字幕编辑工具推荐,Subtitle Edit v4.0.13发布:增强语音识别+优化翻译功能

大家好呀,不知道大家有没有做自媒体相关工作的呢,你们是不是也觉得剪辑视频时最头疼的往往不是画面而是字幕,时间轴对不上、格式不兼容、需要手动翻译,这些琐碎工作消耗的精力甚至超过剪辑本身。 当你试遍各种在线工具却发现要么…

【Java后端】Spring Boot 集成雪花算法唯一 ID

Spring Boot 实现基于雪花算法的分布式唯一 ID 生成器在分布式系统中,我们经常需要生成 全局唯一 ID,比如用户 ID、订单号、消息 ID 等。常见的方式有:数据库自增主键、UUID、Redis/Zookeeper 分布式 ID 服务、百度 UidGenerator、美团 Leaf …

C语言初尝试——洛谷

一、C数组:C 语言支持数组数据结构,它可以存储一个固定大小的相同类型元素的顺序集合。数组是用来存储一系列数据,但它往往被认为是一系列相同类型的变量。声明数组在 C 中要声明一个数组,需要指定元素的类型和元素的数量&#xf…