TimeId 基于时间戳的自增ID算法(Java版)
常用的全局唯一ID算法
1、UUID
首先是大名鼎鼎的 UUID,UUID 是通用唯一识别码(Universally Unique Identifier)的缩写。
UUID是一个128比特的数值,是基于当前时间、计数器(counter)和硬件标识(通常为无线网卡的MAC地址)等数据计算生成的。
虽然 UUID 碰撞几率不为零,但它足够接近于零,可以忽略不计,可以认为UUID能够保证全局唯一。
但是 UUID 有一些缺点,首先他相对比较长(36个字符,去掉多余的“-”后依旧有32个字符),另外UUID一般是无序的(默认的版本4)。
2、SnowFlake
SnowFlake 雪花算法是 Twitter 开源的分布式 ID 生成算法,其具有简洁、高性能、低延迟、ID 按时间趋势有序等特点。
雪花算法生成后是一个 64bit 的 Long 型的数值,其中有1位标识,41位时间截,10位的数据机器位,12位毫秒内的计数。
0 | timestamp (41 bits) | node ID (10 bits) | sequence number (12 bits)
因为第一位标识符是 0(保证long是一个正整数),后面就是时间戳,所以整个ID基本保持了自增。
雪花算法较高的性能和吞吐量,生成时不依赖于数据库,完全在内存中生成,每秒能够产生26万ID左右,能够满足绝大多数高并发场景下的互联网应用的要求,并且因为其自增的特性,数据库索引效率也很高。(在分布式系统多节点的情况下,所有节点的时钟并不能保证不完全同步,所以有可能会出现不是全局递增的情况,但是总体是递增的。)
默认的雪花算法41位时间戳可以使用69年,如果时间戳从2015年开始的,到了2084年会出现ID冲突问题。
算法实现:https://www.cnblogs.com/relucent/p/4955340.html
3、NanoID
NanoID 是一个用于生成小型、安全且唯一标识符(ID)的 JavaScript 库(目前也有其他语言版本的实现)。它专门设计用于在不同的应用场景中生成短、易于处理的标识符,例如用作数据库记录的主键、URL 缩短服务的短链接标识等。
NanoID 它通过使用高质量的随机数生成算法,能够确保生成的标识符是唯一的并且难以预测。NanoID 与 UUID v4 (基于随机) 相当,它们在 ID 中有相似数量的随机位 (NanoID 为126,UUID 为122),因此它们的冲突概率相似。
NanoID 比 UUID 更加紧凑,使用更大的字母表(A-Za-z0-9_-)。 因此,ID 大小从36个符号减少到21个符号。NanoID使用URL友好字符(A-Za-z0-9_-)。非常适合web应用程序中的唯一标识符。
默认的 NanoID 中包含了字母的大小写,在大小写不敏感的情况下(例如对属性大小写不敏感的数据库),冲突概率会增加,但是可以通过自定义字母表以及增加长度,对该问题进行优化。
此外,NanoID生成是无序的,对聚类索引的数据列(B-TREE索引列)并不友好。
TimeId 算法
1、算法介绍
TimeId 是一种时间序列ID算法(20位字符串),其组成结构如下:
时间戳 + 循环计数 + 随机数 + 网络地址 + 进程号
优势:
1、TimeId 是一种时间戳算法,所以保证了其数值整体有序的(有序但不连贯),数据库索引效率也很高。
2、与 SnowflakeId 算法相比,TimeId 的冲突的概率比SnowflakeId低,也没有69年的限制。
3、与 UUID 相比,TimeId 大小从36个符号减少到20个符号。
缺点:
1、因为 TimeId 是一个字符串,相对 SnowflakeId(长整型)会占用更大的空间,但是依旧比 UUID 紧凑。
2、安全性上与 UUID v1 (根据时间和节点生成)类似,使用了节点ID来确保唯一性,雪花算法也存在类似问题。
2、TimeId 的实现(JAVA版本)
注:以下代码实现的网络地址获取部分只考虑了IPv4,如果需要IPv6,请自己修改 isValidIPv4 方法。
import java.lang.management.ManagementFactory;
import java.math.BigInteger;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.net.SocketException;
import java.security.SecureRandom;
import java.util.Enumeration;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.regex.Pattern;
/**
* 时间序列ID生成器(20位)<br>
* 时间戳+循环计数+随机数+网络地址+进程号<br>
* 优势:<br>
* TimeId 是有序的(有序但不连贯)<br>
* TimeId 使用了和 SnowflakeIdWorker 类似的时间戳算法,但是冲突的概率更低<br>
* TimeId 与 UUID 相比,大小从36个符号减少到20个符号<br>
* 劣势:<br>
* TimeId 是字符串,相对 SnowflakeIdWorker( 长整型) 占用更大的空间,但是比UUID紧凑
*/
public class TimeId {
private static final int RADIX36 = 36;
private static final int TIMESTAMP_LENGTH = 9;
private static final int COUNTER_LENGTH = 5;
private static final int MAC_LENGTH = 2;
private static final int PID_LENGTH = 1;
private static final int RANDOM_LENGTH = 3;
private static final int COUNTER_MOD = computeRadix36Mod(COUNTER_LENGTH);
private static final int MAC_MOD = computeRadix36Mod(MAC_LENGTH);
private static final int PID_MOD = computeRadix36Mod(PID_LENGTH);
private static final int RANDOM_MOD = computeRadix36Mod(RANDOM_LENGTH);
private static final char ZERO_CHAR = '0';
private static final String LOCALHOST_IPV4 = "127.0.0.1";
private static final String ANYHOST_IPV4 = "0.0.0.0";
private static final String BROADCAST_IPV4 = "255.255.255.255";
private static final Pattern IPV4_PATTERN = Pattern.compile(//
"^(2(5[0-5]{1}|[0-4]\\d{1})|[0-1]?\\d{1,2})(\\.(2(5[0-5]{1}|[0-4]\\d{1})|[0-1]?\\d{1,2})){3}$"//
);
/** 循环序列号 */
private static final AtomicInteger NEXT_COUNTER = new AtomicInteger(new SecureRandom().nextInt());
/** 此类用来创建随机数的随机数生成器 */
private static class Holder {
static final SecureRandom NUMBER_GENERATOR = new SecureRandom();
static final long MAC_VALUE = getMac();
static final long PID_VALUE = getPid();
}
/**
* 构造函数
*/
protected TimeId() {
}
/**
* 生成 TimeId
* @return TimeId 字符串
*/
public static String nextId() {
StringBuilder buffer = new StringBuilder();
append(buffer, timeGen(), TIMESTAMP_LENGTH);
append(buffer, Math.abs(NEXT_COUNTER.getAndIncrement() % COUNTER_MOD), COUNTER_LENGTH);
append(buffer, Math.abs(Holder.MAC_VALUE % MAC_MOD), MAC_LENGTH);
append(buffer, Math.abs(Holder.PID_VALUE % PID_MOD), PID_LENGTH);
append(buffer, Math.abs(Holder.NUMBER_GENERATOR.nextLong() % RANDOM_MOD), RANDOM_LENGTH);
return buffer.toString();
}
/**
* 将数值添加到字符串缓冲中
* @param buffer 字符串缓冲中
* @param value 数值
* @param length 添加的位数
*/
private static void append(StringBuilder buffer, long value, int length) {
buffer.append(leftPad(Long.toString(value, RADIX36), length, ZERO_CHAR));
}
/**
* 返回以毫秒为单位的当前时间
* @return 当前时间(毫秒)
*/
private static long timeGen() {
return System.currentTimeMillis();
}
/**
* 获得MAC值
* @return MAC值
*/
private static long getMac() {
long mac = 0;
try {
byte[] address = getHardwareAddress();
mac = new BigInteger(address).longValue();
} catch (Exception | Error e) {
mac = Holder.NUMBER_GENERATOR.nextLong();
}
return mac;
}
/**
* 获得当前进程ID
* @return 进程ID
*/
private static long getPid() {
long pid = 0;
String name = ManagementFactory.getRuntimeMXBean().getName();
try {
pid = Long.parseLong(name.split("@")[0]);
} catch (Exception | Error e) {
pid = Holder.NUMBER_GENERATOR.nextLong();
}
return pid;
}
/**
* 计算模数
* @param length 长度
* @return 进制模(32)
*/
private static int computeRadix36Mod(int length) {
int value = 1;
for (int i = 0; i < length; i++) {
value *= RADIX36;
}
return value - 1;
}
/**
* 左填充指定字符的字符串.
* @param cs 需要填充的字符串
* @param size 要填充到的大小
* @param padChar 要填充的字符
* @return 左填充字符串或原始字符串(如果不需要填充)
*/
private static String leftPad(final CharSequence cs, final int size, final char padChar) {
if (cs == null) {
return null;
}
final int pads = size - cs.length();
if (pads <= 0) {
return cs.toString();
}
StringBuilder builder = new StringBuilder(size);
for (int i = 0; i < pads; i++) {
builder.append(padChar);
}
builder.append(cs);
return builder.toString();
}
/**
* 获得本机 MAC地址
* @return 本机MAC地址
*/
private static final byte[] getHardwareAddress() {
try {
for (Enumeration<NetworkInterface> en = NetworkInterface.getNetworkInterfaces(); en.hasMoreElements();) {
NetworkInterface networkInterface = en.nextElement();
for (Enumeration<InetAddress> addrs = networkInterface.getInetAddresses(); addrs.hasMoreElements();) {
String ip = addrs.nextElement().getHostAddress();
if (isValidIPv4(ip)) {
return networkInterface.getHardwareAddress();
}
}
}
} catch (SocketException e) {
// Ignore
}
return new byte[0];
}
/**
* 判断是否是有效IPv4地址
* @param ip IP地址
* @return 如果是有效IPv4地址返回true,否则返回false
*/
private static boolean isValidIPv4(String ip) {
return (ip != null //
&& !ANYHOST_IPV4.equals(ip) //
&& !LOCALHOST_IPV4.equals(ip) //
&& !BROADCAST_IPV4.equals(ip) //
&& IPV4_PATTERN.matcher(ip).matches());
}
}
使用方式
// 获取全局唯一ID
String id = TimeId.nextId();