`

Redis网络监听(2)

 
阅读更多
这篇文章接上一篇,主要介绍Redis网络监听流程的各个步骤。
aeCreateEventLoop :创建用于循环监听的 eventLoop , Redis 支持主流的三种事件触发机制: select ,epoll, kqueue, 可以通过在 config.h 里面配置 HAVE_EPOLL/ HAVE_KQUEUE 来根据不同的操作系统选择合适的机制:调用 ae_epoll.c/ae_select.c/ae_kqueue.c中的 aeApiCreate;创建 eventLoop 的时候没有指定 beforesleep ,在开始循环监听前将函数 beforeSleep 绑定到 eventLoop 上,该函数也放在后面介绍
C代码  收藏代码
/* test for polling API */ 
#ifdef __linux__ 
#define HAVE_EPOLL 1 
#endif 
 
#if (defined(__APPLE__) && defined(MAC_OS_X_VERSION_10_6)) || defined(__FreeBSD__) || defined(__OpenBSD__) || defined (__NetBSD__) 
#define HAVE_KQUEUE 1 
#endif 
aeCreateTimeEvent/:创建定时事件,注册了定时时间函数 serverCron,作用放到后面介绍;
aeCreateFileEvent:注册了一个读 I/O事件,绑定了函数 acceptTcpHandler(同样也放到后面介绍 ),如果多路复用采用epoll机制的话,这采用LT模式进行触发;
创建好server.ae后,通过aeMain这个方法开始网络监听;此处的代码是:
C代码  收藏代码
void aeMain(aeEventLoop *eventLoop) { 
    eventLoop->stop = 0; 
    while (!eventLoop->stop) { 
        if (eventLoop->beforesleep != NULL) 
            eventLoop->beforesleep(eventLoop); 
        aeProcessEvents(eventLoop, AE_ALL_EVENTS); 
    } 

在每次处理事件之前,都要执行一遍server.ae中设定的beforesleep方法,下面就介绍下beforesleep;

Beforesleep:顾名思义,这个方法在Redis每次进入sleep/wait去等待监听的端口发生I/O事件之前被调用(这话太拗口了。。。。),还是来看代码:
C代码  收藏代码
void beforeSleep(struct aeEventLoop *eventLoop) { 
    REDIS_NOTUSED(eventLoop); 
    listNode *ln; 
    redisClient *c; 
 
    /* Awake clients that got all the swapped keys they requested */ 
    if (server.vm_enabled && listLength(server.io_ready_clients)) { 
        listIter li; 
 
        listRewind(server.io_ready_clients,&li); 
        while((ln = listNext(&li))) { 
            c = ln->value; 
            struct redisCommand *cmd; 
 
            /* Resume the client. */ 
            listDelNode(server.io_ready_clients,ln); 
            c->flags &= (~REDIS_IO_WAIT); 
            server.vm_blocked_clients--; 
            aeCreateFileEvent(server.el, c->fd, AE_READABLE, 
                readQueryFromClient, c); 
            cmd = lookupCommand(c->argv[0]->ptr); 
            redisAssert(cmd != NULL); 
            call(c,cmd); 
            resetClient(c); 
            /* There may be more data to process in the input buffer. */ 
            if (c->querybuf && sdslen(c->querybuf) > 0) 
                processInputBuffer(c); 
        } 
    } 
 
    /* Try to process pending commands for clients that were just unblocked. */ 
    while (listLength(server.unblocked_clients)) { 
        ln = listFirst(server.unblocked_clients); 
        redisAssert(ln != NULL); 
        c = ln->value; 
        listDelNode(server.unblocked_clients,ln); 
        c->flags &= ~REDIS_UNBLOCKED; 
 
        /* Process remaining data in the input buffer. */ 
        if (c->querybuf && sdslen(c->querybuf) > 0) 
            processInputBuffer(c); 
    } 
 
    /* Write the AOF buffer on disk */ 
    flushAppendOnlyFile(); 

这个方法做了三件事情:
I.    如果Redi开启了Virtual memory,那么某些clients请求的keys可能因为被swap了,因此这些client会被block住,当这些clients请求的keys又被swap到内存中时,则这些被block住的clients应该unblock,然后被处理;io_ready_clients就是用来维护这些clients的,为了尽快响应client的请求,因此在每次sleep前都先处理这些请求
II.    某些Redis操作是blocking的,如BLPOP,那么执行这些操作的clients可能会被block住,unblocked_clients这个list就是用来维护那些刚被unblock的clients,如果这个list不为空,则也要尽快响应这些clients
III.    flushAppendOnlyFile;因为clients的Socket的write只能在eventLoop里面进行,而flushAppendOnlyFile又是在每次sleep之前被调用,所以在eventLoop里面的所有AOF writes都是先写到内存里的一块buffer里面,flushAppendOnlyFile则负责把这个buffer内容flush到disk;
执行完beforesleep后aeprocessEvents,该方法主要是处理各种监听到的文件读写事件和到期响应的定时事件,因为这个方法的代码比较长,而且逻辑简单,就不贴过来了,简单介绍下过程:                                      a)    首先通过遍历eventLoop中注册的timeEvent找出离当前最近timeEvent(即shortest)。
b)    调用epoll_wait()方法,等待I/O事件的发生, 为了尽快响应时间事件,epoll_wait()方法的等待时间为shortest与当前时间的差值,如果该差值小于零,则epoll_wait()轮询至有I/O事件发生;
c)    响应eventLoop中fired的aeFileEvent,这里调用的就是之前设置的文件处理函数acceptTcpHandler。
d)    响应完I/O事件后,则通过timeEventHead遍历timeEvent,逐一响应timeProc--serverCron。在响应定时事件的时候 需要注意几点点:
C代码  收藏代码
static int processTimeEvents(aeEventLoop *eventLoop) { 
    int processed = 0; 
    aeTimeEvent *te; 
    long long maxId; 
 
    te = eventLoop->timeEventHead; 
   <span style="color: #ff0000;"> maxId = eventLoop->timeEventNextId-1;</span> 
 
 
 
 
    while(te) { 
        long now_sec, now_ms; 
        long long id; 
 
      <span style="color: #ff0000;">  if (te->id > maxId) </span> 
 
 
 

            te = te->next; 
            continue; 
        } 
        aeGetTime(&now_sec, &now_ms); 
        if (now_sec > te->when_sec || 
            (now_sec == te->when_sec && now_ms >= te->when_ms)) 
        { 
            int retval; 
 
            id = te->id; 
            retval = te->timeProc(eventLoop, id, te->clientData); 
            processed++; 
            /* After an event is processed our time event list may
             * no longer be the same, so we restart from head.
             * Still we make sure to don't process events registered
             * by event handlers itself in order to don't loop forever.
             * To do so we saved the max ID we want to handle.
             *
             * FUTURE OPTIMIZATIONS:
             * Note that this is NOT great algorithmically. Redis uses
             * a single time event so it's not a problem but the right
             * way to do this is to add the new elements on head, and
             * to flag deleted elements in a special way for later
             * deletion (putting references to the nodes to delete into
             * another linked list). */ 
          <span style="color: #ff0000;">  if (retval != AE_NOMORE) { 
                aeAddMillisecondsToNow(retval,&te->when_sec,&te->when_ms); 
            } else { 
                aeDeleteTimeEvent(eventLoop, id); 
            }</span> 
 
 
 
 
           <span style="color: #ff0000;"> </span> 
 
 
 
<span style="color: #ff0000;"><span style="background-color: #ffffff;">te = eventLoop->timeEventHead;</span> 
 
 
 
</span> 
 
 
 
 
        } else { 
            te = te->next; 
        } 
    } 
    return processed; 


因为响应一个定时事件后,eventLoop里面的定时事件链表可能会改变了,所以又要从头结点开始遍历定时事件链表;
因为每次都要从头结点开始遍历定时事件链表,因此要考虑如何避免响应循环调用,即在响应定时事件a时,如果a的处理函数timeProc中又register了新的定时事件b,如果响应完事件a后,又响应b的话,那么就会造成循环响应。为了解决这个情况,redis在eventLoop里维护了一个timeEventNextId,即下一个定时事件的id,比如当前eventLoop的只有一个timeEvent  a,那么timeEventNextId=2,a->id = 1当a的timeProc方法又注册了timeEvent  b,那么timeEventNextId = 3,b->id = 2.那么在redis在遍历定时事件开始的时候将遍历前的eventLoop里面的maxId= timeEventNextId-1保存起来,在遍历定时事件的时候,如果某个timeEvent->id >maxId,则跳过这个事件。
作者也意识到了每次都从头结点开始遍历定时事件不是一个好的算法,但是由于目前Redis里面只有一个定时事件,所以目前对redis来说不是个问题,但是作者也提到在未来的版本会对此进行改进  
acceptTcpHandler:这个方法主要是监听网络端口:                                                                             i.    通过调用anetTcpAccept方法获得监听端口上的client connection;
ii.    然后调用acceptCommonHandler创建redisClient对象,如果当前连接的client的数量大于配置的最大client数量,则拒绝当前连接,并返回” max number of clients reached”提示信息;
iii.    调用createClient方法创建redisClient,同时注册新的fileEvent(AE_READABLE),并绑定处理函数为readQueryFromClient;
C代码  收藏代码
redisClient *createClient(int fd) { 
    redisClient *c = zmalloc(sizeof(redisClient)); 
    c->bufpos = 0; 
 
    anetNonBlock(NULL,fd); 
    anetTcpNoDelay(NULL,fd); 
    if (!c) return NULL; 
    <span style="color: #ff0000;">if (aeCreateFileEvent(server.el,fd,AE_READABLE, 
        readQueryFromClient, c) == AE_ERR)</span> 
    { 
        close(fd); 
        zfree(c); 
        return NULL; 
    } 
    ......... 

  iv.    readQueryFromClient:从指定的Socket中读取client发送过来的数据,并按照Redis的协议(后面将单独介绍)进行解析组装成Redis的各个command,然后通过查找commandTable,执行command
_installWriteEvent:上面介绍的文件事件都是AE_READABLE事件,但Redis在执行完client请求后的命令后,向Client端return数据,就是往Socket写入数据,这使一个AE_ AE_WRITABLE事件。Redis执行完command后,调用addReply方法,然后在这个方法里面调用installWriteEvent来注册一个AE_WRITABLE事件,并绑定事件处理函数sendReplyToClient,用来把数据发送到client。
serverCron: 介绍完fileEvent的处理函数后,最后我们来介绍timeEvent的处理函数。顾名思义,serverCron就是Redis Server的定时计划任务。这个方法比较复杂,处理的事情也很多,主要集中在记录Redis的运行情况(memory,clients等),AOF write, VM Swap和BGSAVE等和Redis正常运行息息相关的事项。这个方法的代码很多,因此单独介绍 。
分享到:
评论

相关推荐

    redis网络层echo例子

    本篇文章将深入探讨Redis网络层的工作原理,并通过一个"echo"测试例子来加深理解。 Redis网络层的核心在于它的事件驱动模型,它基于libevent库来处理来自客户端的连接请求和数据传输。libevent是一个跨平台的事件...

    关于Redis网络模型的源码详析

    在本文中,我们将重点探讨基于epoll的Redis网络模型。 首先,`epoll_create()`系统调用用于创建一个epoll实例,它返回一个专用于epoll的文件描述符。参数表示可以监听的最大socket文件描述符数量。`epoll_ctl()`则...

    Redis集群下过期key监听的实现代码

    文章中也提到,目前网络上没有现成的解决方案来完美解决在Redis集群下监听key过期的问题。实现一个高效且准确的过期key监听机制需要对Redis集群的内部原理有深入的理解。 从提供的代码片段中,我们可以看到实现的...

    cent OS7无网络安装redis

    2. **依赖包解析**:在提供的文件列表中,我们可以看到一些重要的依赖包,它们是构建和运行Redis所必需的: - `gcc-4.8.5-44.el7.x86_64.rpm`:GCC(GNU Compiler Collection)是用于编译C、C++等语言的编译器,...

    linux离线安装redis

    默认情况下,Redis会监听6379端口。如果你需要更改端口,需要编辑`redis.conf`配置文件。找到`bind 127.0.0.1`和`port 6379`这两行,取消`bind`行的注释,并将`port`设置为你希望的端口,例如: ```conf # 绑定所有...

    Redis 服务器

    - **安全策略**:由于Redis默认监听所有网络接口,可能存在安全隐患,建议配置为仅监听特定IP或启用密码认证。 - **资源限制**:根据服务器资源情况调整Redis配置,如最大内存限制、文件描述符数量等。 - **监控与...

    redis集群redis集群

    3. port指令,用于指定Redis监听的端口号。 4. cluster-enabled指令,设置为yes以启用集群模式。 5. cluster-config-file指令,指定集群状态文件的名称。 6. cluster-node-timeout指令,设置集群节点超时时间。 7. ...

    redis linux rpm离线安装.zip

    默认情况下,Redis监听所有网络接口,为提高安全性,建议限制只监听本地环回地址(127.0.0.1)。此外,还可以配置访问密码,防止未经授权的访问。 10. **监控与维护**: 为了确保Redis的稳定运行,定期检查系统...

    redis 配置详细介绍

    默认情况下,Redis会监听所有网络接口,如果你只想让它监听特定的IP,如127.0.0.1,可以设置`bind 127.0.0.1`,这有助于提高安全性。 Redis提供了多种数据持久化方式,包括RDB(快照)和AOF(Append Only File)。...

    windows redis 安装 redis 安装 redis 安装

    - `bind`:指定 Redis 服务监听的 IP 地址,如果不配置或设置为 0.0.0.0,则监听所有网络接口。 - `requirepass`:设置访问密码,增加安全性。 - `appendonly yes/no`:是否开启持久化,如果开启,数据将在每次写...

    redis(window)

    - `bind`: 指定Redis服务器监听的IP地址,默认为本机所有网络接口。 - `protected-mode`: 保护模式,如果设置为`yes`,则只有本地连接可以访问。 - `maxmemory`: 设置Redis的最大内存大小,超过此值会触发LRU...

    redis-windows-7.0.10.zip

    其中,`bind`配置项用于指定Redis服务器监听的网络接口,`port`定义了服务端口,默认为6379;`maxmemory`用于设置Redis的最大内存使用量,超过此值将触发LRU(最近最少使用)或LFU(最不经常使用)的内存淘汰策略。 ...

    redis6.2.5安装包Windows版

    Redis(Remote Dictionary Server)是一个开源的、支持网络、内存储存的键值对数据存储系统。它通常以数据结构服务器的身份工作,使用内存存储数据,通过网络接口提供服务,并定期将内存中的数据持久化到磁盘,以...

    redis.conf,版本7.0.8

    2. `bind`: 设置Redis服务器监听的IP地址。默认情况下,Redis会监听所有可用的网络接口。若只想限制在特定IP上,可以在此配置。 3. `daemonize`: 是否以守护进程方式运行Redis。`yes`表示后台运行,`no`表示前台...

    redis mac 7.2.3 arm 包

    - 默认情况下,Redis监听所有网络接口,出于安全原因,建议修改配置文件,仅监听本地接口(`bind 127.0.0.1`)。 - 设置密码认证(`requirepass`),防止未授权访问。 - 避免在生产环境中暴露Redis直接面对互联网...

    redis 搭建 配置 运行

    - `bind`: 指定Redis服务器监听的IP地址,默认监听所有网络接口。 - `maxclients`: 设置最大客户端连接数,防止资源耗尽。 - `dbfilename`: 定义RDB持久化文件名。 - `dir`: 指定数据文件的存储目录。 3. **...

    docker redis 3.2 配置文件

    在默认情况下,Redis 会监听 6379 端口。在 Docker 中,通常会通过 `-p` 或 `--publish` 参数将容器内的端口映射到主机的某个端口。如果“端口绑定注销”,可能意味着你希望避免自动映射,而是通过 Docker Compose ...

    redis7.0.5 Windows版本

    同时,根据网络需求,可以调整监听IP地址和端口。 8. **主从复制** Redis 7.0.5支持主从复制,可以创建多个从节点来备份主节点的数据,提高数据可用性和容错能力。在Windows环境下,配置复制同样简单,只需指定主...

    redis解压版最新

    - Redis通过网络协议与客户端交互,支持多种编程语言的客户端库。 2. **Windows下的Redis配置**: - `redis.windows-service.conf` 和 `redis.windows.conf` 是Redis在Windows上的配置文件。 - `redis.windows-...

    redis win 64/redis win管理工具

    - **安全**:由于Redis默认监听所有网络接口,应配置合适的防火墙规则或设置bind选项,仅允许特定IP访问。 - **密码保护**:启用`requirepass`配置项,为Redis服务器设置密码,增加安全性。 - **资源限制**:根据...

Global site tag (gtag.js) - Google Analytics