天天看點

java生成8位的uuid_分布式ID生成器

在分布式系統中,往往需要對大量的資料和消息進行唯一辨別,此時一個能夠生成全局唯一ID的系統是非常必要的,那麼業務系統對ID号的要求有哪些呢?

  1. 全局唯一性:不能出現重複的ID号,既然是唯一辨別,這是最基本的要求。
  2. 趨勢遞增:在MySQL InnoDB引擎中使用的是聚集索引,在主鍵的選擇上面我們應該盡量使用有序的主鍵保證寫入性能。
  3. 單調遞增:保證下一個ID一定大于上一個ID,例如事務版本号、IM增量消息、排序等特殊需求。

UUID

UUID(Universally Unique Identifier)的标準型式包含32個16進制數字,以連字号分為五段,形式為8-4-4-4-12的36個字元,示例:5e8c4456-6166-40d6-9b9f-fb37a150bc6e,到目前為止業界一共有5種方式生成UUI,Java标準類庫中已經提供了UUID的API。

UUID.randomUUID()
           

優點:

  • 性能非常高:本地生成,沒有網絡消耗。

缺點:

  • 不易于存儲:UUID太長,16位元組128位,通常以36長度的字元串表示,很多場景不适用。
  • 主鍵,在InnoDB引擎下,UUID的無序性可能會引起資料位置頻繁變動,嚴重影響性能。

類snowflake方案

雪花ID生成的是一個64位的二進制正整數,然後轉換成10進制的數。64位二進制數由如下部分組成:

java生成8位的uuid_分布式ID生成器

41-bit的時間可以表示(1L<<41)/(1000L*3600*24*365)=69年的時間,10-bit機器可以分别表示1024台機器。如果我們對IDC劃分有需求,還可以将10-bit分5-bit給IDC,分5-bit給工作機器。這樣就可以表示32個IDC,每個IDC下可以有32台機器,可以根據自身需求定義。12個自增序列号可以表示2^12個ID,理論上snowflake方案的QPS約為409.6w/s,這種配置設定方式可以保證在任何一個IDC的任何一台機器在任意毫秒内生成的ID都是不同的。

但是對于絕大部分普通應用程式來說,根本不需要每秒超過400萬的ID,機器數量也達不到1024台,是以,我們可以改進一下,使用更短的ID生成方式:53bitID由32bit秒級時間戳+16bit自增+5bit機器辨別組成,累積32台機器,每秒可以生成6.5萬個序列号。

代碼示例

/** * 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 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++; //如果offset=65536 next=0 long next = offset & MAX_NEXT; if (next == 0) { logger.warn("maximum id reached in 1 second in epoch: " + epochSecond); //向下一秒借65535個 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 {}: {}.