1.深度解析单线程的源码 Redis 如何做到每秒数万 QPS 的超高处理能力!
2.Redis——Epoll网络模型
3.Redis源码从哪里读起?
4.redis源码解读(一):事件驱动的源码io模型,为什么,源码是源码什么,怎么做
深度解析单线程的 Redis 如何做到每秒数万 QPS 的超高处理能力!
大家好,源码江苏区块链开发源码交付我是源码飞哥! 在网络编程中,源码提到高性能,源码很多人会想到多线程。源码但实际上,源码服务器端仅需单线程便可实现极高处理能力,源码Redis 源码就是这一模式的杰出代表,能够支撑每秒数万 QPS 的源码性能。今天,源码我们将深入探讨 Redis 核心网络模块的实现,揭示它是如何实现如此高性能的。 本文转自个人技术公众号「开发内功修炼」,关注以获取飞哥最新深度文章。同时,分享我撰写的电子书《理解了实现再谈网络性能》。该书正处于出版流程中。需要电子版的朋友可点击下载,或添加我本人: zhangyanfei,获取《理解了实现再谈网络性能》的链接。一、理解多路复用原理
在开始介绍 Redis 之前,让我们先简单介绍下 epoll。 在传统的同步阻塞网络编程模型中,进程线程的高开销是影响性能的根本原因。单个进程或线程只能处理一个用户请求,犹如一个人只能看管一只羊。当面对成千上万的请求时,这种模式的成本非常高。 性能提升的关键在于让众多请求复用同一个进程或线程,这就是多路复用。多路指的是众多用户连接,复用指的是对进程或线程的高效利用。以羊群为例,只需一名牧羊人即可管理。 实现多路复用需要特殊的比特币 网站源码 socket 事件管理机制,其中 epoll 是最高效和典型的方案。它的工作原理类似于一只牧羊犬,负责管理与进程或线程复用相关的 socket 事件。二、Redis 服务启动与初始化
理解了 epoll 的基本原理后,我们将探索 Redis 如何具体实现这一机制。通过 Github 即可获取 Redis 源码,我们关注 5.0.0 版本中的单线程版本实现。 整个 Redis 服务的核心入口位于 src/server.c 文件中,主要集中在 initServer 和 aeMain 函数。在 initServer 这个关键函数内,Redis 执行了以下三项重要操作。2.1 创建 epoll 对象
在 aeCreateEventLoop 函数中创建 epoll 对象,然后将其保存在 redisServer 的 aeEventLoop 成员中。接下来,我们深入了解 aeCreateEventLoop 的具体逻辑。 在 eventLoop 对象中,eventLoop->events 数组用于保存各种事件处理器。真正创建 epoll 对象的过程发生在 ae_epoll.c 文件的 aeApiCreate 函数中,通过调用 epoll_create 实现。2.2 绑定监听服务端口
Redis 的 listen 过程发生在 listenToPort 函数中,通过 bind 和 listen 系统调用完成。 Redis 支持多个端口,listenToPort 函数内部使用循环调用 anetTcpServer,逐步展开调用直到执行 bind 和 listen。2.3 注册事件回调函数
initServer 函数中,Redis 调用 aeCreateEventLoop 创建 epoll 对象后,通过 listenToPort 进行服务端口绑定。接着,调用 aeCreateFileEvent 注册 accept 事件处理器。 在 aeCreateFileEvent 函数中,Redis 为 listen socket 上的新用户连接注册了 acceptTcpHandler 作为读回调函数,负责处理新连接请求。三、Redis 事件处理循环
在上一节中,我们了解了 Redis 的启动初始化过程。接下来,Redis 将进入 aeMain 开始真正的用户请求处理。 aeMain 函数是一个无休止的循环,每次循环中执行如下关键操作。棋牌源码组件网站3.1 epoll_wait 发现事件
Redis 通过 epoll_wait 统一发现和管理可读(包括 listen socket 上的 accept 事件)、可写事件,并管理 timer 事件。 每当发现特定事件发生,epoll_wait 调用相应注册的事件处理函数进行处理。aeProcessEvents 函数封装了 epoll_wait 的逻辑。3.2 处理新连接请求
假设现在有新用户连接到达。listen socket 上的 rfileProc 注册的 acceptTcpHandler 负责处理新连接请求。在 acceptTcpHandler 中,主要完成接收连接、创建客户端连接对象和注册读事件处理器。 anetTcpAccept 负责调用 accept 接收新连接,acceptCommonHandler 为连接创建客户端连接对象,createClient 注册读事件处理器。3.3 处理客户连接上的可读事件
当用户发送命令时,Redis 通过 epoll_wait 发现可读事件后,调用已注册的读处理函数 readQueryFromClient。 在 readQueryFromClient 中,Redis 处理命令并将其转换为内部数据结构,如执行 GET 命令并从内存中查找值。四、高性能 Redis 网络原理总结
Redis 服务器通过单线程实现高处理能力,每秒可达到数万 QPS,这主要得益于对 Linux 多路复用机制 epoll 的高效利用。 Redis 的核心逻辑集中于 initServer 和 aeMain 两个关键函数,理解这些函数是掌握 Redis 网络原理的基础。 总结 Redis 的网络核心模块,包括启动服务和事件循环处理,对于深入理解网络编程大有裨益。相信通过本文的介绍,您对网络编程的理解将会更加深入。 快来分享这篇文章给您的技术好友吧! 最后,分享我撰写的电子书《理解了实现再谈网络性能》,该书正处于出版流程中。电子版下载地址或添加我本人: zhangyanfei,获取《理解了实现再谈网络性能》的链接。Redis——Epoll网络模型
Redis 的高效性在于其使用多路复用技术管理大量连接,通过单线程事件循环处理请求,实现每秒数万 QPS 的游网平台源码性能。在深入了解 Redis 的 Epoll 实现之前,需要先对 Epoll 有清晰的认识。上一篇文章已对此进行深入浅出的讲解,涉及 select、poll、epoll 的实现。
在掌握了 Epoll 的核心原理后,让我们深入 Redis 如何具体利用 Epoll。通过查阅 Redis 5.0.0 版本的源码,我们可以清楚地看到 Redis 如何实现 Epoll。通过本文,我们重点探讨以下三个关键点:
1. Epoll 是 Linux 内核提供的一种高效事件多路复用机制,核心方法有三个。它通过红黑树、双向链表和事件回调机制实现高效率。
2. Redis 采用 Epoll 实现了 IO 多路复用,其原理是利用 Epoll 进行事件监听,通过事件循环处理各种请求。
3. Redis 的事件驱动机制处理网络 IO 和时间事件,采用成熟的 I/O 多路复用模型(如 select、epoll)进行文件事件处理,对模型进行封装。
事件驱动的核心组件在 src/ae.c 文件中实现,它通过 aeCreateEventLoop、aeMain 和 aeDeleteEventLoop 函数管理事件循环。aeMain 函数是事件循环的主体,调用 aeProcessEvents 处理就绪事件。
Redis 采用自定义的事件驱动库 ae_event 实现 IO 多路复用,支持 select、epoll、evport 和 kqueue 等技术。在不同的操作系统上,Redis 会选择合适的多路复用技术。
Redis 的实现细节如下:
1. initServerConfig 函数初始化服务器配置,确保内部数据结构和参数正确。
2. initServer 函数创建事件管理器 aeEventLoop。
3. aeCreateEventLoop 创建事件管理器并初始化关键属性,如事件表、就绪事件数组等。
4. aeCreateFileEvent 注册文件事件到底层多路复用系统。挖矿网站源码下载
5. aeMain 作为事件循环的主体,无限循环处理文件事件和时间事件。
6. aeProcessEvents 处理就绪事件,调用底层多路复用实现。
Redis 的 Epoll 实现展示了其对底层技术的深入理解和灵活应用,通过高效的事件处理机制实现了高性能。
Redis源码从哪里读起?
如果你正寻求理解Redis源码的路径,本文为你提供了一个全面的指南。Redis 是使用 C 语言构建的,因此,我们从 main 函数开始,深入探索其核心逻辑。在阅读过程中,我们应聚焦于从外部命令输入到内部执行流程的路径,逐步理解 Redis 的工作原理。
理解事件机制对于深入 Redis 的核心至关重要。通过 Redis 的事件循环,我们可以实现单线程环境下的高效处理多任务的能力。这一机制允许 Redis 以线程安全的方式处理大量请求,同时在执行后台任务时保持响应速度。事件循环与系统提供的异步 I/O 多路复用机制相结合,确保了 CPU 资源的高效利用,避免了并发执行的复杂性。
在讨论事件循环时,我们重点关注了两个阶段:初始化和事件处理。初始化阶段涉及配置和数据加载,而事件处理阶段则负责响应客户端请求、执行命令以及周期性任务的调度。通过事件循环,Redis 实现了在单一线程下处理多个请求的高效运行模式。
理解 Redis 命令请求的处理流程是整个指南的关键部分。当客户端向 Redis 发送命令时,流程分为两个阶段:连接建立和命令执行与响应。连接建立阶段由事件循环触发,而命令执行与响应阶段则涉及读取客户端发送的数据,执行命令并返回结果。这一过程通过特定的回调函数实现,确保了命令处理的高效和线程安全。
此外,我们还讨论了 Redis 的事件机制,即事件驱动程序库 ae.c,它在不同操作系统上支持多种 I/O 多路复用机制。在选择底层机制时,Redis 优先考虑后三种更现代、高效的方案,例如 macOS 上的 kqueue 和 Linux 上的 epoll。理解这些机制对于实现高性能网络服务至关重要。
为了帮助读者在阅读 Redis 源码时构建清晰的思维路径,我们提供了一个树型图展示关键函数之间的调用关系。这张图基于 Redis 源码的 5.0 分支,详细地展示了初始化、事件处理、命令请求处理等关键流程的调用顺序。
最后,本文提供的参考文献旨在为读者提供进一步学习的资源。对于希望深入理解 Redis 源码并学习 C 语言编程经验的读者,这些资源将起到重要作用。总的来说,本文旨在为那些希望从源头上理解 Redis 工作机制的技术爱好者提供一个全面、系统化的指南。
redis源码解读(一):事件驱动的io模型,为什么,是什么,怎么做
Redis作为一个高性能的内存数据库,因其出色的读写性能和丰富的数据结构支持,已成为互联网应用不可或缺的中间件之一。阅读其源码,可以了解其内部针对高性能和分布式做的种种设计,包括但不限于reactor模型(单线程处理大量网络连接),定时任务的实现(面试常问),分布式CAP BASE理论的实际应用,高效的数据结构的实现,其次还能够通过大神的代码学习C语言的编码风格和技巧,让自己的代码更加优雅。
下面进入正题:为什么需要事件驱动的io模型
我们可以简单地将一个服务端程序拆成三部分,接受请求->处理请求->返回结果,其中接收请求和处理请求便是我们常说的网络io。那么网络io如何实现呢,首先我们介绍最基础的io模型,同步阻塞式io,也是很多同学在学校所学的“网络编程”。
使用同步阻塞式io的单线程服务端程序处理请求大致有以下几个步骤
其中3,4步都有可能使线程阻塞(6也会可能阻塞,这里先不讨论)
在第3步,如果没有客户端请求和服务端建立连接,那么服务端线程将会阻塞。如果redis采用这种io模型,那主线程就无法执行一些定时任务,比如过期key的清理,持久化操作,集群操作等。
在第4步,如果客户端已经建立连接但是没有发送数据,服务端线程会阻塞。若说第3步所提到的定时任务还可以通过多开两个线程来实现,那么第4步的阻塞就是硬伤了,如果一个客户端建立了连接但是一直不发送数据,服务端便会崩溃,无法处理其他任何请求。所以同步阻塞式io肯定是不能满足互联网领域高并发的需求的。
下面给出一个阻塞式io的服务端程序示例:
刚才提到,阻塞式io的主要问题是,调用recv接收客户端请求时会导致线程阻塞,无法处理其他客户端请求。那么我们不难想到,既然调用recv会使线程阻塞,那么我们多开几个几个线程不就好了,让那些没有阻塞的线程去处理其他客户端的请求。
我们将阻塞式io处理请求的步骤改造下:
改造后,我们用一个线程去做accept,也就是获取已经建立的连接,我们称这个线程为主线程。然后获取到的每个连接开一个新的线程去处理,这样就能够将阻塞的部分放到新的线程,达到不阻塞主线程的目的,主线程仍然可以继续接收其他客户端的连接并开新的线程去处理。这个方案对高并发服务器来说是一个可行的方案,此外我们还可以使用线程池等手段来继续优化,减少线程建立和销毁的开销。
将阻塞式io改为多线程io:
我们刚才提到多线程可以解决并发问题,然而redis6.0之前使用的是单线程来处理,之所以用单线程,官方给的答复是redis的瓶颈不在cpu,既然不在cpu那么用单线程可以降低系统的复杂度,避免线程同步等问题。如何在一个线程中非阻塞地处理多个socket,进而实现多个客户端的并发处理呢,那就要借助io多路复用了。
io多路复用是操作系统提供的另一种io机制,这种机制可以实现在一个线程中监控多个socket,返回可读或可写的socket,当一个socket可读或可写时再去操作它,这样就避免了对某个socket的阻塞等待。
将多线程io改为io多路复用:
什么是事件驱动的io模型(Reactor)
这里只讨论redis用到的单线程Reactor模型
事件驱动的io模型并不是一个具体的调用,而是高并发服务器的一种抽象的编程模式。
在Reactor模型中,有三种事件:
与这三种事件对应的,有三种handler,负责处理对应的事件。我们在一个主循环中不断判断是否有事件到来(一般通过io多路复用获取事件),有事件到来就调用对应的handler去处理时间。
听着玄乎,实际上也就这一张图:
事件驱动的io模型在redis中的实现
以下提及的源码版本为 5.0.8
文字的苍白的,建议参照本文最后的方法下载代码,自己调试下
整体框架
redis-server的main方法在 src/server.c 最后,在main方法中,首先进行一系列的初始化操作,最后进入进入Reactor模型的主循环中:
主循环在aeMain函数中,aeMain函数传入的参数 server.el ,是一个 aeEventLoop 类型的全局变量,保存了主循环的一些状态信息,包括需要处理的读写事件、时间事件列表,epoll相关信息,回调函数等。
aeMain函数中,我们可以看到当 eventLoop->stop 标志位为0时,while循环中的内容会被重复执行,每次循环首先会调用beforesleep回调函数,然后处理时间。beforesleep函数在main函数中被注册,会进行集群状态更新、AOF落盘等任务。
之所以叫beforesleep,是因为aeProcessEvents函数中包含了获取事件和处理事件的逻辑,其中获取读写事件时通过epoll_wait实现,会将线程阻塞。
在aeProcessEvents函数中,处理读写事件和时间事件,参数flags定义了需要处理的事件类型,我们可以暂时忽略这个参数,认为读写时间都需要处理。
aeProcessEvents函数的逻辑可以分为三个部分,首先获取距离最近的时间事件,这一步的目的是为了确定epoll_wait的超时时间,并不是实际处理时间事件。
第二个部分为获取读写事件并处理,首先调用epoll_wait,获取需要处理的读写事件,超时时间为第一步确定的时间,也就是说,如果在超时时间内有读写事件到来,那么处理读写时间,如果没有读写时间就阻塞到下一个时间事件到来,去处理时间事件。
第三个部分为处理时间事件。
事件注册与获取
上面我们讲了整体框架,了解了主循环的大致流程。接下来我们来看其中的细节,首先是读写事件的注册与获取。
redis将读、写、连接事件用结构aeFileEvent表示,因为这些事件都是通过epoll_wait获取的。
事件的具体类型通过mask标志位来区分。aeFileEvent还保存了事件处理的回调函数指针(rfileProc、wfileProc)和需要读写的数据指针(clientData)。
既然读写事件是通过epoll io多路复用实现,那么就避不开epoll的三部曲 epoll_create epoll_ctrl epoll_wait,接下来我们看下redis对epoll接口的封装。
我们之前提到aeMain函数的参数是一个 aeEventLoop 类型的全局变量,aeEventLoop中保存了epoll文件描述符和epoll事件。在aeApiCreate函数(src/ae_epoll.c)中,会调用epoll_create来创建初始化epoll文件描述符和epoll事件,调用关系为 main -> initServer -> aeCreateEventLoop -> aeApiCreate
调用epoll_create创建epoll后,就可以添加需要监控的文件描述符了,需要监控的情形有三个,一是监控新的客户端连接连接请求,二是监控客户端发送指令,也就是读事件,三是监控客户端写事件,也就是处理完了请求写回结果。
这三种情形在redis中被抽象为文件事件,文件事件通过函数aeCreateFileEvent(src/ae.c)添加,添加一个文件事件主要包含三个步骤,通过epoll_ctl添加监控的文件描述符,指定回调函数和指定读写缓冲区。
最后是通过epoll_wait来获取事件,上文我们提到,在每次主循环中,首先根据最近到达的时间事件来计算epoll_wait的超时时间,然后调用epoll_wait获取事件,再处理事件,其中获取事件在函数aeApiPoll(src/ae_epoll.c)中。
获取到事件后,主循环中会逐个调用事件的回调函数来处理事件。
读写事件的实现
写累了,有空补上……
如何使用vscode调试redis源码
编译出二进制程序
这一步有可能报错:
jemalloc是内存分配的一种更高效的实现,用于代替libc的默认实现。这里报错找不到jemalloc,我们只需要将其替换成libc默认实现就好:
如果报错:
我们可以在src目录找到一个脚本名为mkreleasehdr.sh,其中包含创建release.h的逻辑,将报错信息网上翻可以发现有一行:
看来是这个脚本没有执行权限,导致release.h没有成功创建,我们需要给这个脚本添加执行权限然后重新编译:
2. 创建调试配置(vscode)
创建文件 .vscode/launch.json,并填入以下内容:
然后就可以进入调试页面打断点调试了,main函数在 src/server.c