對於絕大部分普通應用程序來講,根本不須要每秒超過400萬的ID,機器數量也達不到1024臺,因此,咱們能夠改進一下,使用更短的ID生成方式:java
53bitID由32bit秒級時間戳+16bit自增+5bit機器標識組成,累積32臺機器,每秒能夠生成6.5萬個序列號,核心代碼:git
package com.itranswarp.util; import java.net.InetAddress; import java.net.UnknownHostException; import java.time.LocalDate; import java.time.ZoneId; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * 53 bits unique id: * * |--------|--------|--------|--------|--------|--------|--------|--------| * |00000000|00011111|11111111|11111111|11111111|11111111|11111111|11111111| * |--------|---xxxxx|xxxxxxxx|xxxxxxxx|xxxxxxxx|xxx-----|--------|--------| * |--------|--------|--------|--------|--------|---xxxxx|xxxxxxxx|xxx-----| * |--------|--------|--------|--------|--------|--------|--------|---xxxxx| * * Maximum ID = 11111_11111111_11111111_11111111_11111111_11111111_11111111 * * Maximum TS = 11111_11111111_11111111_11111111_111 * * Maximum NT = ----- -------- -------- -------- ---11111_11111111_111 = 65535 * * Maximum SH = ----- -------- -------- -------- -------- -------- ---11111 = 31 * * It can generate 64k unique id per IP and up to 2106-02-07T06:28:15Z. */ public final class IdUtil { private static final Logger logger = LoggerFactory.getLogger(IdUtil.class); private static final Pattern PATTERN_LONG_ID = Pattern.compile("^([0-9]{15})([0-9a-f]{32})([0-9a-f]{3})$"); private static final Pattern PATTERN_HOSTNAME = Pattern.compile("^.*\\D+([0-9]+)$"); private static final long OFFSET = LocalDate.of(2000, 1, 1).atStartOfDay(ZoneId.of("Z")).toEpochSecond(); private static final long MAX_NEXT = 0b11111_11111111_111L; private static final long SHARD_ID = getServerIdAsLong(); private static long offset = 0; private static long lastEpoch = 0; public static long nextId() { return nextId(System.currentTimeMillis() / 1000); } private static synchronized long nextId(long epochSecond) { if (epochSecond < lastEpoch) { // warning: clock is turn back: logger.warn("clock is back: " + epochSecond + " from previous:" + lastEpoch); epochSecond = lastEpoch; } if (lastEpoch != epochSecond) { lastEpoch = epochSecond; reset(); } offset++; long next = offset & MAX_NEXT; if (next == 0) { logger.warn("maximum id reached in 1 second in epoch: " + epochSecond); return nextId(epochSecond + 1); } return generateId(epochSecond, next, SHARD_ID); } private static void reset() { offset = 0; } private static long generateId(long epochSecond, long next, long shardId) { return ((epochSecond - OFFSET) << 21) | (next << 5) | shardId; } private static long getServerIdAsLong() { try { String hostname = InetAddress.getLocalHost().getHostName(); Matcher matcher = PATTERN_HOSTNAME.matcher(hostname); if (matcher.matches()) { long n = Long.parseLong(matcher.group(1)); if (n >= 0 && n < 8) { logger.info("detect server id from host name {}: {}.", hostname, n); return n; } } } catch (UnknownHostException e) { logger.warn("unable to get host name. set server id = 0."); } return 0; } }
時間戳減去一個固定值,此方案最高可支持到2106年。github
若是每秒6.5萬個序列號不夠怎麼辦?不要緊,能夠繼續遞增時間戳,向前「借」下一秒的6.5萬個序列號。.net
同時還解決了時間回撥的問題。code
機器標識採用簡單的主機名方案,只要主機名符合host-1
,host-2
就能夠自動提取機器標識,無需配置。server
最後,爲何採用最多53位整型,而不是64位整型?這是由於考慮到大部分應用程序是Web應用,若是要和JavaScript打交道,因爲JavaScript支持的最大整型就是53位,超過這個位數,JavaScript將丟失精度。所以,使用53位整數能夠直接由JavaScript讀取,而超過53位時,就必須轉換成字符串才能保證JavaScript處理正確,這會給API接口帶來額外的複雜度。這也是爲何新浪微博的API接口會同時返回id
和idstr
的緣由。接口
參考:ip
https://www.liaoxuefeng.com/article/1280526512029729字符串
https://github.com/michaelliao/itranswarp/blob/master/src/main/java/com/itranswarp/util/IdUtil.javaget