`

Redis代码阅读3--Redis网络监听(2)

 
阅读更多

这篇文章接上一篇,主要介绍Redis网络监听流程的各个步骤。

  1. 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 上,该函数也放在后面介绍
    /* 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
    
  2. aeCreateTimeEvent/:创建定时事件,注册了定时时间函数 serverCron,作用放到后面介绍;
  3. aeCreateFileEvent:注册了一个读 I/O事件,绑定了函数 acceptTcpHandler(同样也放到后面介绍 ),如果多路复用采用epoll机制的话,这采用LT模式进行触发;

  4. 创建好server.ae后,通过aeMain这个方法开始网络监听;此处的代码是:

    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;

  5. Beforesleep:顾名思义,这个方法在Redis每次进入sleep/wait去等待监听的端口发生I/O事件之前被调用(这话太拗口了。。。。),还是来看代码:

    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;
  6. 执行完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。在响应定时事件的时候 需要注意几点点:
    static int processTimeEvents(aeEventLoop *eventLoop) {
        int processed = 0;
        aeTimeEvent *te;
        long long maxId;
    
        te = eventLoop->timeEventHead;
        maxId = eventLoop->timeEventNextId-1;
    
    
    
    
        while(te) {
            long now_sec, now_ms;
            long long id;
    
            if (te->id > maxId) 
    
    
    
    {
                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). */
                if (retval != AE_NOMORE) {
                    aeAddMillisecondsToNow(retval,&te->when_sec,&te->when_ms);
                } else {
                    aeDeleteTimeEvent(eventLoop, id);
                }
    
    
    
    
                
    
    
    
    te = eventLoop->timeEventHead;
    
    
    
    
    
    
    
    
            } 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来说不是个问题,但是作者也提到在未来的版本会对此进行改进   
  7. acceptTcpHandler:这个方法主要是监听网络端口:                                                                             i.    通过调用anetTcpAccept方法获得监听端口上的client connection;
    ii.    然后调用acceptCommonHandler创建redisClient对象,如果当前连接的client的数量大于配置的最大client数量,则拒绝当前连接,并返回” max number of clients reached”提示信息;
    iii.    调用createClient方法创建redisClient,同时注册新的fileEvent(AE_READABLE),并绑定处理函数为readQueryFromClient;
    redisClient *createClient(int fd) {
        redisClient *c = zmalloc(sizeof(redisClient));
        c->bufpos = 0;
    
        anetNonBlock(NULL,fd);
        anetTcpNoDelay(NULL,fd);
        if (!c) return NULL;
        if (aeCreateFileEvent(server.el,fd,AE_READABLE,
            readQueryFromClient, c) == AE_ERR)
    
    
    
    
        {
            close(fd);
            zfree(c);
            return NULL;
        }
        .........
    }
      iv.    readQueryFromClient:从指定的Socket中读取client发送过来的数据,并按照Redis的协议(后面将单独介绍)进行解析组装成Redis的各个command,然后通过查找commandTable,执行command
  8.  _installWriteEvent:上面介绍的文件事件都是AE_READABLE事件,但Redis在执行完client请求后的命令后,向Client端return数据,就是往Socket写入数据,这使一个AE_ AE_WRITABLE事件。Redis执行完command后,调用addReply方法,然后在这个方法里面调用installWriteEvent来注册一个AE_WRITABLE事件,并绑定事件处理函数sendReplyToClient,用来把数据发送到client。
  9. serverCron: 介绍完fileEvent的处理函数后,最后我们来介绍timeEvent的处理函数。顾名思义,serverCron就是Redis Server的定时计划任务。这个方法比较复杂,处理的事情也很多,主要集中在记录Redis的运行情况(memory,clients等),AOF write, VM Swap和BGSAVE等和Redis正常运行息息相关的事项。这个方法的代码很多,因此单独介绍
1
0
分享到:
评论

相关推荐

    Redis-x64-5.0.14.msi和Redis-x64-5.0.14.zip

    1. **防火墙设置**:由于Redis默认监听6379端口,你需要确保Windows防火墙允许这个端口的入站连接,否则外部客户端无法连接到Redis服务器。 2. **服务化**:为了使Redis在系统启动时自动运行,可以将`redis-server....

    redis安装文件Redis-x64-3.2.10、Redis-x64-3.0.50

    3. 启动服务:Redis服务器可以通过命令行启动,如在Windows上执行`redis-server.exe redis.conf`,在Linux上通过`./redis-server /path/to/redis.conf`。在启动后,Redis会监听配置的端口,等待客户端连接。 4. ...

    Redis-x64-7.0.5-windows11

    6. **安全考虑**:默认情况下,Redis监听所有网络接口,为了安全性,应限制为仅监听本地地址或特定IP。 在实际应用中,Redis可以与其他技术结合,如Spring Boot、Docker等,以提高开发效率和系统性能。例如,通过...

    Redis-x64-5.0.14 windows

    标签"源码软件"表明Redis是开源的,用户可以查看和修改其源代码,这对于开发者来说是一个巨大的优势,可以深度定制以满足特定需求。同时,"数据库"标签强调了Redis作为数据库的角色,它不仅可以快速读写,还能实现...

    Redis-x64-3.0.504 MSI ZIP

    3. **网络通信**:Redis作为服务器,通过TCP/IP协议监听指定端口(默认6379),接受来自客户端的请求并进行响应,支持多种网络协议如TCP、Unix域套接字等。 4. **多语言支持**:Redis提供了丰富的客户端库,包括...

    Redis-x64-3.0.504.msi.zip

    2. **Redis 3.0.504 版本特性**: - Redis 3.0.504是该数据库的一个稳定版本,发布于2016年,提供了许多改进和修复。 - 这个版本可能包括性能优化,bug修复,以及对某些功能的增强。 - 对于Windows用户来说,这是...

    Redis-x64-3.2.100

    另一个是Redis-x64-3.2.100.zip,这是一个压缩文件,可能包含了Redis服务器的源代码、二进制文件、配置文件和其他相关工具。 **Redis简介** Redis(Remote Dictionary Server)由Salvatore Sanfilippo创建,它的...

    Redis-x64-5.0.14.1.zip

    当你运行这个程序时,Redis会作为一个后台服务运行,监听指定的端口(默认为6379),等待客户端的连接请求。 2. **redis-cli.exe**:这是一个命令行接口工具,用于与Redis服务器交互。你可以通过它来发送命令、查看...

    Redis-x64-5.0.14.1.msi 安装包

    7. **安全与网络**:Redis默认监听6379端口,可以通过配置文件更改端口。5.0.14版本可能增强了安全特性,如访问控制和密码认证。 8. **性能优化**:Redis因其内存存储和单线程模型而著称于高性能,5.0.14版本可能对...

    最新版windows Redis-x64-5.0.14.zip

    3. **配置文件**: Redis的配置文件是`redis.windows.conf`,在安装目录下可以找到。通过编辑这个文件,用户可以自定义Redis的行为,如修改端口号、设置最大内存、调整日志级别、启用AOF持久化等。 4. **持久化**: ...

    redis+redis-desktop-manager-0.8.3.3850+笔记

    - `bind`: 指定Redis监听的IP地址,可以限制访问来源。 - `save`: 定义数据持久化的规则,当满足条件时自动执行`RDB`持久化。 - `appendonly`: 是否开启`AOF`(Append Only File)持久化。 - `requirepass`: 设置...

    Redis-x64-3.2.100.zip

    - Redis默认监听所有网络接口,为避免未授权访问,应配置绑定到本地回环地址(127.0.0.1)或特定IP,或者设置访问密码。 4. **性能优化**: - 考虑到Redis是内存数据库,内存管理至关重要。合理配置最大内存、...

    redis-windows-7.2.4.zip

    - 运行`redis-server.exe`,默认情况下,Redis监听6379端口。 - 可以通过配置文件`redis.windows.conf`修改默认设置,如端口、内存限制、持久化策略等。 - 使用`redis-cli.exe`客户端工具与Redis服务器交互,执行...

    Redis-x64-3.2.100压缩包及使用说明.rar

    - `port 6379`:Redis默认监听6379端口,可按需更改。 - `save`:定义数据持久化策略,例如`save 60 10000`表示在60秒内有10000个写操作时进行RDB快照。 - `appendonly yes/no`:开启或关闭AOF(Append Only File)...

    Redis-x64-3.2.100 windows 安装包

    8. **安全注意事项**:默认情况下,Redis监听所有网络接口,这可能带来安全隐患。在生产环境中,建议修改配置文件,限制Redis只监听特定IP地址或localhost,以防止未授权访问。 9. **性能优化**:在Windows上运行...

    Redis-x64-5.0.10.zip

    - 默认情况下,Redis监听本地主机(127.0.0.1),如需对外提供服务,需要修改配置文件,开启监听其他IP地址。 - Redis可以通过SSL加密连接,提高数据传输的安全性。 6. **服务管理**: - 使用"sc create"命令...

    Redis-x64-5.0.zip

    在安装或升级Redis时,阅读这份文档是很有帮助的,可以确保你了解所有重要的变更。 接下来,有两个配置文件:`redis.windows-service.conf`和`redis.windows.conf`。`redis.windows.conf`是Redis服务器的标准配置...

    redis-win64-2.8.12.zip

    2. **配置文件 - redis.windows.conf** `redis.windows.conf`是Redis在Windows上的配置文件,用于设置服务器的各种参数。常见的配置项包括端口号、绑定的IP地址、数据库数量、内存限制、日志级别、持久化策略等。...

Global site tag (gtag.js) - Google Analytics