<< Evernote가 가진 기술과 기업 문화 | Home | 소프트웨어 아키텍트를 위한 팁 >>

[Redis 소스 분석] 서버 구동 및 커맨드 처리 흐름

서비스 아키텍처상에 데이터 스토어 분야중 RDB에서 많이 쓰고 있는 MySQL과 캐시 분야에서 많이 쓰이고 있는 Redis의 소스를 살펴보고 있는 중입니다. 후에는 스키마리스 모델인 MongoDB도 살펴볼 예정입니다.

소스 분석의 목적은 내부 로직이 어떻게 돌아가는 지 앎으로 인해서 설정 튜닝이나 운영 이슈 등에 대응하기 위한 감을 높이는 작업으로 봐도 무방할 것입니다.

그리고 Redis는 소스코드의 양이 그다지 많이 않은편에 속해 분석하는데 진입장벽이 다른것들보다는 크지 않다는 점입니다. 물론 쉽다는 이야기는 아니지요. ^^
Redis는 C로 개발되어 있고, 분석하는 버전은 2.4.17 버전을 참고하여 소스 흐름을 정리해 봅니다.

Redis 구동시 소스 흐름



서버 구동의 소스 분석시에 이해에 도움을 주기 위해 Persistence개념을 알고 있으면 좋다. Redis의 저장 매커니즘은 Snapshotting(RDB)방식과 Append-only file(AOF)방식 두가지가 존재한다.
Snapshotting(RDB)방식은 save(blocking), bgsave(non-blocking) 방식에 의해 저장되며 그 주기에따라 특정 시점의 메모리의 내용을 디스크에 쓴다. 이 방식은 백업 시점의 데이터만 유지된다.
Append-only file(AOF)방식은 non-blocking형태로 dataset이 변경될때마다 모두 로그파일에 기록한다. 데이터 유실 방지를 목적이 있으나 Redis 서버의 재구동시 데이터 정합성 체크가 길어져 서버 구동이 느려진다.
그래서 RDB와 AOF를 잘 활용하는 전략이 필요하다.

Redis의 서버 구동은 redis.c 파일의 main() 함수부터 시작된다.
redis.c : 1672
int main(int argc, char **argv) {
    time_t start;

    initServerConfig();
    if (argc >= 2 && strcmp(argv[1], "--test-memory") == 0) {
        if (argc == 3) {
            memtest(atoi(argv[2]),50);
            exit(0);
        } else {
            fprintf(stderr,"Example: ./redis-server --test-memory 4096\n\n");
            exit(1);
        }
    }
    if (argc == 2) {
        if (strcmp(argv[1], "-v") == 0 ||
            strcmp(argv[1], "--version") == 0) version();
        if (strcmp(argv[1], "--help") == 0) usage();
        resetServerSaveParams();
        loadServerConfig(argv[1]);
    } else if ((argc > 2)) {
        usage();
    } else {
        redisLog();
    }
    if (server.daemonize) daemonize();
    initServer();
    if (server.daemonize) createPidFile();
.....
    aeSetBeforeSleepProc(server.el,beforeSleep);
    aeMain(server.el);
    aeDeleteEventLoop(server.el);
    return 0;
}

