유일 ID 생성기 설계 및 snowflake 구현

요구사항

  • 대규모, 분산 시스템에서 유일 ID를 생성한 시스템을 설계해야 한다. 요구사항은 아래와 같다.
  • 유일한 값을 생성한다.
  • 정렬할 수 있다. ID가 1씩 증가하는 것은 아니지만, 시간이 지날수록 숫자의 크기는 커야 한다.
  • 초당 10,000개(1밀당 10개)의 ID를 생성할 수 있다.
  • 64비트 이하의 용량을 차지한다.

후보

다중 마스터 복제

  • DB에서 ID를 생성하여 제공한다. auto_increment를 사용한다.
  • DB의 갯수를 확장하여, 1이 아닌 N씩 아이디를 증가시킬 수 있다.
  • 다만 문제는
    • DB 서버를 확장하기 어렵고
    • ID 전체를 정렬 했을 때 시간의 순서를 보장하지 않는다.

UUID

  • 128비트이다.
  • 시간 순으로 정렬할 수 없다.
  • 숫자가 아닌 값이 포함된다.

티켓서버

  • 별도의 ID 생성 서버를 둔다.
  • 만들기 쉽다. 다루기 쉽다.
  • 다만 SPOF(Single point of failure) 문제가 발생 한다.

트위터 스노플레이크(twitter snowflake id generator)

  • 64비트의 숫자로 구성된 ID 생성 로직
  • ID 생성을 위한 별도의 서버나 네트워크를 필요로 하지 않는다.

구성

  • 1비트: sign
    • 현재 쓰임새가 없으나 나중을 위해 유보한 값
    • 기본값은 0이며 바이너리에서 Long으로 파싱할 경우 언제나 정수로 표현된다.
  • 41비트: 타임스탬프:
    • 타임스탬프를 사용하며 최소단위는 밀리세컨이다.
    • 시작점은 1970-01-01이 아닌 별도의 시간으로 설정한다.
  • 5비트: 데이터 센터 ID: 2^5개로 32개의 데이터 센터를 지원한다.
  • 5비트: 서버 ID: 하나의 데이터 센터에서 32개의 서버를 사용할 수 있다.
  • 12비트: 일련번호: 각 서버에서는 ID를 생성할 때마다 이 번호를 1씩 증가시킨다. 1밀리초가 경과할 때마다 0으로 초기화 된다.

기타

  • 시계 동기화 clock synchronization : 서버는 같은 시계를 사용해야 한다. NTP(Network Time Protocol)은 보편적인 해결 방식이다.
  • 필요에 따라 각 비트의 갯수를 조절할 수 있다. 동시성이 적을 수록 타임스탬프를 늘리는 것이 유리하다.
  • ID 생성기는 높은 가용성을 요구 받는다.

snowflake의 구현

  • snowflake를 직접 구현해봤다.
  • 병렬처리 가능하도록 synchronized를 사용했다. 가능하면 우회하거나 그 범위를 축소해보려 하였지만 좋은 아이디가 나오지 않았다.
  • 4096개를 같은 시간 동안 소모할 경우 while을 사용하여 다음 시간까지 대기하였다.
  • 아래는 비트연산자 «, 등을 사용하였다. int v = 10 << 1;의 형태로 사용 가능하다.
public class SnowFlake {
    private final long worker;
    private final long defaultTimestamp;
    private volatile int sequence = 0;
    private volatile long latestTimestamp = 0;

    public SnowFlake(int node, int app, long defaultTimestamp) {
        this.worker = ((long) node << 5) + app;
        this.defaultTimestamp = defaultTimestamp;
    }

    public synchronized long create() {
        long currentTimestamp = Instant.now().toEpochMilli();
        if(currentTimestamp != latestTimestamp){
            sequence = 0;
        } else if(sequence > 4095 && currentTimestamp == latestTimestamp){
            while((currentTimestamp = Instant.now().toEpochMilli()) == latestTimestamp);
            sequence = 0;
        }
        latestTimestamp = currentTimestamp;

        return (latestTimestamp - defaultTimestamp) << 22
                | worker << 12
                | sequence++;
    }
}
  • 실제 작성한 코드 및 몇가지 테스트 내용은 다음 링크 참고 바랍니다! https://github.com/infoqoch/system-desgin/tree/master/unique-id
  • “가상 면접 사례로 배우는 대규모 시스템 설계 기초”를 재밌게 읽고 있습니다! 격주로 책의 내용을 정리하고 업로드할 예정입니다.