English | 简体中文 | 繁體中文 | Русский язык | Français | Español | Português | Deutsch | 日本語 | 한국어 | Italiano | بالعربية
In server system development, to adapt to high concurrency data requests, we often need to perform asynchronous storage of data, especially when working on distributed systems. At this time, we cannot wait for the database insertion to return and automatically obtain the ID, but need to generate a global unique ID before inserting the database. Using a global unique ID, in game servers, a globally unique ID can be used for future server merging, which is convenient and will not result in key conflicts. It can also be used to implement sharding and table splitting in the case of business growth, such as when a user's items need to be placed in the same shard, and this shard may be determined by the range value of the user ID, such as user ID greater than1000 is less than100000 users are within a single shard. Currently, the commonly used ones are as follows:
1Java's built-in UUID.
UUID.randomUUID().toString(), which can be generated locally by the service program, so the ID generation does not depend on the implementation of the database.
Advantages:
Locally generated IDs do not require remote calls.
Global uniqueness without repetition.
Excellent horizontal scalability capabilities.
Disadvantages:
The ID has128 bits, which occupies a large amount of space and needs to be stored as a string type, resulting in extremely low index efficiency.
The generated IDs do not contain Timestamps, so it cannot guarantee trended increment, and it is not good to rely on it when the database is sharded and table split.
2Based on the Redis incr method
Redis itself operates on a single thread, and the incr operation ensures an atomic increment. It also supports setting the increment step.
Advantages:
Deployment is convenient and simple, only requiring the invocation of a Redis API.
Multiple servers can share a single Redis service, reducing the development time for shared data.
Redis can be deployed in clusters to solve the problem of single point of failure.
Disadvantages:
If the system is too large, with many services simultaneously requesting from Redis, it can cause a performance bottleneck.
3A solution from Flicker
This solution is based on the auto-incrementing ID of the database, which uses a separate database dedicated to generating IDs. Detailed information can be found online, and personally, I find it quite麻烦 to use, so I do not recommend using it.
4, Twitter Snowflake
Snowflake is a distributed ID generation algorithm open-sourced by Twitter, with the core idea of: generating a long-type ID, using some41bit as the millisecond number,10bit as the machine number,12bit as the sequence number within milliseconds. This algorithm can theoretically generate up to1000*(2^12) bits, which is about400W ID, which can fully meet the needs of the business.
According to the idea of the snowflake algorithm, we can generate our own global unique ID according to our business scenario. Because the length of the long type in Java is64bits, so the ID we designed needs to be controlled within64bits.
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: Area | 10 bits: Machine number | 10 bits: Sequence number |
Java code to generate a unique ID:
/** * Custom ID generator * ID generation rule: The ID is up to 64 bits * * | 41 bits: Timestamp (milliseconds) | 3 bits: Area (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 the area identifier private final static long regionIdBits = 3L; // Number of bits for the machine identifier private final static long workerIdBits = 10L; // Number of bits for the sequence ID private final static long sequenceBits = 10L; // Maximum value of the area ID private final static long maxRegionId = -1L ^ (-1L << regionIdBits); // Maximum value of the machine ID private final static long maxWorkerId = -1L ^ (-1L << workerIdBits); // Maximum value of the sequence ID private final static long sequenceMask = -1L ^ (-1L << sequenceBits); // Machine ID shifted to the left10Bit private final static long workerIdShift = sequenceBits; // Business ID left shift20Bit private final static long regionIdShift = sequenceBits + workerIdBits; // Time milliseconds left shift23Bit 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 out of range, throw an exception 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 out of range, throw an exception 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); /** * The actual code generation * * @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 the last generated time is the same as the current time, within the same millisecond if (lastTimestamp == timestamp) { //sequence increments because sequence only10bit, so bitwise AND with sequenceMask to remove the high bits sequence = (sequence + 1) & sequenceMask; //Determine whether there is an overflow, that is, within each millisecond, it exceeds1024, when it is1024When, bitwise AND with sequenceMask, sequence equals 0 if (sequence == 0) { //Spin and wait for the next millisecond timestamp = tailNextMillis(lastTimestamp); } else { // If the generated time is different from the last time, reset the sequence, which means starting from the next millisecond, the sequence count accumulates from 0 again, // To ensure a greater degree of randomness in the tail number, set the last digit to a random number sequence = new SecureRandom().nextInt(10; lastTimestamp = timestamp; return ((timestamp - twepoch) << timestampLeftShift) | (paddingnum << regionIdShift) | (workerId << workerIdShift) | sequence; // To prevent the generated time from being smaller than the previous time (due to NTP rollback and other issues), maintain the trend of increment. private long tailNextMillis(final long lastTimestamp) { long timestamp = this.timeGen(); while (timestamp <= lastTimestamp) { timestamp = this.timeGen(); return timestamp; // Get the current timestamp protected long timeGen() { return System.currentTimeMillis();
The following points should be noted when using this custom method:
To maintain the trend of growth, it is necessary to avoid the time of some servers being early and the time of some servers being late. It is necessary to control the time of all servers and avoid the time rollback of the NTP time server to the server time; when crossing milliseconds, the serial number is always reset to 0, which will make the serial number 0 more, leading to the unbalanced distribution of the generated ID after modulo operation. Therefore, the serial number is not reset to 0 every time, but to a 0 to9random number.
The methods mentioned above can be chosen according to our needs. In the development of game servers, choose according to the type of your game, for example, mobile games can use a simple redis method, which is simple and not easy to make mistakes. Since the amount of new ID generation with high concurrency in this type of game server is not too large, it can fully meet the needs. For large-scale world game servers, they are mainly distributed, so you can use the snowflake method. The snowflake code mentioned above is just an example, and it needs to be customized according to your own needs, so there is additional development work, and you should also pay attention to the precautions mentioned above.
The above-mentioned methods are a summary of the methods for generating a globally unique ID for game servers based on Java code introduced by the editor. I hope it will be helpful to everyone. If you have any questions, please leave a message, and the editor will reply to everyone in a timely manner. Here, I also want to express my heartfelt thanks to everyone for their support of the Yell Tutorial website!
Declaration: The content of this article is from the Internet, the copyright belongs to the original author. The content is contributed and uploaded by Internet users spontaneously, and this website does not own the copyright, has not been edited by humans, and does not bear relevant legal liabilities. If you find any copyright-infringing content, please send an email to: notice#oldtoolbag.com (Please replace # with @ when sending an email to report violations, and provide relevant evidence. Once verified, this site will immediately delete the infringing content.)