처음, 전역변수인 redisServer의 속성을 초기화하기 위해 initServerConfig()함수를 호출한다. redisServer 구조체는 redis 서버측의 프로그램 구성의 대부분 속성을 포함하고 있다.
redisServer에 대해 간략히 설명하면..
  • lruclock : LRU는 least recently used의 약자로 서버의 타임스탬프. 이 변수와 키의 타임스탬프가 짧을수록 최신으로 사용된 것으로 판단함.
  • requirepass : 클라이언트가 다른 명령을 보내기 전에 AUTH 를 수행 할 것을 요구하는 파라미터.
  • runid : 구동될때마다 고유 ID 할당, 여러 Redis 서버 프로그램을 식별하는데 사용함.
  • sentinel_mode : master나 slave 가 제대로 동작하는지 모니터링을 하거나, API를 제공해서 관리자가 장애 상황에 대해서 통보 받을 수 있는 알림, 그리고 마스터가 장애가 나면 Slave를 자동으로 새로운 마스터로 만들고 다른 Slave들이 해당 마스터를 사용하도록 설정하는 자동 오류 복구 등을 위해 여러 redis 인스턴스를 관리하는데 사용되는 값.
  • maxidletime : 서버에 클라이언트 연결이 이 값을 초과하는 경우 끊어지게 된다. 그러나, Redis의 마스터, 슬레이브는 해당되지 않는다.
  • commands : redis 커맨드용 딕셔너리.
  • dbnum : 설정 파일의 databases 값. 즉, 데이터 베이스 개수.
  • maxmemory_policy : Redis는 사용 메모리에 상한을 설정하고 임계값을 초과하는 경우 정책(maxmemory-policy)을 설정할 수 있도록 되어 있다. 디폴트는 volatile-lru. Redis가 LRU 캐시 정책을 사용한다는 의미이다.
그 다음으로, resetServerSaveParams() 함수는 saveparam 구조체를 리셋시킨다. saveparam은 persistence 매개변수로 설정파일의 save 매개변수(스냅샷 저장 주기)와 관련이 되는 구조체이다.
save 900 1
save 300 10
save 60 10000

그리고 loadServerConfig()는 Redis 구동 스크립트의 첫번재 인자값인 /etc/redis/6379.conf를 읽어서 설정 파일을 구동한다.
/database/server/redis-2.4.17/bin/redis-server /etc/redis/6379.conf

daemonize()함수는 부모 프로세스가 존재하는지 확인하고 난 다음 부팅모드인 daemonize가 yes면 백그라운드 프로세스로 구동된다.
daemonize yes

initServer()인 서버 초기화 함수는 아래와 같은 내용을 수행한다.

1) sighup, sigpipe로 signal 핸들러를 설정하고.
2) syslog 파일 열고.
3) server.clients, server.slaves, server.monitors 등 서버 구조를 초기화하고.
4) 객체 생성 오버헤드를 피해기 위해서 공유 객체를 만들어(-2*63 and 2^63-1 범위츼 정수 객체를 재사용할 수 있고, 커맨드/응답 문자열/에러 메세지 등도 재사용하게 함)재사용 하고.
5) aeCreateEventLoop(ae.c)에서 I/O event Notification(세가지 I/O 멀티플렉싱 epoll,kqueue, select 사용) 루프를 플랫폼 독립적으로 래핑해서 만들고.
  - aeApiCreate(ae_epoll.c) epoll_create 하고.
  - 참고로 epoll은 epoll_create(이벤트 저장 공간 확보), epoll_wait(이벤트 감지), epoll_ctl(이벤트 풀 제어) API만 알면되고, 이에 더해 LT(상태 유지되는 동안 감지), ET(변화시점에 감지, 논블럭킹) 모드 또한 이해해 두면 좋다.

6) server.db인 redisDb의 메모리 확보.
- 설정파일의 databases 값만큼 메모리 확보함.

7) anetTcpServer(anet.c)로 리스너 redis 포트, 유닉스 소켓 파일과 TCP를 연다.
8) redisDb 초기화 계속
  • dict : DB에 대한 keyspace.
  • expires : 키 만료 시간.
  • blocking_keys : 데이터를 대기하는 클라이언트 키(B{L,R}POP).
  • watched_keys : MULTI/EXEC CAS에 대한 키.
  • io_keys(VM Enabled) : I/O를 대기하는 클라이언트 키.


9) redisServer 초기화 추가 계속
- pubsub 채널 초기화, cronloops 초기화, AOF 버퍼 초기화, bgsavechildpid(비동기 프로세스 아이디)/bgrewritechildpid(비동기 AOF 쓰기 프로세스 아이디) 초기화 등
- 명령어 실행 수, 연결 수, 만료키 키 미스율, 키히트율 등의 통계 정보 초기화

10) aeCreateTimeEvent(ae.c)
 - 타임 이벤트를 생성하고 타임 이벤트 루프에 콜백 함수 serverCron을 등록한다. 1ms마다 serverCron을 수행된다.
 - serverCron의 내용을 살펴보면
   . redisServer의 unixtime 셋팅하고 lruclock(1.5년)을 셋팅하고
   . tryResizeHashTables()는 dic과 expire의 실제 사용 사이즈와 할당된 사이즈의 비율이 10%보다 작으면 (REDIS_HT_MINFILL) dic 리사이징을 함.
   . incrementallyRehash()는 데이터 베이스 수만큼 rehashidx가 -1이 아니면 dictRehash를 호출해서 ht[0]의 데이터를 하나씩 ht[1]으로 이동하고 ht[0]비우는 작업.
   . closeTimedoutClients()은 타임아웃 지난 클라이언트는 연결을 끊고 자원을 회수한다.
   . schedule에 의해 rewriteAppendOnlyFileBackground()는 자식 프로세스를 생성(fork)하고, DB의 상태와 커맨드를 반영하기 위해 임시 파일을 작성하도록 rewriteAppendOnlyFile()을 호출한다. 여기는 AOF파일을 작성한다.
   . rewriteAppendOnlyFile() 함수는 BGREWRITEAOF, REWRITEAOF에 의해 redisDB의 딕셔너리(메모리)에 데이터를 읽어서 데이터 타입별로 temp-rewriteaof파일에 임시로 쓴다. 그리고 aof_fsync()함수를 호출하고, 이 함수는 fdatasync()를 통해 디스크로 저장한다. 그런 후 temp-rewriteaof파일을 temp-rewriteaof-bg로 리네임한다.
   . 자식 프로세스가 종료되면 부모 프로세스가 시그널을 수신해 자식 파일에 의해 생성된 임시파일의 데이터를 쓰고 기존 tmp파일을 새로운 AOF파일 이름으로 바꾼다.
   . fsync작업의 100ms 지연이 발생해서 비동기적으로 처리하도록 backgroundSaveDoneHandler(aof.c)과 backgroundRewriteDoneHandler(aof.c)을 백그라운드로 구동하게 한다.
     ~ bgsave후 자식 프로세스가 종료되었다면 backgroundSaveDoneHandler()를 호출해서 부모 프로세스가 Background saving 종료 처리 및 Slave 동기화 처리를 한다.
     ~ updateSlavesWaitingBgsave(replication.c) 함수에서는 sendBulkToSlave(replication.c)함수를 통해 슬래이브 동기화 처리를 수행한다.
     ~ bgrewrite후 자식 프로세스 쓰기 완료해서 종료되면 backgroundRewriteDoneHandler()함수를 호출해서 부모 프로세스가 차이만큼 bgrewritebuf의 데이터를 temp-rewriteaof-bg 템프 파일에 추가해서 쓴다. 그리고 temp-rewriteaof-bg파일명을 설정 파일의 appendfilename으로 바꾼다.
     ~ 그런 후 디스크에 쓰기 위해서는 APPENDFSYNC_ALWAYS(느리지만 안전)면 fdatasyc()시스템 함수를 호출하고, APPENDFSYNC_EVERYSEC(1초단위로)면 aof_background_fsync() 함수를 호출해 처리한다.
     ~ 그리고 aofUpdateCurrentSize()를 호출해서 AOF 사이즈 갱신하고 새로운 데이터를 받기 위해서 AOF buffer를 초기화한다.
   . bgsave, bgrewrite가 아닐 경우 save 주기 설정에 따라 rdbSaveBackground() 함수를 호출해서 설정의 dbfilename이름의 rdb파일에 스냅샷을 저장한다. copy-on-write 개념이 적용된 예이다.
     ~ 자식 프로세스를 생성(fork)해서 임시 RDB파일에 데이터를 쓴다.
     ~ 데이터 쓰기기 완료되면 temp.rdb 파일을 설정의 dbfilename이름으로 리네임한다.
  . auto-aof-rewrite-percentage 설정이 되어 있고, 현재 aof 파일 크기가 auto_aofrewrite_min_size 설정값보다 클경우 rewriteAppendOnlyFileBackground() 함수를 호출해서 AOF rewriting을 자동 실행한다.
     ~ 자식 프로세스를 생성하고 rewriteAppendOnlyFile() 함수를 호출해서 temp-rewriteaof파일에 데이터 타입별로 데이터를 쓴다. 그리고 aof_fsync()로 디스크에 쓰기를 하고 난 다음 temp-rewriteaof파일을 temp-rewriteaof-bg파일로 리네임한다.
  . flush 플래그가 딜레이 되었다고 설정되어 있으면 flushAppendOnlyFile()를 통해 메모리 버퍼에 있는 aof를 디스크 파일로 쓰기를 한다.
     ~ force 참이 아니면, aof_flush_postponed_start에 현재 시간을 넣는다.
     ~ aofbuf 버퍼의 내용을 appendfd에 쓰기를 한다. aofbuf가 4000바이트보다 작으면 재사용하기 위해 clear하고 크면 free한다.
     ~ appendfsync 설정 정책에 따라 aof 파일을 디스크에 쓴다.

   . 마스터 서버라면 activeExpireCycle()를 호출해서 데이터의 만료처리를 한다.
   . 수행내용은, 10개씩 임이의 expire key를 검사하고 하나씩 만료처리를 한다. 만료처리가 2개 이하일 경우 루프 종료.

11) aeCreateFileEvent(ae.c)
 - AE 파일 이벤트 생성하고(epoll_create) TCP나 유닉스 소켓 포트를 처리하기 위해(클라이언트 요청 처리) 커넥션 핸들러를 이벤트 루프에 등록한다.
 - 각 클라이언트는 AE 파일 이벤트, 비동기 수신 커맨드를 통해 요청 커맨드를 연결시켜준다.

12) "-appendonly yes"로 지정하면 appendfile을 연다.
13) maxmemory 정책 설정한다.

createPidFile()는 server.pidfile을 연다.

appendonly가 yes면 apppend file을 읽고 rdb일 경우 rdb파일을 읽는다.

aeSetBeforeSleepProc는 이벤트에 대한 알림을 위해서 대기하는 기능을 한다. 이벤트를 알리는 기능을 수행한다. beforeSleep은 두가지 기능을 수행하는데, VM이 활성화되었을 때 디스크에 스왑되어 있던 키들을 처리하는거와 AOF내용을 디스크에 쓰기 작업을 한다.
 - readQueryFromClient()를 통해 클라이언트로부터 쿼리를 읽고.
 - lookupCommand()로 요청 커맨드를 찾는다.
void beforeSleep(struct aeEventLoop *eventLoop) {
    REDIS_NOTUSED(eventLoop);
    listNode *ln;
    redisClient *c;

    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);
            resetClient(c);
            /* There may be more data to process in the input buffer. */
            if (c->querybuf && sdslen(c->querybuf) > 0) {
                server.current_client = c;
                processInputBuffer(c);
                server.current_client = NULL;
            }
        }
    }
    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(0);
}

그 다음은 aeMain이 수행된다. 여기에는 이벤트 폴링 루프 기능이 구현되어 있다.
 - aeProcessEvents는 aeApiPoll 함수 호출을 통해 이벤트 수신을 대기한다.(epoll_wait)

aeDeleteEventLoop가 마지막으로 수행된다.
  - aeApiFree 함수 호출을 통해 이벤트 제거 처리를 해 자원을 회수한다.

이상으로 서버의 구동 시나리오에 대해서 살며봤고 이어 클라이언트로 부터 요청을 받았을 때 처리 시나리오에 대해서 알아본다.

Redis 커맨드 처리를 위한 소스 흐름



새로운 연결이 들어올 때까지 acceptTcpHandler가 이벤트 루프에 등록되어 대기하고 있다.
networking.c:455
void acceptTcpHandler(aeEventLoop *el, int fd, void *privdata, int mask) {
    int cport, cfd; 
    char cip[128];
    REDIS_NOTUSED(el);
    REDIS_NOTUSED(mask);
    REDIS_NOTUSED(privdata);

    cfd = anetTcpAccept(server.neterr, fd, cip, &cport);
    if (cfd == AE_ERR) {
        redisLog(REDIS_WARNING,"Accepting client connection: %s", server.neterr);
        return;
    }    
    redisLog(REDIS_VERBOSE,"Accepted %s:%d", cip, cport);
    acceptCommonHandler(cfd);
}

새로운 연결이 올 때는 TCP를 통해서는 anetTcpAccept()가 호출된다. 그 다음 acceptCommonHandler()가 수행된다. 새로운 클라이언트가 연결을 수신했을 때
anet.c:330
int anetTcpAccept(char *err, int s, char *ip, int *port) {
    int fd; 
    struct sockaddr_in sa; 
    socklen_t salen = sizeof(sa);
    if ((fd = anetGenericAccept(err,s,(struct sockaddr*)&sa,&salen)) == ANET_ERR)
        return ANET_ERR;

    if (ip) strcpy(ip,inet_ntoa(sa.sin_addr));
    if (port) *port = ntohs(sa.sin_port);
    return fd; 
}

