All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.cedarsoftware.util.UniqueIdGenerator Maven / Gradle / Ivy

There is a newer version: 2.18.0
Show newest version
package com.cedarsoftware.util;

import java.nio.charset.StandardCharsets;
import java.security.SecureRandom;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

import static java.lang.Integer.parseInt;
import static java.lang.Math.abs;
import static java.lang.System.currentTimeMillis;

/**
 * Generate a unique ID that fits within a long value.  The ID will be unique for the given JVM, and it makes a
 * solid attempt to ensure uniqueness in a clustered environment.  An environment variable JAVA_UTIL_CLUSTERID
 * can be set to a value 0-99 to mark this JVM uniquely in the cluster.  If this environment variable is not set,
 * then hostname, cluster id, and finally a SecureRandom value from 0-99 is chosen for the machine's id within cluster.
 * 

* There is an API [getUniqueId()] to get a unique ID that will work through the year 5138. This API will generate * unique IDs at a rate of up to 1 million per second. There is another API [getUniqueId19()] that will work through * the year 2286, however this API will generate unique IDs at a rate up to 10 million per second. The trade-off is * the faster API will generate positive IDs only good for about 286 years [after 2000].
*
* The IDs are guaranteed to be strictly increasing. There is an API you can call (getDate(unique)) that will return * the date and time (to the millisecond) that the ID was created. * * @author John DeRegnaucourt ([email protected]) * @author Roger Judd (@HonorKnight on GitHub) for adding code to ensure increasing order. *
* Copyright (c) Cedar Software LLC *

* Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at *

* License *

* Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ @SuppressWarnings("unchecked") public class UniqueIdGenerator { public static final String JAVA_UTIL_CLUSTERID = "JAVA_UTIL_CLUSTERID"; private UniqueIdGenerator() { } private static final Lock lock = new ReentrantLock(); private static final Lock lock19 = new ReentrantLock(); private static int count = 0; private static int count2 = 0; private static long previousTimeMilliseconds = 0; private static long previousTimeMilliseconds2 = 0; private static final int serverId; private static final Map lastIds = new LinkedHashMap() { protected boolean removeEldestEntry(Map.Entry eldest) { return size() > 1000; } }; private static final Map lastIdsFull = new LinkedHashMap() { protected boolean removeEldestEntry(Map.Entry eldest) { return size() > 10_000; } }; static { int id = getServerId(JAVA_UTIL_CLUSTERID); String setVia = "environment variable: " + JAVA_UTIL_CLUSTERID; if (id == -1) { String envName = SystemUtilities.getExternalVariable(JAVA_UTIL_CLUSTERID); if (StringUtilities.hasContent(envName)) { String envValue = SystemUtilities.getExternalVariable(envName); id = getServerId(envValue); setVia = "environment variable: " + envName; } if (id == -1) { // Try Cloud Foundry instance index id = getServerId("CF_INSTANCE_INDEX"); setVia = "environment variable: CF_INSTANCE_INDEX"; if (id == -1) { String hostName = SystemUtilities.getExternalVariable("HOSTNAME"); if (StringUtilities.isEmpty(hostName)) { // use random number if all else fails SecureRandom random = new SecureRandom(); id = abs(random.nextInt()) % 100; setVia = "new SecureRandom()"; } else { String hostnameSha256 = EncryptionUtilities.calculateSHA256Hash(hostName.getBytes(StandardCharsets.UTF_8)); id = (byte) ((hostnameSha256.charAt(0) & 0xFF) % 100); setVia = "environment variable hostname: " + hostName + " (" + hostnameSha256 + ")"; } } } } System.out.println("java-util using server id=" + id + " for last two digits of generated unique IDs. Set using " + setVia); serverId = id; } private static int getServerId(String externalVarName) { try { String id = SystemUtilities.getExternalVariable(externalVarName); if (StringUtilities.isEmpty(id)) { return -1; } return abs(parseInt(id)) % 100; } catch (Throwable e) { System.err.println("Unable to get unique server id or index from environment variable/system property key-value: " + externalVarName); e.printStackTrace(System.err); return -1; } } /** * ID format will be 1234567890123.999.99 (no dots - only there for clarity - the number is a long). There are * 13 digits for time - good until 2286, and then it will be 14 digits (good until 5138) for time - milliseconds * since Jan 1, 1970. This is followed by a count that is 000 through 999. This is followed by a random 2 digit * number. This number is chosen when the JVM is started and then stays fixed until next restart. This is to * ensure cluster uniqueness.
*
* Because there is the possibility two machines could choose the same random number and be at the same count, at the * same time, a unique machine index is chosen to provide a 00 to 99 value for machine instance within a cluster. * To set the unique machine index value, set the environment variable JAVA_UTIL_CLUSTERID to a unique two-digit * number on each machine in the cluster. If the machines are in a managed container, the uniqueId will use the * hash of the hostname, first byte of hash, modulo 100 to provide unique machine ID. If neither of these * environment variables are set, it will resort to using a secure random number from 00 to 99 for the machine * instance number portion of the unique ID.
*
* This API is slower than the 19 digit API. Grabbing a bunch of IDs in a tight loop for example, could cause * delays while it waits for the millisecond to tick over. This API can return up to 1,000 unique IDs per millisecond.
*
* The IDs returned are guaranteed to be strictly increasing. * * @return long unique ID */ public static long getUniqueId() { lock.lock(); try { long id = getUniqueIdAttempt(); while (lastIds.containsKey(id)) { id = getUniqueIdAttempt(); } lastIds.put(id, null); return id; } finally { lock.unlock(); } } private static long getUniqueIdAttempt() { count++; if (count >= 1000) { count = 0; } long currentTimeMilliseconds = currentTimeMillis(); if (currentTimeMilliseconds > previousTimeMilliseconds) { count = 0; previousTimeMilliseconds = currentTimeMilliseconds; } return currentTimeMilliseconds * 100_000 + count * 100L + serverId; } /** * ID format will be 1234567890123.9999.99 (no dots - only there for clarity - the number is a long). There are * 13 digits for time - milliseconds since Jan 1, 1970. This is followed by a count that is 0000 through 9999. * This is followed by a random 2-digit number. This number is chosen when the JVM is started and then stays fixed * until next restart. This is to ensure uniqueness within cluster.
*
* Because there is the possibility two machines could choose the same random number and be at the same count, at the * same time, a unique machine index is chosen to provide a 00 to 99 value for machine instance within a cluster. * To set the unique machine index value, set the environment variable JAVA_UTIL_CLUSTERID to a unique two-digit * number on each machine in the cluster. If the machines are in a managed container, the uniqueId will use the * hash of the hostname, first byte of hash, modulo 100 to provide unique machine ID. If neither of these * environment variables are set, will it resort to using a secure random number from 00 to 99 for the machine * instance number portion of the unique ID.
*
* The returned ID will be 19 digits and this API will work through 2286. After then, it will return negative * numbers (still unique).
*
* This API is faster than the 18 digit API. This API can return up to 10,000 unique IDs per millisecond.
*
* The IDs returned are guaranteed to be strictly increasing. * * @return long unique ID */ public static long getUniqueId19() { lock19.lock(); try { long id = getFullUniqueId19(); while (lastIdsFull.containsKey(id)) { id = getFullUniqueId19(); } lastIdsFull.put(id, null); return id; } finally { lock19.unlock(); } } // Use up to 19 digits (much faster) private static long getFullUniqueId19() { count2++; if (count2 >= 10_000) { count2 = 0; } long currentTimeMilliseconds = currentTimeMillis(); if (currentTimeMilliseconds > previousTimeMilliseconds2) { count2 = 0; previousTimeMilliseconds2 = currentTimeMilliseconds; } return currentTimeMilliseconds * 1_000_000 + count2 * 100L + serverId; } /** * Find out when the ID was generated. * * @param uniqueId long unique ID that was generated from the .getUniqueId() API * @return Date when the ID was generated, with the time portion accurate to the millisecond. The time * is measured in milliseconds, between the time the id was generated and midnight, January 1, 1970 UTC. */ public static Date getDate(long uniqueId) { return new Date(uniqueId / 100_000); } /** * Find out when the ID was generated. "19" version. * * @param uniqueId long unique ID that was generated from the .getUniqueId19() API * @return Date when the ID was generated, with the time portion accurate to the millisecond. The time * is measured in milliseconds, between the time the id was generated and midnight, January 1, 1970 UTC. */ public static Date getDate19(long uniqueId) { return new Date(uniqueId / 1_000_000); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy