분산 시퀀스 번호 생성?


103

나는 일반적으로 과거에 데이터베이스 시퀀스를 사용하여 시퀀스 번호 생성 을 구현했습니다 .

예 : Postgres SERIAL 유형 사용 http://www.neilconway.org/docs/sequences/

데이터베이스가없는 대규모 분산 시스템의 시퀀스 번호를 생성하는 방법이 궁금합니다. 누구나 여러 클라이언트 에 대해 스레드 안전 방식으로 시퀀스 번호 생성을 달성하기위한 모범 사례에 대한 경험이나 제안이 있습니까?


이 질문은 오래되었지만 pls는 내 새 답변을 참조하십시오. stackoverflow.com/questions/2671858/…
Jesper M

nextval.org를 어떻게 사용합니까? 웹 사이트가 조금 이상해서 무슨 일인지 모르겠습니다. 유닉스 명령입니까? 아니면 일부 클라우드 서비스?
diegosasw

답변:


116

좋아요, 이것은 제가 지금 처음 보는 아주 오래된 질문입니다.

특정 기준 (일반적으로 생성 시간)에 따라 (선택적으로) 느슨하게 정렬 할 수있는 시퀀스 번호고유 ID 를 구별해야합니다 . 실제 시퀀스 번호는 다른 모든 작업자가 수행 한 작업에 대한 지식을 의미하므로 공유 상태가 필요합니다. 분산 된 대규모 방식으로이 작업을 수행하는 쉬운 방법은 없습니다. 네트워크 브로드 캐스트, 각 작업자에 대한 창 범위, 고유 작업자 ID에 대한 분산 해시 테이블 등을 살펴볼 수 있지만 많은 작업이 필요합니다.

고유 ID는 또 다른 문제입니다. 분산 된 방식으로 고유 ID를 생성하는 몇 가지 좋은 방법이 있습니다.

a) Twitter의 Snowflake ID 네트워크 서비스를 사용할 수 있습니다 . Snowflake는 다음과 같습니다.

  • 네트워크 서비스, 즉 고유 ID를 얻기 위해 네트워크 전화를 겁니다.
  • 생성 시간별로 정렬 된 64 비트 고유 ID를 생성합니다.
  • 서비스는 확장 성이 뛰어나고 (잠재적으로) 가용성이 높습니다. 각 인스턴스는 초당 수천 개의 ID를 생성 할 수 있으며 LAN / WAN에서 여러 인스턴스를 실행할 수 있습니다.
  • Scala로 작성되었으며 JVM에서 실행됩니다.

b) UUID 및 Snowflake의 ID가 만들어 지는 방식에서 파생 된 접근 방식을 사용하여 클라이언트 자체에서 고유 ID를 생성 할 수 있습니다 . 여러 옵션이 있지만 다음과 같은 내용이 있습니다.

  • 최상위 40 비트 정도 : 타임 스탬프; ID의 생성 시간. (우리는 생성 시간별로 ID를 정렬 할 수 있도록 타임 스탬프에 가장 중요한 비트를 사용하고 있습니다.)

  • 다음 14 개 정도의 비트 : 각 생성기가 생성 된 각 새 ID에 대해 하나씩 증가하는 생성기 별 카운터 . 이렇게하면 동시에 생성 된 ID (동일한 타임 스탬프)가 겹치지 않습니다.

  • 마지막 10 개 정도의 비트 : 각 생성기에 대한 고유 한 값입니다. 이를 사용하면 모든 생성기가이 값으로 인해 겹치지 않는 ID를 생성하므로 생성기간에 동기화를 수행 할 필요가 없습니다 (매우 어렵습니다).

c) 타임 스탬프와 임의의 값만 사용하여 클라이언트에서 ID를 생성 할 수 있습니다 . 이렇게하면 모든 생성기를 알 필요가 없으며 각 생성기에 고유 한 값을 할당 할 수 있습니다. 반대로 이러한 ID는 전역 적으로 고유하다고 보장 할 수 없으며 고유 할 가능성매우 높습니다 . (충돌하려면 하나 이상의 생성기가 정확히 동시에 동일한 임의의 값을 생성해야합니다.)

  • 최상위 32 비트 : 타임 스탬프, ID 생성 시간.
  • 최하위 32 비트 : 32 비트 임의성, 각 ID에 대해 새로 생성됩니다.

