首页 > 数据库 >Redis的IO模型

Redis的IO模型

时间:2024-09-13 16:49:47浏览次数:10  
标签:AE 多路复用 Redis 模型 eventLoop mask 事件 IO

Redis IO模型

Redis IO模型 使用的是基于 Reactor 模式的 I/O 多路复用模型。这个模型通过单线程事件循环来处理所有的客户端请求和响应。

基本模式

1. Reactor 模式

Reactor 模式是一种用于处理并发 I/O 操作的设计模式。它包含以下几个组件:

  • 多路复用器(Multiplexer):负责监听多个 I/O 事件,如读、写、连接等。
  • 事件处理器(Event Handler):处理特定事件的回调函数。

2. Redis 的 I/O 多路复用

Redis 使用操作系统提供的 I/O 多路复用机制,如 selectpollepoll 或 kqueue。这些机制可以在单个线程中监视多个文件描述符,以便在任何一个描述符准备好进行 I/O 操作时通知应用程序。

主要步骤:
  1. 事件循环初始化:创建和初始化事件循环及相应的多路复用器。
  2. 事件注册:将客户端套接字上的读写事件注册到多路复用器。
  3. 事件等待:调用多路复用器的方法,如 select 或 epoll_wait,等待事件发生。
  4. 事件分发:当事件发生时,多路复用器会返回就绪的文件描述符列表。
  5. 事件处理:根据事件类型调用相应的事件处理器(读事件、写事件等)。
  6. 响应客户端:将处理结果返回给客户端。

基本原理

Redis 的 I/O 模型实现原理,需要深入了解其事件驱动框架和多路复用机制。

1. 事件驱动框架

Redis 的事件驱动框架主要由以下几个组件组成:

  • 事件循环(Event Loop):这是事件驱动模型的核心,负责管理所有的 I/O 事件。
  • 事件类型(Event Types):Redis 主要处理两种事件:文件事件(File Events)和时间事件(Time Events)。
  • 事件处理器(Event Handlers):每个事件类型都有相应的事件处理器。

Redis 使用 aeEventLoop 结构体来管理事件循环,包含了文件事件和时间事件的注册、取消、处理等功能。

2. I/O 多路复用

Redis 使用操作系统提供的 I/O 多路复用机制,如 selectpollepoll(Linux)或 kqueue(BSD系统),这些系统调用允许一个线程同时监视多个文件描述符。

多路复用器(Multiplexer)

在 Redis 中,多路复用器通过 aeApi 结构体进行抽象,并根据操作系统的不同实现选择不同的多路复用策略:

  • ae_select.c:基于 select 系统调用。
  • ae_epoll.c:基于 epoll 系统调用。
  • ae_kqueue.c:基于 kqueue 系统调用。

3. Redis 的事件处理流程

Redis 的事件处理流程大致如下:

事件循环初始化
aeEventLoop *aeCreateEventLoop(int setsize) {
    aeEventLoop *eventLoop = zmalloc(sizeof(*eventLoop));

    // 初始化事件循环
    eventLoop->events = zmalloc(sizeof(aeFileEvent)*setsize);
    eventLoop->fired = zmalloc(sizeof(aeFiredEvent)*setsize);
    eventLoop->setsize = setsize;
    eventLoop->timeEventHead = NULL;
    eventLoop->timeEventNextId = 0;
    eventLoop->stop = 0;

    // 初始化多路复用API
    if (aeApiCreate(eventLoop) == -1) {
        zfree(eventLoop->events);
        zfree(eventLoop->fired);
        zfree(eventLoop);
        return NULL;
    }

    return eventLoop;
}
事件注册
int aeCreateFileEvent(aeEventLoop *eventLoop, int fd, int mask,
                      aeFileProc *proc, void *clientData) {
    if (fd >= eventLoop->setsize) return AE_ERR;

    aeFileEvent *fe = &eventLoop->events[fd];
    if (aeApiAddEvent(eventLoop, fd, mask) == -1)
        return AE_ERR;

    fe->mask |= mask;
    if (mask & AE_READABLE) fe->rfileProc = proc;
    if (mask & AE_WRITABLE) fe->wfileProc = proc;
    fe->clientData = clientData;

    return AE_OK;
}
事件等待
int aeApiPoll(aeEventLoop *eventLoop, struct timeval *tvp) {
    int retval, numevents = 0;

    retval = epoll_wait(eventLoop->apidata->epfd, eventLoop->apidata->events,
                        eventLoop->setsize, 
                        tvp ? (tvp->tv_sec*1000 + tvp->tv_usec/1000) : -1);

    if (retval > 0) {
        int j;

        numevents = retval;
        for (j = 0; j < numevents; j++) {
            int mask = 0;
            struct epoll_event *e = eventLoop->apidata->events+j;

            if (e->events & EPOLLIN) mask |= AE_READABLE;
            if (e->events & EPOLLOUT) mask |= AE_WRITABLE;
            if (e->events & EPOLLERR) mask |= AE_WRITABLE;
            if (e->events & EPOLLHUP) mask |= AE_WRITABLE;

            eventLoop->fired[j].fd = e->data.fd;
            eventLoop->fired[j].mask = mask;
        }
    }

    return numevents;
}
事件处理
void aeProcessEvents(aeEventLoop *eventLoop, int flags) {
    int processed = 0, numevents;

    if (!(flags & AE_TIME_EVENTS) && !(flags & AE_FILE_EVENTS)) return;

    if (eventLoop->maxfd != -1) {
        numevents = aeApiPoll(eventLoop, tvp);
        for (j = 0; j < numevents; j++) {
            aeFileEvent *fe = &eventLoop->events[eventLoop->fired[j].fd];
            int mask = eventLoop->fired[j].mask;
            int fd = eventLoop->fired[j].fd;
            int rfired = 0;

            if (fe->mask & mask & AE_READABLE) {
                rfired = 1;
                fe->rfileProc(eventLoop,fd,fe->clientData,mask);
            }
            if (fe->mask & mask & AE_WRITABLE) {
                if (!rfired || fe->wfileProc != fe->rfileProc)
                    fe->wfileProc(eventLoop,fd,fe->clientData,mask);
            }
            processed++;
        }
    }

    if (flags & AE_TIME_EVENTS)
        processed += processTimeEvents(eventLoop);

    return processed;
}

