본문 바로가기

좋아하는 것_매직IT/9.redis

23.Redis, 레디스 공유객체에 대해서 알아볼께요.

반응형

Redis 공유객체란?

  • 이전에 문자열 데이터 인코딩에서 잠시 살펴본것 같이, Redis 는 자주 사용되는 값을 전역변수인 공유객체에 저장해 두고 사용함.
  • 공유객체에 포함하고 있는 값 리스트
    • 에러메시지
    • 프로토콜을 위한 문자열
    • 자주 사용되는 문자열
    • 0~9999 까지의 숫자
    • 기타 등등.
  • 모든 값은 redisObject 구조체를 사용해서 표현됨.
  • redisObject 구조체 선언

redis 공유객체 선언

  • server.h 헤더 파일

레디스 서버는 server.c 소스파일의 main 함수를 통해서 시작됨.

  • main 함수 시작 시 Redis 가 사용할 라이브러리와 서버의 기본 설정 변수등을 초기화하고 redis.conf 설정 파일을 읽어서 레디스 서버에 적용함.
    • main 함수 주요소스
int main(int argc, char **argv) {
    struct timeval tv;
    int j;
...(생략)...
    setlocale(LC_COLLATE,"");
    tzset(); /* Populates 'timezone' global. */
    zmalloc_set_oom_handler(redisOutOfMemoryHandler);
    srand(time(NULL)^getpid());
    gettimeofday(&tv,NULL);

    char hashseed[16];
    getRandomHexChars(hashseed,sizeof(hashseed));
    dictSetHashFunctionSeed((uint8_t*)hashseed);
    server.sentinel_mode = checkForSentinelMode(argc,argv);
    initServerConfig();
    moduleInitModulesSystem();

...(생략)...

    if (argc == 1) {
        serverLog(LL_WARNING, "Warning: no config file specified, using the default config. In order to specify a config file use %s /path/to/%s.conf", argv[0], server.sentinel_mode ? "sentinel" : "redis");
    } else {
        serverLog(LL_WARNING, "Configuration loaded");
    }

    server.supervised = redisIsSupervised(server.supervised_mode);
    int background = server.daemonize && !server.supervised;
    if (background) daemonize();

    initServer();
    if (background || server.pidfile) createPidFile();
    redisSetProcTitle(argv[0]);

...(생략)...

    /* Warning the user about suspicious maxmemory setting. */
    if (server.maxmemory > 0 && server.maxmemory < 1024*1024) {
        serverLog(LL_WARNING,"WARNING: You specified a maxmemory value that is less than 1MB (current value is %llu bytes). Are you sure this is what you really want?", server.maxmemory);
    }

    aeSetBeforeSleepProc(server.el,beforeSleep);
    aeSetAfterSleepProc(server.el,afterSleep);
    aeMain(server.el);
    aeDeleteEventLoop(server.el);
    return 0;
}
  • 일련의 작업 이 성공적으로 완료되면, initServer() 함수가 호출 됨.
  • initServer() 함수에서 공유객체 생성을 위한 createSharedObjects() 함수가 호출되어지는 구조임.
void initServer(void) {
    int j;

    signal(SIGHUP, SIG_IGN);
    signal(SIGPIPE, SIG_IGN);
    setupSignalHandlers();

    if (server.syslog_enabled) {
        openlog(server.syslog_ident, LOG_PID | LOG_NDELAY | LOG_NOWAIT,
            server.syslog_facility);
    }

    server.hz = server.config_hz;
    server.pid = getpid();
    server.current_client = NULL;
    server.clients = listCreate();
    server.clients_index = raxNew();
    server.clients_to_close = listCreate();
    server.slaves = listCreate();
    server.monitors = listCreate();
    server.clients_pending_write = listCreate();
    server.slaveseldb = -1; /* Force to emit the first SELECT command. */
    server.unblocked_clients = listCreate();
    server.ready_keys = listCreate();
    server.clients_waiting_acks = listCreate();
    server.get_ack_from_slaves = 0;
    server.clients_paused = 0;
    server.system_memory_size = zmalloc_get_memory_size();

    createSharedObjects();

    ...(생략)...

    /* Open the AOF file if needed. */
    if (server.aof_state == AOF_ON) {
        server.aof_fd = open(server.aof_filename,
                               O_WRONLY|O_APPEND|O_CREAT,0644);
        if (server.aof_fd == -1) {
            serverLog(LL_WARNING, "Can't open the append-only file: %s",
                strerror(errno));
            exit(1);
        }
    }

    /* 32 bit instances are limited to 4GB of address space, so if there is
     * no explicit limit in the user provided configuration we set a limit
     * at 3 GB using maxmemory with 'noeviction' policy'. This avoids
     * useless crashes of the Redis instance for out of memory. */
    if (server.arch_bits == 32 && server.maxmemory == 0) {
        serverLog(LL_WARNING,"Warning: 32 bit instance detected but no memory limit set. Setting 3 GB maxmemory limit with 'noeviction' policy now.");
        server.maxmemory = 3072LL*(1024*1024); /* 3 GB */
        server.maxmemory_policy = MAXMEMORY_NO_EVICTION;
    }

    if (server.cluster_enabled) clusterInit();
    replicationScriptCacheInit();
    scriptingInit(1);
    slowlogInit();
    latencyMonitorInit();
    bioInit();
    server.initial_memory_usage = zmalloc_used_memory();
}
  • 위의 소스 중간에 createSharedObjects() 함수 호출 부분이 보임.
  • createSharedObjects 함수 소스 코드