d) 쉬운 방법 은 UUID / GUID를 사용하는 것 입니다.


Cassandra는 카운터 ( cassandra.apache.org/doc/cql3/CQL.html#counters )를 지원 하지만 몇 가지 제한 사항이 있습니다.
Piyush Kansal 15. 01.

시퀀스 번호는 비트 맵 인덱스의 위치를 ​​설정하기 쉽지만 고유 ID가 너무 길 때 (64 비트 또는 128 비트), 어떻게 고유 ID를 비트 맵 인덱스 위치에 매핑 할 수 있습니까? 감사.
brucenan

2
정말 좋아 옵션 #B는 ..... 그것은 높은 규모를 허용하고 동시성 문제의 원인 많은 수 없었다
puneet

2
twitter/snowflake더 이상 유지되지 않습니다
Navin

옵션 B의 Apache2 라이선스 구현을 원하는 경우 bitbucket.org/pythagorasio/common-libraries/src/master/…를 확인하세요 . maven io.pythagoras.common : distributed-sequence-id-generator : 1.0에서도 가져올 수 있습니다. .0
Wpigott

16

이제 더 많은 옵션이 있습니다.

이 질문은 "오래된"질문이지만 여기에 왔으므로 지금까지 알고있는 옵션을 그대로 두는 것이 유용 할 것 같습니다.

  • Hazelcast를 사용해 볼 수 있습니다. 1.9 릴리스에는 java.util.concurrent.AtomicLong의 분산 구현이 포함되어 있습니다.
  • Zookeeper를 사용할 수도 있습니다 . 시퀀스 노드를 만드는 방법을 제공합니다 (노드의 버전 번호 사용을 선호하지만 znode 이름에 추가됨). 그래도주의해야합니다. 시퀀스에서 누락 된 번호를 원하지 않으면 원하는 것이 아닐 수 있습니다.

건배


3
Zookeeper는 제가 함께했던 옵션이었습니다. 제가 시작한 메일 링리스트에 이것에 대한 좋은 설명과 글이 있습니다. mail-archive.com/zookeeper-user@hadoop.apache.org/msg01967.html
Jon

Jon, 그 스레드를 지적 해주셔서 감사합니다. 이것이 제가 생각했던 바로 그 유형의 솔루션입니다. BTW, MAX_INT 제한을 극복하기 위해 코드를 만들었습니까?
Paolo

15

각 노드에 고유 한 ID (어쨌든 가지고있을 수 있음)를 갖고이를 시퀀스 번호 앞에 추가 할 수 있습니다.

예를 들어 노드 1은 시퀀스 001-00001 001-00002 001-00003 등을 생성하고 노드 5는 005-00001 005-00002를 생성합니다.

독특한 :-)

또는 일종의 중앙 집중식 시스템을 원한다면 시퀀스 서버가 블록 단위로 제공되도록 고려할 수 있습니다. 이렇게하면 오버 헤드가 크게 줄어 듭니다. 예를 들어, 할당해야하는 각 ID에 대해 중앙 서버에서 새 ID를 요청하는 대신 중앙 서버에서 10,000 개의 블록으로 ID를 요청한 다음 다 떨어졌을 때 다른 네트워크 요청 만 수행하면됩니다.


1
나는 배치 ID 생성에 대한 귀하의 요점을 좋아하지만 실시간 계산 가능성을 제한합니다.
ishan

비슷한 메커니즘을 구현했습니다. 여기에서 클라이언트가 시퀀스 블록을 캐싱하는 것 외에도 시퀀스 블록을 캐시하는 여러 서버 호스트를 추가했습니다. (단일) 마스터 생성기는 일부 고 가용성 스토리지 또는 단일 마스터 호스트에서 유지 관리되며 서버 호스트 집합에서만 액세스 할 수 있습니다. 서버 캐싱은 단일 마스터가 잠시 중단 되더라도 가동 시간을 늘리는데도 도움이됩니다.
Janakiram