anetGenericAccept을 호출해서 수신 대기 상태로 된다. 그리고 acceptTcpHandler함수의 acceptCommonHandler()를 호출하는데 여기서 클라이언트의 수신을 처리하게 된다.
acceptCommonHandler에서는 클라이언트마다 새연결을 처리할 redisClient를 생성한다. 동시에 최대 클라이언트 개수를 체크한다.
networking.c:431
static void acceptCommonHandler(int fd) {
    redisClient *c;
    if ((c = createClient(fd)) == NULL) {
        redisLog(REDIS_WARNING,"Error allocating 
         resoures for the client");
        close(fd); /* May be already closed, just ingore errors */
        return;
    }
    if (server.maxclients && 
      listLength(server.clients) > server.maxclients) {
        char *err = "-ERR max number of clients reached\r\n";

        if (write(c->fd,err,strlen(err)) == -1) {
            /* Nothing to do, Just to avoid the warning... */
        }
        freeClient(c);
        return;
    }
    server.stat_numconnections++;
}

createClient(networking.c) 함수에서는 I/O 멀티플렉싱 epoll 이벤트 루프에 readQueryFromClient(networking.c) 함수를 등록한다. 지금까지가 Redis서버가 클라이언트의 커맨드를 수신할 준비가 된 것이다.
networking.c:23
redisClient *createClient(int fd) {
    redisClient *c = zmalloc(sizeof(redisClient));
    c->bufpos = 0;

    anetNonBlock(NULL,fd);
    anetTcpNoDelay(NULL,fd);
    if (aeCreateFileEvent(server.el,fd,AE_READABLE,
        readQueryFromClient, c) == AE_ERR)
    {
        close(fd);
        zfree(c);
        return NULL;
    }
    ...
}

readQueryFromClient() 함수에서 데이터를 읽을 때 1GB이상의 크기면 오류, 그 외 정상적으로 읽은 다음, processInputBuffer()함수를 호출해서 프로토콜을 분석한다.
 - redis는 두 프로토콜을 지원하는데, 하나는 인라인(processInlineBuffer), 다른 하나는 multibulk(processMultibulkBuffer)이다.
networking.c:911
void readQueryFromClient(aeEventLoop *el, int fd, void *privdata, int mask) {
    redisClient *c = (redisClient*) privdata;
    char buf[REDIS_IOBUF_LEN];
    int nread;
    REDIS_NOTUSED(el);
    REDIS_NOTUSED(mask);

    server.current_client = c;
    nread = read(fd, buf, REDIS_IOBUF_LEN);
    ...
    processInputBuffer(c);
    server.current_client = NULL;
}

processInputBuffer()에서 프로토콜 분석이 완료되면 클라이언트의 모든 입력을 읽었으며 명령어 처리를 위한 실행단계가 이루어진다. 그리고 processCommand(redis.c)함수를 호출한다.
 - processCommand() 처리가 끝나면 resetClient(networking.c)호출해서 클라이언트 자원을 회수한다.
redis.c:1068
int processCommand(redisClient *c) {
    if (!strcasecmp(c->argv[0]->ptr,"quit")) {
        addReply(c,shared.ok);
        c->flags |= REDIS_CLOSE_AFTER_REPLY;
        return REDIS_ERR;
    }
    c->cmd = c->lastcmd = lookupCommand(c->argv[0]->ptr);
    .....

processCommand() 함수에서는 클라이언트의 요청 커맨드를 처리할려면 요청 커맨드를 찾아야 한다. 그래서 lookupCommand()함수를 호출하게 된다.
 - 인증 요청이 필요하면 인증 처리를 수행하고
 - maxmemory면 freeMemoryIfNeeded() 함수 호출을 통해 메모리가 부족하면 LRU에 따라 메모리 해제가 이루어진다.
 - pubsub 커맨드일 경우 유효성 체크를 한다.
 - 멀티 커맨드면 queueMultiCommand()함수를 호출하고 인라인일 경우 call() 함수를 호출한다.

call 함수는 아래와 같다.
redis.c:1041
void call(redisClient *c) {
    long long dirty, start = ustime(), duration;

    dirty = server.dirty;
    c->cmd->proc(c);
    dirty = server.dirty-dirty;
    duration = ustime()-start;
    slowlogPushEntryIfNeeded(c->argv,c->argc,duration);

    if (server.appendonly && dirty > 0)
        feedAppendOnlyFile(c->cmd,c->db->id,c->argv,c->argc);
    if ((dirty > 0 || c->cmd->flags & REDIS_CMD_FORCE_REPLICATION) &&
        listLength(server.slaves))
        replicationFeedSlaves(server.slaves,c->db->id,c->argv,c->argc);
    if (listLength(server.monitors))
        replicationFeedMonitors(server.monitors,c->db->id,c->argv,c->argc);
    server.stat_numcommands++;
}

proc()함수를 통해 커맨드 처리를 수행한다.
feedAppendOnlyFile()함수에서는 커맨드가 일어난 거에 대한 추적을 위해서 자동 저장을 해 줘야 하는 기능을 한다.
replicationFeedSlaves()함수는 사용자 명령에 의한 슬레이브 동기화 처리를 한다.
redis.c:73
struct redisCommand *commandTable;
struct redisCommand readonlyCommandTable[] = {
    {"get",getCommand,2,0,NULL,1,1,1},
    {"set",setCommand,3,REDIS_CMD_DENYOOM,NULL,0,0,0},
....
get 명령어를 실행했다면 아래의 함수가 호출된다. 데이터 타입에 맞게 유추할 수 있다.
t_string.c:67
void getCommand(redisClient *c) {
    getGenericCommand(c);
}
t_string.c:52
int getGenericCommand(redisClient *c) {
    robj *o; 

    if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.nullbulk)) == NULL)
        return REDIS_OK;

    if (o->type != REDIS_STRING) {
        addReply(c,shared.wrongtypeerr);
        return REDIS_ERR;
    } else {
        addReplyBulk(c,o);
        return REDIS_OK;
    }   
}