struct sharedObjectsStruct shared;

...(생략)...

void createSharedObjects(void) {
    int j;

    shared.crlf = createObject(OBJ_STRING,sdsnew("\r\n"));
    shared.ok = createObject(OBJ_STRING,sdsnew("+OK\r\n"));
    shared.err = createObject(OBJ_STRING,sdsnew("-ERR\r\n"));
    shared.emptybulk = createObject(OBJ_STRING,sdsnew("$0\r\n\r\n"));
    shared.czero = createObject(OBJ_STRING,sdsnew(":0\r\n"));
    shared.cone = createObject(OBJ_STRING,sdsnew(":1\r\n"));
    shared.cnegone = createObject(OBJ_STRING,sdsnew(":-1\r\n"));
    shared.nullbulk = createObject(OBJ_STRING,sdsnew("$-1\r\n"));
    shared.nullmultibulk = createObject(OBJ_STRING,sdsnew("*-1\r\n"));
    shared.emptymultibulk = createObject(OBJ_STRING,sdsnew("*0\r\n"));
    shared.pong = createObject(OBJ_STRING,sdsnew("+PONG\r\n"));
    shared.queued = createObject(OBJ_STRING,sdsnew("+QUEUED\r\n"));
    shared.emptyscan = createObject(OBJ_STRING,sdsnew("*2\r\n$1\r\n0\r\n*0\r\n"));
    shared.wrongtypeerr = createObject(OBJ_STRING,sdsnew(
        "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"));
    shared.nokeyerr = createObject(OBJ_STRING,sdsnew(
        "-ERR no such key\r\n"));
    shared.syntaxerr = createObject(OBJ_STRING,sdsnew(
        "-ERR syntax error\r\n"));
    shared.sameobjecterr = createObject(OBJ_STRING,sdsnew(
        "-ERR source and destination objects are the same\r\n"));
    shared.outofrangeerr = createObject(OBJ_STRING,sdsnew(
        "-ERR index out of range\r\n"));
    shared.noscripterr = createObject(OBJ_STRING,sdsnew(
        "-NOSCRIPT No matching script. Please use EVAL.\r\n"));
    shared.loadingerr = createObject(OBJ_STRING,sdsnew(
        "-LOADING Redis is loading the dataset in memory\r\n"));
    shared.slowscripterr = createObject(OBJ_STRING,sdsnew(
        "-BUSY Redis is busy running a script. You can only call SCRIPT KILL or SHUTDOWN NOSAVE.\r\n"));
    shared.masterdownerr = createObject(OBJ_STRING,sdsnew(
        "-MASTERDOWN Link with MASTER is down and replica-serve-stale-data is set to 'no'.\r\n"));
    shared.bgsaveerr = createObject(OBJ_STRING,sdsnew(
        "-MISCONF Redis is configured to save RDB snapshots, but it is currently not able to persist on disk. Commands that may modify the data set are disabled, because this instance is configured to report errors during writes if RDB snapshotting fails (stop-writes-on-bgsave-error option). Please check the Redis logs for details about the RDB error.\r\n"));
    shared.roslaveerr = createObject(OBJ_STRING,sdsnew(
        "-READONLY You can't write against a read only replica.\r\n"));
    shared.noautherr = createObject(OBJ_STRING,sdsnew(
        "-NOAUTH Authentication required.\r\n"));
    shared.oomerr = createObject(OBJ_STRING,sdsnew(
        "-OOM command not allowed when used memory > 'maxmemory'.\r\n"));
    shared.execaborterr = createObject(OBJ_STRING,sdsnew(
        "-EXECABORT Transaction discarded because of previous errors.\r\n"));
    shared.noreplicaserr = createObject(OBJ_STRING,sdsnew(
        "-NOREPLICAS Not enough good replicas to write.\r\n"));
    shared.busykeyerr = createObject(OBJ_STRING,sdsnew(
        "-BUSYKEY Target key name already exists.\r\n"));
    shared.space = createObject(OBJ_STRING,sdsnew(" "));
    shared.colon = createObject(OBJ_STRING,sdsnew(":"));
    shared.plus = createObject(OBJ_STRING,sdsnew("+"));
    for (j = 0; j < PROTO_SHARED_SELECT_CMDS; j++) {
        char dictid_str[64];
        int dictid_len;

        dictid_len = ll2string(dictid_str,sizeof(dictid_str),j);
        shared.select[j] = createObject(OBJ_STRING,
            sdscatprintf(sdsempty(),
                "*2\r\n$6\r\nSELECT\r\n$%d\r\n%s\r\n",
                dictid_len, dictid_str));
    }
    shared.messagebulk = createStringObject("$7\r\nmessage\r\n",13);
    shared.pmessagebulk = createStringObject("$8\r\npmessage\r\n",14);
    shared.subscribebulk = createStringObject("$9\r\nsubscribe\r\n",15);
    shared.unsubscribebulk = createStringObject("$11\r\nunsubscribe\r\n",18);
    shared.psubscribebulk = createStringObject("$10\r\npsubscribe\r\n",17);
    shared.punsubscribebulk = createStringObject("$12\r\npunsubscribe\r\n",19);
    shared.del = createStringObject("DEL",3);
    shared.unlink = createStringObject("UNLINK",6);
    shared.rpop = createStringObject("RPOP",4);
    shared.lpop = createStringObject("LPOP",4);
    shared.lpush = createStringObject("LPUSH",5);
    shared.rpoplpush = createStringObject("RPOPLPUSH",9);
    shared.zpopmin = createStringObject("ZPOPMIN",7);
    shared.zpopmax = createStringObject("ZPOPMAX",7);
    for (j = 0; j < OBJ_SHARED_INTEGERS; j++) {
        shared.integers[j] =
            makeObjectShared(createObject(OBJ_STRING,(void*)(long)j));
        shared.integers[j]->encoding = OBJ_ENCODING_INT;
    }
    for (j = 0; j < OBJ_SHARED_BULKHDR_LEN; j++) {
        shared.mbulkhdr[j] = createObject(OBJ_STRING,
            sdscatprintf(sdsempty(),"*%d\r\n",j));
        shared.bulkhdr[j] = createObject(OBJ_STRING,
            sdscatprintf(sdsempty(),"$%d\r\n",j));
    }

    /* The following two shared objects, minstring and maxstrings, are not
     * actually used for their value but as a special object meaning
     * respectively the minimum possible string and the maximum possible
     * string in string comparisons for the ZRANGEBYLEX command. */
    shared.minstring = sdsnew("minstring");
    shared.maxstring = sdsnew("maxstring");
}
  • 위와 같이 Redis는 자주 사용되는 값들을 레디스 서버 기동시 미리 생성해 두고 마치 상수처럼 사용함.
  • 예전에 살펴보았던 REDIS_SHARED_INTEGERS 는 미리 등록하고자 하는 숫자의 개수를 지정하는 상수임.
  • server.h 헤더파일에 아래와 같이 현재 10000으로 지정되어 있음

  • 해당 값을 튜닝해서, 숫자 데이터를 많이 저장한다고 하면, 해당 상수를 적정값으로 변경 후 재 컴파일해서 데이터 저장공간을 더 조밀하게 사용할 수 있음.
    • 그러나, 해당 값이 너무 커지게 되면, 공유객체를 할당하는데 메모리가 더들게 됨.
      • 즉, 꼭 적정값을 산정해서 변경하기를 조언드립니다.

 결론

  • Redis 는 자주 사용하는 값들을 공유객체로 전역변수에 저장해 두고 사용함.
  • Redis 가 공유객체를 사용하는 이유는, 좀 더 효율적으로 메모리를 사용하려는 개발자의 아이디어를 차용함.
    • 즉, 공유객체를 통해서 메모리 사용량을 줄일 수 있음.
  • Redis 는 오픈 소스 이기때문에 소스 분석 후 사용하려는 개발자가 소스 수정을 통해서 좀 더 현재 서비스에 맞도록 튜닝을 할 수 있는 장점이 있음.
    • 다시말해서 Redis 의 저장구조를 이해한다면 Redis를 더 효율적으로 사용할 수 있음.
  • Redis의 장점이라고 한다면, 다른 오픈소스에 비해서 소스코드 분량이 적은편에 속함.
    • 즉, 상대적으로 분석하기도 수월한거 같음
    • 그러나, 소스코드를 처음부터 끝까지 완벽하게 파악하려는 시도보다는 server.c 파일을 기준으로 가볍게 흐름을 따라가 보고 흐름이 파악되었으면, 세부흐름을 훑어 보는것을 조언드립니다.

  • 오늘의 명언 한마디
    • 사랑하는 일을하라, 무엇을 해야 기쁨이 느껴지는지 모르겠다면, "내가좋아하는 게 뭐지?"라고 자문하라. 기뻐하는 일에 몰입하면, 기쁨을 발산하게 되고 따라서, 그려한 일이 쏟아져 들어올 것이다. -론다번 지음, "시크릿" 중에서..

  • 오늘의 영어 한마디
    • 질문) Maybe I can help you out.
      • 내가 널 도울 만한 거 있니?
    • 대답) It's none of your business!
      • 네가 무슨 상관이야?
    • 해설
      • help you out 은 "너를 돕다"
      • It's none of your business! 는 관용어.
        • 같은 표현으로 It's my businness! "그건 내 일이야" 도 있는데 즉, 쉽게말해서 쓸데없는 참견이라는 의미임.
728x90
300x250