Snowflake算法 - 變種-53位

對於絕大部分普通應用程序來講,根本不須要每秒超過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-1host-2就能夠自動提取機器標識,無需配置。server

最後,爲何採用最多53位整型,而不是64位整型?這是由於考慮到大部分應用程序是Web應用,若是要和JavaScript打交道,因爲JavaScript支持的最大整型就是53位,超過這個位數,JavaScript將丟失精度。所以,使用53位整數能夠直接由JavaScript讀取,而超過53位時,就必須轉換成字符串才能保證JavaScript處理正確,這會給API接口帶來額外的複雜度。這也是爲何新浪微博的API接口會同時返回ididstr的緣由。接口

 

參考:ip

https://www.liaoxuefeng.com/article/1280526512029729字符串

https://github.com/michaelliao/itranswarp/blob/master/src/main/java/com/itranswarp/util/IdUtil.javaget

相關文章
相關標籤/搜索