lookupKeyReadOrReply() 함수가 호출되는데 키가 읽기 기반이고, db에서 키를 읽고 리턴을 addReply() 함수를 통해 한다.
networking.c:216
void addReply(redisClient *c, robj *obj) {
    if (_installWriteEvent(c) != REDIS_OK) return;
    redisAssert(!server.vm_enabled || obj->storage == REDIS_VM_MEMORY);
    if (obj->encoding == REDIS_ENCODING_RAW) {
        if (_addReplyToBuffer(c,obj->ptr,sdslen(obj->ptr)) != REDIS_OK)
            _addReplyObjectToList(c,obj);
    } else {
        /* FIXME: convert the long into string and use _addReplyToBuffer()
         * instead of calling getDecodedObject. As this place in the
         * code is too performance critical. */
        obj = getDecodedObject(obj);
        if (_addReplyToBuffer(c,obj->ptr,sdslen(obj->ptr)) != REDIS_OK)
            _addReplyObjectToList(c,obj);
        decrRefCount(obj);
    }
}

_installWriteEvent() 함수에서는 리턴을 위해서 이벤트 루푸에 sendReplyToClient 함수를 등록한다.
그리고 데이터 쓸 준비를 한다. 로우 데이터 포멧을 리턴하면 버퍼의 사이즈보다 응답 데이터가 더 클경우 리스트에 넣어서 리턴을 한다.
마지막으로 sendReplyToClient()함수를 통해서 데이터를 클라이언트 버퍼에 넣어서 리턴한다.
event 삭제하고 클라이언트 종료한다.

소스흐름을 종합해 보면

1. Redis 서버 구동시



2. Redis 서버로 클라이언트의 커맨드 수신 시



3. 서버 로그
1) Master 로그

[28639] 05 Jun 17:21:06 - Accepted 192.168.0.201:57054
[28639] 05 Jun 17:21:06 * Slave ask for synchronization
[28639] 05 Jun 17:21:06 * Starting BGSAVE for SYNC
[28639] 05 Jun 17:21:06 * Background saving started by pid 7574
[28639] 05 Jun 17:21:06 * DB saved on disk
[28639] 05 Jun 17:21:06 * Background saving terminated with success
[28639] 05 Jun 17:21:06 * Synchronization with slave succeeded
[28639] 05 Jun 17:21:06 - 0 clients connected (1 slaves), 717608 bytes in use


2) Slave 로그

[24693] 05 Jun 17:23:19 * The server is now ready to accept connections on port 6379
[24693] 05 Jun 17:23:19 - 0 clients connected (0 slaves), 717596 bytes in use
[24693] 05 Jun 17:23:19 * Connecting to MASTER...
[24693] 05 Jun 17:23:19 * MASTER <-> SLAVE sync started
[24693] 05 Jun 17:23:19 * Non blocking connect for SYNC fired the event.
[24693] 05 Jun 17:23:19 * MASTER <-> SLAVE sync: receiving 10 bytes from master
[24693] 05 Jun 17:23:19 * MASTER <-> SLAVE sync: Loading DB in memory
[24693] 05 Jun 17:23:19 * MASTER <-> SLAVE sync: Finished with success


[참고 사이트]
Tags :


Avatar: 황재연

Re: [Redis 소스 분석] 서버 구동 및 커맨드 처리 흐름

 안녕하세요

Redis 관련 자료 찾는 중 들렀습니다.

훌륭한 분석 자료 잘 보고 갑니다. 감사합니다.

Re: [Redis 소스 분석] 서버 구동 및 커맨드 처리 흐름

그림으로 쉽게 설명해주셔서 감사합니다^^ 

Re: [Redis 소스 분석] 서버 구동 및 커맨드 처리 흐름

좋은정보 공유 감사합니다.

 

제 블로그에 링크 걸어도 될까요?~ 두고두고 보고싶네요 ㅎㅎ

Avatar: 미물

Re: [Redis 소스 분석] 서버 구동 및 커맨드 처리 흐름

네.. 거세요. ㅋㅋ


Add a comment Send a TrackBack