11

Redisson으로 할 수 있습니다 . 분산되고 확장 가능한 버전의 AtomicLong. 예를 들면 다음과 같습니다.

Config config = new Config();
config.addAddress("some.server.com:8291");

Redisson redisson = Redisson.create(config);
RAtomicLong atomicLong = redisson.getAtomicLong("anyAtomicLong");
atomicLong.incrementAndGet();

8

단순히 고유하지 않고 전 세계적으로 순차적이어야한다면 이러한 숫자를 분배하기위한 단일하고 간단한 서비스를 만드는 것을 고려할 것입니다.

분산 시스템은 상호 작용하는 많은 작은 서비스에 의존하며, 이러한 간단한 작업을 위해 다른 복잡한 분산 솔루션이 정말로 필요합니까 아니면 실제로 이익을 얻을 수 있습니까?


3
... 그 서비스를 실행하는 서버가 다운되면 어떻게됩니까?
Navin

누군가에게 다른 것을 시작하라고 알려주는 경고가 있습니까? 때때로 그것은 괜찮을 것입니다. 대답은 "관점을 유지하라"는 것입니다. 완벽한 분산 솔루션에는 고유 한 단점이 있으며 때로는 더 간단한 것이 좋습니다.
nic ferrier

6

몇 가지 전략이 있습니다. 그러나 내가 아는 것은 실제로 배포되고 실제 시퀀스를 제공 할 수 없습니다.

  1. 중앙 번호 생성기가 있습니다. 큰 데이터베이스 일 필요는 없습니다. memcached빠른 원자 카운터가 있으며 대부분의 경우 전체 클러스터에 대해 충분히 빠릅니다.
  2. 각 노드에 대해 정수 범위를 분리하십시오 (예 : Steven Schlanskter의 답변 )
  3. 난수 또는 UUID 사용
  4. 노드의 ID와 함께 일부 데이터를 사용하고 모두 해시 (또는 hmac )

개인적으로 나는 UUID에 의지하거나 대부분 인접한 공간을 원한다면 memcached에 의지합니다.


5

(스레드 안전) UUID 생성기를 사용하지 않는 이유는 무엇입니까?

나는 이것에 대해 확장해야 할 것입니다.

UUID는 전역 적으로 고유함이 보장됩니다 (유일성이 매우 높은 난수를 기반으로 한 UUID를 피하는 경우).

사용하는 UUID 생성기 수에 관계없이 각 UUID의 글로벌 고유성에 따라 "분산"요구 사항이 충족됩니다.

"스레드 안전"요구 사항은 "스레드 안전"UUID 생성기를 선택하여 충족 할 수 있습니다.

"시퀀스 번호"요구 사항은 각 UUID의 보장 된 전역 고유성에 의해 충족되는 것으로 간주됩니다.

많은 데이터베이스 시퀀스 번호 구현 (예 : Oracle)은 단조 증가하거나 (심지어) 시퀀스 번호 증가 ( "연결"기준)를 보장하지 않습니다. 연속 된 일련 번호 배치가 연결별로 "캐시 된"블록에 할당되기 때문입니다. 이는 글로벌 고유성을 보장 하고 적절한 속도를 유지한다. 그러나 실제로 할당 된 시퀀스 번호 (시간이 지남에 따라)는 여러 연결에 의해 할당 될 때 뒤죽박죽 될 수 있습니다!


1
UUID가 작동하는 동안 문제는 궁극적으로 생성 된 키를 인덱싱해야 할 경우 저장 방법에주의해야한다는 것입니다. 또한 일반적으로 단조롭게 증가 된 시퀀스보다 훨씬 더 많은 공간을 차지합니다. MySQL에 저장하는 방법에 대한 논의는 percona.com/blog/2014/12/19/store-uuid-optimized-way 를 참조하십시오 .
Pavel