单线程的优势

  • 避免锁竞争:由于 Redis 运行在单线程中,所有操作都是原子的,不需要加锁机制来保护共享资源,简化了实现。
  • CPU 缓存友好:在单线程中,数据访问模式更加线性,减少了 CPU 缓存失效,提高了缓存命中率。
  • 性能:由于 Redis 的大部分操作是内存操作,并且操作系统的多路复用机制非常高效,单线程模型能够提供足够高的性能。
  • 原子性:单线程模型确保每个命令是原子执行的,不会出现数据竞争问题。

关于ArchManual

https://archmanual.com

https://github.com/yingqiangh/ArchManual

标签:AE,多路复用,Redis,模型,eventLoop,mask,事件,IO
From: https://blog.csdn.net/weixin_36796512/article/details/142210604

相关文章

  • permission_required 权限需要添加应用名称
    Django之所以能在permission_required='view_customer'中正常判断权限,是因为它在默认情况下使用当前视图所属应用的app_label。如果没有显式指定应用名,Django会假设权限属于当前视图对应的应用。在customers应用中,permission_required='view_customer'能够正常工作,原......
  • Whisper 模型在实时语音转录中有哪些具体的应用场景?
    关注我,持续分享逻辑思维&管理思维&面试题;可提供大厂面试辅导、及定制化求职/在职/管理/架构辅导;推荐专栏《10天学会使用asp.net编程AI大模型》,目前已完成所有内容。一顿烧烤不到的费用,让人能紧跟时代的浪潮。从普通网站,到公众号、小程序,再到AI大模型网站。干货满满。学成后可......
  • 保姆级,手把手教你物理机搭建Redis-sentinel(哨兵)集群
    集群介绍        Redis,作为一种开源的、基于内存的数据结构存储系统,被广泛应用于各种场景,包括缓存、消息队列、短期存储等。单一实例的工作模式通常无法保证Redis的可用性和拓展性,Redis提供了三种分布式方案:主从模式哨兵模式集群模式      主从模式    ......
  • Gradle Installation
    PrerequisitesGradlerunsonallmajoroperatingsystems.ItrequiresJavaDevelopmentKit(JDK)version8orhighertorun.Youcancheckthecompatibilitymatrixformoreinformation.Tocheck,runjava-version:❯java-versionopenjdkversion"11......
  • 深度解析高斯混合模型(GMM)及其在WebRTC VAD中的应用
    目录一、引言二、高斯混合模型概述三、GMM的应用——语音与背景噪声分离四、GMM的训练与优化五、GMM的优势与挑战六、GMM的实际应用案例七、结论八、参考文献一、引言高斯混合模型(GaussianMixtureModel,GMM)是统计学中的一种经典模型,广泛应用于模式识别、聚类和信......
  • Linux字符设备驱动:分层/分离思想、总线设备驱动模型和设备树
    本文章参考韦东山嵌入式Linux应用开发完全手册......
  • @Transactional 注解使用场景
    我把这些事务问题归结成了三类:不必要、不生效、不回滚,接下用一些demo演示下各自的场景。不必要1.无需事务的业务在没有事务操作的业务方法上使用@Transactional注解,比如:用在仅有查询或者一些HTTP请求的方法,虽然加上影响不大,但从编码规范的角度来看还是不够严谨,建议去掉。@Tran......
  • 在Windows中,如何设置LdapSrvPriority的值?
    在Windows中,设置LdapSrvPriority的值可以通过修改注册表来实现。具体步骤如下:打开注册表编辑器(regedit)。导航至HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\Netlogon\Parameters路径。在右侧空白处,右键点击选择“新建”->“DWORD(32位)值”。将新建的值命名为LdapSrvPrior......
  • 如何优化LdapSrvPriority值的设置
    优化LdapSrvPriority值的设置,可以通过修改Windows注册表中的相应条目来实现。具体步骤如下:打开注册表编辑器(regedit)。导航至HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\Netlogon\Parameters路径。在右侧窗格中,新建一个DWORD值,命名为LdapSrvPriority。双击该值,输入一个......
  • 如何判断LdapSrvPriority的设置是否生效?
    要判断LdapSrvPriority的设置是否生效,可以通过以下步骤进行验证:查看注册表设置:确认在注册表路径“HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\Netlogon\Parameters”下是否已创建“LdapSrvPriority”子键,并检查其值是否已设置为预期的值。查看SRV记录:使用DNS管理器或Ns......