2013년 4월 8일 월요일

Redis Run ID 만들기

Redis는 각각의 Redis instance를 구별하기 위해 Run ID라는 것을 사용합니다. 이 값이 같으면 같은 instance로 확인하는 것이죠.

이 값의 생성 방법은 다음과 같습니다.

시스템의 '/dev/urandom' 디바이스 파일을 사용하여 난수를 발생시키는데요. 만약 디바이스 파일이 정상동작하지 않으면 pid와 현재 시간과 rand()함수를 가지고 난수를 발생시킵니다. 이렇게 생성된 난수 값의 character중 하위의 4bit만을 취한 Hex값으로 최종 Run ID를 만들게 됩니다.

Run ID를 만들어주는 함수는 getRandomHexChars() 이고 내부 동작은 아래와 같습니다.

1. 난수 발생을 위해 시스템의 /dev/urandom을 사용한다.
FILE *fp = fopen("/dev/urandom","r");
2. 이 파일을 REDIS_RUN_ID_SIZE(디폴트 값이 40으로 되어 있습니다.)만큼 읽는다.
if (fp == NULL || fread(p,len,1,fp) == 0) {
  ...
}
3. 읽은 값을 HEX값으로 변환한다. 이때 character의 하위 4비트만을 사용한다.
for (j = 0; j < len; j++)
        p[j] = charset[p[j] & 0x0F];

하지만 시스템의 /dev/urandom이 정상적으로 동작하지 않는 경우는 아래의 방법을 사용하게 됩니다.

1. getpid()함수를 통해 Redis의 pid값을 얻어온다.
pid_t pid = getpid();
2. gettimeofday()함수를 통해 현재 시간을 얻어온다.
gettimeofday(&tv,NULL);
3. microsecond, second, pid의 순으로 버퍼에 저장한다.
if (l >= sizeof(tv.tv_usec)) {
    memcpy(x,&tv.tv_usec,sizeof(tv.tv_usec));
    l -= sizeof(tv.tv_usec);
    x += sizeof(tv.tv_usec);
}
if (l >= sizeof(tv.tv_sec)) {
    memcpy(x,&tv.tv_sec,sizeof(tv.tv_sec));
    l -= sizeof(tv.tv_sec);
    x += sizeof(tv.tv_sec);
}
if (l >= sizeof(pid)) {
    memcpy(x,&pid,sizeof(pid));
    l -= sizeof(pid);
    x += sizeof(pid);
}
4. rand()함수를 사용하여 발생한 랜덤 값과 XOR을 한다.
for (j = 0; j < len; j++)
    p[j] ^= rand();
5. 읽은 값을 HEX값으로 변환한다.

전체 코드

void getRandomHexChars(char *p, unsigned int len) {
    // 디바이스 파일 열기
    FILE *fp = fopen("/dev/urandom","r");
    char *charset = "0123456789abcdef"; // hex 변환에 사용
    unsigned int j;
    / 디바이스 파일로부터 난수 가져오기
    if (fp == NULL || fread(p,len,1,fp) == 0) {
        // 디바이스 파일을 읽을 수 없는 경우
        /* If we can't read from /dev/urandom, do some reasonable effort
         * in order to create some entropy, since this function is used to
         * generate run_id and cluster instance IDs */
        char *x = p;
        unsigned int l = len;
        struct timeval tv;
        pid_t pid = getpid();
        /* Use time and PID to fill the initial array. */
        gettimeofday(&tv,NULL);
        if (l >= sizeof(tv.tv_usec)) {
            // microsecond의 값을 버퍼에 저장
            memcpy(x,&tv.tv_usec,sizeof(tv.tv_usec));
            l -= sizeof(tv.tv_usec);
            x += sizeof(tv.tv_usec);
        }
        if (l >= sizeof(tv.tv_sec)) {
            // second의 값을 버퍼에 저장
            memcpy(x,&tv.tv_sec,sizeof(tv.tv_sec));
            l -= sizeof(tv.tv_sec);
            x += sizeof(tv.tv_sec);
        }
        if (l >= sizeof(pid)) {
            // pid의 값을 버퍼에 저장
            memcpy(x,&pid,sizeof(pid));
            l -= sizeof(pid);
            x += sizeof(pid);
        }
        /* Finally xor it with rand() output, that was already seeded with
         * time() at startup. */
        for (j = 0; j < len; j++)
            p[j] ^= rand(); // 위에서 저장한 값을 rand()와 XOR
    }
    /* Turn it into hex digits taking just 4 bits out of 8 for every byte. */
    // 위에서 저장한 값을 character단위로 하여 하위 4bit만 가지고 hex로 저장
    for (j = 0; j < len; j++)
        p[j] = charset[p[j] & 0x0F];
    fclose(fp);
}

Building asynchronous views in SwiftUI 정리

Handling loading states within SwiftUI views self loading views View model 사용하기 Combine을 사용한 AnyPublisher Making SwiftUI views refreshable r...