2

분산 ID 생성은 Redis 및 Lua로 보관할 수 있습니다. Github 에서 사용할 수있는 구현 입니다. 분산되고 k 정렬 가능한 고유 ID를 생성합니다.


2

나는 이것이 오래된 질문이라는 것을 알고 있지만 우리도 같은 요구에 직면했고 우리의 요구를 충족시키는 해결책을 찾을 수 없었습니다. 우리의 요구 사항은 고유 한 ID 시퀀스 (0,1,2,3 ... n)를 가져 오는 것이었기 때문에 눈송이가 도움이되지 않았습니다. Redis를 사용하여 ID를 생성하기 위해 자체 시스템을 만들었습니다. Redis는 단일 스레드이므로 목록 / 대기열 메커니즘은 항상 한 번에 1 개의 팝을 제공합니다.

우리가하는 일은 ID 버퍼를 생성하는 것입니다. 처음에 큐에는 요청시 발송할 준비가 된 0 ~ 20 개의 ID가 있습니다. 여러 클라이언트가 ID를 요청할 수 있으며 redis는 한 번에 1 개의 ID를 팝합니다. 왼쪽에서 팝할 때마다 BUFFER + currentId를 오른쪽에 삽입하여 버퍼 목록을 계속 유지합니다. 여기에서 구현


0

반 고유 한 비 순차적 64 비트 긴 숫자를 생성 할 수있는 간단한 서비스를 작성했습니다. 중복성과 확장 성을 위해 여러 시스템에 배포 할 수 있습니다. 메시징을 위해 ZeroMQ를 사용합니다. 작동 방식에 대한 자세한 내용은 github 페이지 : zUID를 참조하십시오.


0

데이터베이스를 사용하면 단일 코어로 초당 1.000+ 증가에 도달 할 수 있습니다. 꽤 쉽습니다. 자체 데이터베이스를 백엔드로 사용하여 해당 숫자를 생성 할 수 있습니다 (DDD 용어로 자체 집계 여야하므로).

비슷한 문제가있는 것 같습니다. 나는 여러 파티션을 가지고 있었고 각각에 대한 오프셋 카운터를 얻고 싶었습니다. 다음과 같이 구현했습니다.

CREATE DATABASE example;
USE example;
CREATE TABLE offsets (partition INTEGER, offset LONG, PRIMARY KEY (partition));
INSERT offsets VALUES (1,0);

그런 다음 다음 문을 실행했습니다.

SELECT @offset := offset from offsets WHERE partition=1 FOR UPDATE;
UPDATE offsets set offset=@offset+1 WHERE partition=1;

응용 프로그램에서 허용하는 경우 한 번에 블록을 할당 할 수 있습니다 (내 경우).

SELECT @offset := offset from offsets WHERE partition=1 FOR UPDATE;
UPDATE offsets set offset=@offset+100 WHERE partition=1;

추가 처리량이 필요하고 오프셋을 미리 할당 할 수없는 경우 실시간 처리를 위해 Flink를 사용하여 자체 서비스를 구현할 수 있습니다. 파티션 당 약 10 만 증가를 얻을 수있었습니다.

도움이 되었기를 바랍니다.


0

문제는 다음과 유사합니다. iscsi 세계에서 각 LUN / 볼륨은 클라이언트 측에서 실행되는 이니시에이터가 고유하게 식별 할 수 있어야합니다. iscsi 표준에 따르면 처음 몇 비트는 스토리지 공급자 / 제조업체 정보를 나타내야하고 나머지는 단조롭게 증가합니다.

마찬가지로 노드의 분산 시스템에서 초기 비트를 사용하여 nodeID를 나타낼 수 있으며 나머지는 단조롭게 증가 할 수 있습니다.


1
좀 더 세부 사항을 추가하십시오
VED 프라 카쉬에게

0

괜찮은 해결책 중 하나는 오랜 시간 기반 세대를 사용하는 것입니다. 분산 데이터베이스의 백업으로 수행 할 수 있습니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.