English | 简体中文 | 繁體中文 | Русский язык | Français | Español | Português | Deutsch | 日本語 | 한국어 | Italiano | بالعربية

Обзор методов генерации уникального идентификатора для игрового сервера на Java

При разработке серверных систем для адаптации к высокому уровню параллельных запросов к данным, мы часто должны выполнять асинхронное хранение данных, особенно при разработке распределенных систем. В этот момент мы не можем ждать, чтобы получить автоматический ID после вставки в базу данных, а должны сгенерировать глобально уникальный ID до вставки в базу данных. Использование глобально уникального ID может быть удобно для будущей слияния серверов, чтобы избежать конфликта ключей. Также в будущем при росте бизнеса можно реализовать разделение базы данных и таблиц, например, предметы одного пользователя нужно поместить в одну шину, а эта шина может быть определена по диапазону значений идентификатора пользователя, например, пользователи с идентификатором больше 1000 и меньше 100000 находятся в одной шине. В настоящее время常用的 следующие几种:

1, UUID, встроенный в Java.

UUID.randomUUID().toString(), можно генерировать локально через сервис программы, генерация ID не зависит от реализации базы данных.

Преимущества:

Генерация ID локально, не требует удаленного вызова.

Глобально уникальный и не повторяющийся.

Очень хорошая способность к горизонтальному масштабированию.

Недостатки:

ID имеет 128 bits, занимая много места, его необходимо хранить в виде строки, индексация очень низкая.

Генерированные ID не содержат Timestamp, что не гарантирует тенденцию к увеличению, что не очень хорошо для разделения базы данных и таблиц.

2, Метод incr на основе Redis

Redis сам по себе operates в одном потоке, а incr гарантирует атомарное увеличение. Кроме того, поддерживается установка шага увеличения.

Преимущества:

Удобство部署а, простота использования, достаточно вызвать api redis.

Множество серверов могут делиться одним redis-сервисом, что сокращает время разработкиshared данных.

Redis можно部署 в кластере, что решает проблему точки отказа.

Недостатки:

Если система слишком большая, множество сервисов одновременно запрашивают redis, что может привести к узкому месту в производительности.

3, Решение от Flicker

Этот метод основан на автоматическом увеличении ID базы данных, он использует отдельную базу данных для генерации ID. Подробности можно найти в Интернете, на мой взгляд, это довольно сложно, не рекомендуется использовать.

4, Twitter Snowflake

Snowflake - это распределенный алгоритм генерации ID, открытый Twitter, его основная идея заключается в том, чтобы создать ID типа long, использовать 41bit для миллисекунд, 10bit для идентификатора машины и 12bit для последовательного номера в миллисекунде. Теоретически, этот алгоритм может генерировать до 1000*(2^12) ID в секунду на одном компьютере, что составляет около 4 миллионов ID, что полностью удовлетворяет потребностям бизнеса.

Согласно идее алгоритма snowflake, мы можем создавать свои глобально уникальные ID в зависимости от нашей бизнес-сценарии. Поскольку длина типа long в Java составляет 64bits, мы должны контролировать设计的ID, чтобы он не превышал 64bits.

Advantages: high performance, low latency; independent application; ordered by time.

Disadvantages: requires independent development and deployment.

For example, the ID we designed includes the following information:

| 41 bits: Timestamp | 3 bits: Region | 10 bits: Machine number | 10 bits: Sequence number |

Java code to generate a unique ID:

/**
* Custom ID generator
* ID generation rule: ID is 64 bits long
*
* | 41 bits: Timestamp (milliseconds) | 3 bits: Region (data center) | 10 bits: Machine number | 10 bits: Sequence number |
*/
public class GameUUID{
// Base time
private long twepoch = 1288834974657L; //Thu, 04 Nov 2010 01:42:54 GMT
// Number of bits for region flag
private final static long regionIdBits = 3L;
// Number of bits for machine identifier
private final static long workerIdBits = 10L;
// Number of bits for sequence ID
private final static long sequenceBits = 10L;
// Maximum value of region flag ID
private final static long maxRegionId = -1L ^ (-1L << regionIdBits);
// Maximum value of machine ID
private final static long maxWorkerId = -1L ^ (-1L << workerIdBits);
// Maximum value of sequence ID
private final static long sequenceMask = -1L ^ (-1L << sequenceBits);
// Machine ID shifted left by 10 bits
private final static long workerIdShift = sequenceBits;
// Business ID shifted left by 20 bits
private final static long regionIdShift = sequenceBits + workerIdBits;
// Time milliseconds shifted left by 23 bits
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("Часы переместились назад. Отказываюсь генерировать id для " + (lastTimestamp - timestamp) + " миллисекundy");
}
e.printStackTrace();
}
}
// Если время генерации предыдущего и текущего времени одинаковы, в одном миллисекundy
if (lastTimestamp == timestamp) {
// Автоматическое увеличение sequence, так как sequence имеет только 10 bit, поэтому после сравнения с sequenceMask удаляются высокие биты
sequence = (sequence + 1) & sequenceMask;
// Проверка перелива, то есть превышение 1024 в каждом миллисекundy, когда это 1024, и sequenceMask, sequence становится равным 0
if (sequence == 0) {
// Вращательное ожидание до следующей миллисекundy
timestamp = tailNextMillis(lastTimestamp);
}
}
// Если время генерации не отличается от предыдущего, сброс sequence, то есть с начала следующей миллисекundy, счет 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, что приводит к тому, что большое количество ID с номером序列ции 0, что приводит к тому, что ID, полученные после модуляции, не равномерны. Поэтому номер序列ции не всегда возвращается к 0, а возвращается случайное число от 0 до 9.

Указанные выше методы можно выбирать в зависимости от наших потребностей. В разработке сервера игры, в зависимости от типа вашей игры, например, для мобильных игр можно использовать простой способ redis, который прост и не ошибочен. Поскольку количество новых идентификаторов, создаваемых одним сервером, не слишком велико, это完全可以 удовлетворить ваши потребности. Для крупных серверов мира, которые themselves основаны на распределении, можно использовать способ snowflake. Указанный код snowflake является примером, который нужно адаптировать в зависимости от ваших потребностей, поэтому есть дополнительный объем разработки, и нужно учитывать вышеупомянутые注意事项.

Указанные выше методы реализации глобально уникального ID сервера игры на основе Java, предложенные редактором, надеются помочь вам. Если у вас есть какие-либо вопросы, пожалуйста, оставьте комментарий, редактор ответит вам в кратчайшие сроки. В этом разделе также выражаем благодарность всем пользователям за поддержку сайта呐喊 уроков!

Заявление: содержимое этой статьи взято из Интернета, авторские права принадлежат соответствующему автору. Контент предоставлен пользователями Интернета, самостоятельно загружен, сайт не имеет права собственности, не был отредактирован вручную и не несет ответственности за связанные с этим юридические вопросы. Если вы обнаружите спорное содержимое, пожалуйста, отправьте письмо по адресу: notice#oldtoolbag.com (во время отправки письма замените # на @),并提供相关证据. Если будет установлено, что содержимое нарушает авторские права, сайт немедленно удалят.

Рекомендуется вам