English | 简体中文 | 繁體中文 | Русский язык | Français | Español | Português | Deutsch | 日本語 | 한국어 | Italiano | بالعربية
서버 시스템 개발 시, 데이터 대규모 동시 요청에 적응하기 위해 데이터를 비동기 저장해야 하며, 특히 분산 시스템을 개발할 때, 데이터베이스에 삽입 후 자동 ID를 가져오는 것을 기다릴 수 없습니다. 대신, 데이터베이스에 삽입하기 전에 전체적으로 유일한 ID를 생성해야 하며, 전체적으로 유일한 ID를 사용하여, 게임 서버에서 전체적으로 유일한 ID는 앞으로 서버 통합을 쉽게 할 수 있으며, 키 충돌이 발생하지 않습니다. 또한, 비즈니스 성장 시, 데이터베이스 분리 및 테이블화를 구현할 수 있습니다. 예를 들어, 특정 사용자의 아이템을 동일한 셰이프에 두고, 이 셰이프는 사용자 ID의 범위 값에 따라 결정될 수 있습니다. 예를 들어, 사용자 ID가1000이 작습니다100000의 사용자는 하나의 셰이프 내에 있습니다. 현재 일반적으로 사용되는 것은 다음과 같습니다:
1Java가 내장한 UUID.
UUID.randomUUID().toString()를 통해 서비스 프로그램 로컬에서 생성할 수 있으며, ID 생성은 데이터베이스 구현에 의존하지 않습니다.
점:
로컬에서 ID를 생성하여 원격 호출을 필요로 하지 않습니다.
전체적으로 유일하고 중복되지 않습니다.
수평 확장 능력이 매우 좋습니다.
단점:
ID는128 bits는 공간을 많이 차지하며, 문자열로 저장해야 하며, 인덱싱 효율이 매우 낮습니다.
생성된 ID에는 Timestamp이 포함되지 않으며, 추세적 증가를 보장할 수 없어 데이터베이스 분리 및 테이블화 시 의존성이 좋지 않습니다.
2Redis의 incr 메서드를 기반으로
Redis는 본래 단일 스레드로 작동하며, incr은 원자적인 증가 작업을 보장합니다. 또한 증가 단계를 설정할 수 있습니다.
점:
배포가 쉽고 사용하기도 간단합니다. 단지 Redis API를 호출하면 됩니다.
여러 서버가 하나의 Redis 서비스를 공유할 수 있어 공유 데이터의 개발 시간을 줄일 수 있습니다.
Redis는 클러스터 배포가 가능하며, 단일점 장애 문제를 해결할 수 있습니다.
단점:
시스템이 너무 크면, 많은 서비스가 동시에 Redis에 요청을 보내 performance bottleneck가 발생합니다.
3Flicker에서 제공하는 해결 방법
이 해결 방법은 데이터베이스 자동 증가 ID를 기반으로 하며, ID 생성을 위해 별도의 데이터베이스를 사용합니다. 자세한 내용은 인터넷에서 찾아보세요. 개인적으로 매우 복잡하다고 생각하며, 사용하지 않는 것을 추천합니다.
4Twitter Snowflake
Snowflake는 Twitter가 오픈소스로 제공하는 분산 ID 생성 알고리즘으로, 그 핵심 원리는 다음과 같습니다: long형의 ID를 생성하고, 그 중41bit을 밀리초로 사용하여,10bit을 기계 번호로 사용하여,12bit을 타임스탬프로 사용하여, 이 알고리즘은 단일 기기에서 이제 한 분당에 이론적으로 가장 많이 생성할 수 있는1000*(2^12)개, 즉 약400W의 ID는, 비즈니스 요구사항을 완전히 충족할 수 있습니다.
Snowflake 알고리즘의 원리에 따르면, 우리는 자신의 비즈니스 시나리오에 맞게 자신의 전체적인 고유한 ID를 생성할 수 있습니다. 왜냐하면 Java의 long 타입의 길이는64bits, 따라서 우리가 설계한 ID는 제어되어야 합니다64bits。
점: 고성능, 낮은 지연; 독립적인 애플리케이션; 시간순으로 정렬됩니다.
단점: 독립적인 개발과 배포가 필요합니다.
예를 들어 우리가 설계한 ID는 다음과 같은 정보를 포함하고 있습니다:
| 41 bits: 타임스탬프 | 3 bits: 지역 | 10 bits: 기계 번호 | 10 bits: 시퀀스 번호 |
고유한 ID를 생성하는 Java 코드:
/** * 사용자 정의 ID 생성기 * ID 생성 규칙: ID 길이는 64 bits * * | 41 bits: 타임스탬프(밀리초) | 3 bits: 지역(서버실) | 10 bits: 기계 번호 | 10 bits: 시퀀스 번호 | */ public class GameUUID{ // 기준 시간 private long twepoch = 1288834974657L; //Thu, 04 Nov 2010 01:42:54 GMT // 지역 표시 비트수 private final static long regionIdBits = 3L; // 기계 표시 비트수 private final static long workerIdBits = 10L; // 시퀀스 ID 비트수 private final static long sequenceBits = 10L; // 지역 표시 ID 최대값 private final static long maxRegionId = -1L ^ (-1L << regionIdBits); // 기계ID 최대값 private final static long maxWorkerId = -1L ^ (-1L << workerIdBits); // 시퀀스 ID 최대값 private final static long sequenceMask = -1L ^ (-1L << sequenceBits); // 기계ID 왼쪽으로 이동10비트 private final static long workerIdShift = sequenceBits; // 비즈니스 ID를 왼쪽으로 이동20비트 private final static long regionIdShift = sequenceBits + workerIdBits; // 시간 밀리초를 왼쪽으로 이동23비트 private final static long timestampLeftShift = sequenceBits + workerIdBits + regionIdBits; private static long lastTimestamp = -1L; private long sequence = 0L; private final long workerId; private final long regionId; public GameUUID(long workerId, long regionId) { // 범위를 벗어나면 예외를 투げ치기 if (workerId > maxWorkerId || workerId < 0) { throw new IllegalArgumentException("worker Id can't be greater than %d or less than 0"); } if (regionId > maxRegionId || regionId < 0) { throw new IllegalArgumentException("datacenter Id can't be greater than %d or less than 0"); } this.workerId = workerId; this.regionId = regionId; } public GameUUID(long workerId) { // 범위를 벗어나면 예외를 투げ치기 if (workerId > maxWorkerId || workerId < 0) { throw new IllegalArgumentException("worker Id can't be greater than %d or less than 0"); } this.workerId = workerId; this.regionId = 0; } public long generate() { return this.nextId(false, 0); } /** * 실제 코드를 생성하는 * * @param isPadding * @param busId * @return */ private synchronized long nextId(boolean isPadding, long busId) { long timestamp = timeGen(); long paddingnum = regionId; if (isPadding) {}} paddingnum = busId; } if (timestamp < lastTimestamp) { try { throw new Exception("Clock moved backwards. Refusing to generate id for " + (lastTimestamp - timestamp) + " milliseconds"); } catch (Exception e) { e.printStackTrace(); } } //이전 생성 시간과 현재 시간이 같다면, 같은 밀리초 내에 if (lastTimestamp == timestamp) { //sequence는 증가합니다. sequence는 오직10비트만 있기 때문에, sequenceMask와 AND 연산을 통해 높은 비트를 제거합니다. sequence = (sequence + 1) & sequenceMask; //overflow를 판단하는 것은, 밀리초 내에1024그 때1024그때, sequenceMask와 AND 연산을 통해 sequence는 0이 됩니다. if (sequence == 0) { //다음 밀리초까지 스피닝을 기다립니다. timestamp = tailNextMillis(lastTimestamp); } } else { // 이전 생성 시간과 다를 경우, sequence을 재설정합니다. 즉, 다음 밀리초부터 sequence 계산이 다시 0부터 시작합니다. // 끝자리의 랜덤성을 더 높이기 위해, 마지막 자리에 랜덤 수를 설정합니다. sequence = new SecureRandom().nextInt(10; } lastTimestamp = timestamp; return ((timestamp - twepoch) << timestampLeftShift) | (paddingnum << regionIdShift) | (workerId << workerIdShift) | sequence; } // 위전에 생겨난 시간이 이전 시간보다 작아지는 것을 방지하기 위해 (NTP 반전 등의 문제로 인해), 증가하는 추세를 유지합니다. private long tailNextMillis(final long lastTimestamp) { long timestamp = this.timeGen(); while (timestamp <= lastTimestamp) { timestamp = this.timeGen(); } return timestamp; } // 현재의 타임스탬프를 가져오기 protected long timeGen() { return System.currentTimeMillis(); } }
사용자 정의 방법을 사용할 때 유의해야 할 몇 가지 사항:
성장 추세를 유지하기 위해, 일부 서버의 시간이 일찍, 일부 서버의 시간이 늦은 것을 피하고, 모든 서버의 시간을 제어해야 하며, NTP 시간 서버가 서버의 시간을 되돌리는 것을 피해야 합니다. 다중 밀리초에서, 시리얼 번호는 항상 0으로 초기화되며, 시리얼 번호가 0인 ID가 많아지면, 생성된 ID가 모듈러 연산 후 불균형해질 수 있습니다. 따라서 시리얼 번호는 항상 0으로 초기화되지 않고, 0에서9의 무작위 수.
위에서 설명한 몇 가지 방법들은 우리의 필요에 따라 선택할 수 있습니다. 게임 서버 개발에서, 자신의 게임 유형에 따라 선택할 수 있습니다. 예를 들어, 모바일 게임은 간단한 redis 방식을 사용할 수 있으며, 간단하며 오류가 적습니다. 이러한 게임 서버의 단일 서버에서 새로운 ID를 생성하는 양은 많지 않기 때문에, 필요를 충족할 수 있습니다. 대형의 세계 게임 서버는 분산형으로 주로 이루어져 있기 때문에, snowflake 방식을 사용할 수 있습니다. 위의 snowflake 코드는 예제일 뿐, 자신의 필요에 따라 맞춤화해야 하며, 추가적인 개발량이 필요하며, 위에서 설명한 주의사항을 유의해야 합니다.
위에서 설명한 것들은 저가 여러분에게 소개한 Java 코드를 기반으로 한 게임 서버 전용 전용 유일한 ID 생성 방법의 요약입니다. 여러분이 어떤 질문이나 의문이 있으면, 저에게 댓글을 달아 주시기 바랍니다. 저는 즉시 여러분의 질문에 답변할 것입니다. 또한, 여러분이 양악教程 사이트에 대한 지원에 감사드립니다!
선언: 이 문서의 내용은 인터넷에서 수집되었으며, 저작권은 원저자에게 있으며, 인터넷 사용자가 자발적으로 기여하고 업로드한 내용입니다. 이 사이트는 소유권을 가지지 않으며, 인공 편집 처리를 하지 않았으며, 관련 법적 책임을 지지 않습니다. 저작권 침해 내용이 발견되면, notice#w로 이메일을 보내 주시기 바랍니다.3codebox.com(메일을 보내면, #을 @으로 변경하여 신고하시고, 관련 증거를 제공하시기 바랍니다. 확인되면, 이 사이트는 즉시 저작권 침해 내용을 삭제할 것입니다.)