自定义分布式ID算法

自定义分布式ID算法

算法原理

参考雪花算法,将当前时间减去基准时间(8位36进制) + 自增序列(4位36进制) + 服务器ID(4位10进制:worker+datacenter) + 随机数(4位10进制)。

例如:当前时间和20100101的差值(8位36进制可使用89年,9位可以使用3220年) + 自增序列(4位36进制,每毫秒支持160万个自增序列值) +服务器ID(4位10进制:worker+datacenter) + 随机数(4位10进制,可拆分8192个数据库),理论上不同服务器每秒支持生成至少16亿个不重复ID(实际笔记本测试可达400万个ID/秒)

使用36进制的原因:

主要是解决雪花算法时间回拨导致ID重复问题,增加ID的实际精度实现唯一性。之所以不采用62进制是因为ID有可能用于文件名,部分数据库可能设置为不区分大小写的场景,而且62进制对ID的整体长度影响不大。

优点

  1. 毫秒数在高位,自增序列在低位,整个 ID 都是趋势递增的。(同一毫秒内排序可能不正确,因为计数器没做清零)
  2. 不依赖数据库等第三方系统,以服务的方式部署,稳定性更高,生成 ID 的性能也是非常高的。可以达到400万个ID/秒。
  3. 可以根据自身业务特性分配 bit 位,非常灵活。
  4. 支持类似mycat的分库分表机制,可以根据策略进行分库的框架,最大支持拆分8192个数据库。
  5. ID长度在89年内保持20位,大于89年为21位,有随机数和自增序列可以保证数据的安全。
  6. 理论不存在时钟回拨问题,计数器每毫秒不清零,并且每毫秒有9999个随机数。
  7. 可支持本地批量生成,缓存ID。

缺点

  1. ID是20位字符串,相对自增序列占用更大的存储空间。
  2. 不满足具备业务规则的id生成方式,例如希望以年月日开头的id。
  3. 分库分表只支持随机或者根据worker+datacenter方式拆分。不支持类似自增序列的方式进行拆分。
  4. VARCHAR似乎比NUMBER类型性能差一些(oracle自己测试感觉差的不多),对索引的支持可能不同的数据库有一些差异,例如对id有order by 或者是大于,小余等需求。
  5. 其他未知问题。

源码

StringIdGenerator

import java.security.SecureRandom;

/**
 * 20位字符串Id生成类
 *
 * @author huangsq
 * @version 1.0, 2018-02-19
 */
public class StringIdGenerator implements IdGenerator<String> {

	/**
	 * 开始时间截 (20100101)
	 */
	public static final long START_TIME = 1262275200000L;

	public static final int MIN_SEQUENCE = 50000;
	public static final int MAX_SEQUENCE = 1679615;
	/**
	 * 支持的最大服务器ID标识
	 */
	public static final long MAX_SERVER_ID = 9999;
	/**
	 * 默认进制
	 */
	private static final int RADIX = 36;

	/**
	 * 机器id所占的位数
	 */
	private static final long workerIdBits = 12L;
	/**
	 * 数据标识id所占的位数
	 */
	private static final long datacenterIdBits = 12L;
	/**
	 * 支持的最大机器id,结果是4095
	 */
	private static final long maxWorkerId = -1L ^ (-1L << workerIdBits);
	/**
	 * 支持的最大数据标识id,结果是4095
	 */
	private static final long maxDatacenterId = -1L ^ (-1L << datacenterIdBits);
	/**
	 * 机器ID向左移12位
	 */
	private final long workerIdShift = 0;
	/**
	 * 数据标识id向左移17位(12+5)
	 */
	private final long datacenterIdShift = workerIdBits;

	private SecureRandom random = new SecureRandom();

	private int sequence = MIN_SEQUENCE;

	public static void main(String[] args) {
		System.out.println(maxWorkerId);
	}

	/**
	 * 上次生成ID的时间截
	 */
	private long lastTimestamp = -1L;

	/**
	 * 服务器ID(0~9999)
	 */
	private int serverId;

	/**
	 * 构造函数
	 *
	 * @param workerId     工作ID (0~31)
	 * @param datacenterId 数据中心ID (0~31)
	 */
	public StringIdGenerator(int workerId, int datacenterId) {
		if (workerId > maxWorkerId || workerId < 0) {
			throw new IllegalArgumentException(
					String.format("worker Id can't be greater than %d or less than 0", maxWorkerId));
		}
		if (datacenterId > maxDatacenterId || datacenterId < 0) {
			throw new IllegalArgumentException(
					String.format("datacenter Id can't be greater than %d or less than 0", maxDatacenterId));
		}
		this.serverId = (datacenterId << datacenterIdShift) | (workerId << workerIdShift);
	}

	/**
	 * 生成20位字符串ID:当前时间和20100101的差值(8位36进制可使用89年,9位3220年) + 自增序列(4位36进制,160万个自增序列值) +
* 服务器ID(4位10进制:worker+datacenter) + 随机数(4位10进制,可拆分8192个数据库),理论上不同服务器每秒支持生成16亿个不重复ID(实际400万个ID/秒)。 * * @return * @author huangsq */
public String nextId() { return nextId(serverId); } /** * 生成20位字符串ID:当前时间和20100101的差值(8位36进制可使用89年,9位3220年) + 自增序列(4位36进制,160万个自增序列值) +
* 服务器ID(4位10进制:worker+datacenter) + 随机数(4位10进制,可拆分8192个数据库),理论上不同服务器每秒支持生成16亿个不重复ID(实际400万个ID/秒)。 * * @param serverId 服务器ID(worker+datacenter) * @return * @author huangsq */
public String nextId(int serverId) { if (serverId > MAX_SERVER_ID || serverId < 0) { throw new IllegalArgumentException(String.format("serverId can't be > %d or < 0", MAX_SERVER_ID)); } StringBuilder idString = timeIdBuilder(RADIX); appendId(idString, serverId); appendId(idString, random.nextInt(10000)); return idString.toString(); } /** * 生成16位字符串ID:当前时间和20100101的差值(8位36进制可使用89年,9位3220年) + 自增序列(4位36进制,160万个自增序列值) +
* 随机数(4位10进制),理论上每秒支持生成16亿个不重复ID。多用于临时文件名。 * * @return * @author huangsq */
public String timeId() { StringBuilder idString = timeIdBuilder(RADIX); appendId(idString, random.nextInt(10000)); return idString.toString(); } /** * 生成12位字符串ID:当前时间和20100101的差值(8位36进制可使用89年,9位3220年) + 自增序列(4位36进制,160万个自增序列值) +
* * @return * @author huangsq */
private synchronized StringBuilder timeIdBuilder(int radix) { long timestamp = timeGen(); // 如果是同一时间生成的,则进行毫秒内序列 ++sequence; if (sequence > MAX_SEQUENCE) { sequence = MIN_SEQUENCE; if (lastTimestamp == timestamp) { // 毫秒内序列溢出 // 阻塞到下一个毫秒,获得新的时间戳 timestamp = tilNextMillis(lastTimestamp); } } lastTimestamp = timestamp; StringBuilder idString = new StringBuilder(); idString.append(NumberUtil.toString(timestamp - START_TIME, radix)); idString.append(NumberUtil.toString(sequence, radix)); return idString; } private void appendId(StringBuilder idString, int number) { if (number < 1) { idString.append("0000"); } else if (number < 10) { idString.append("000").append(number); } else if (number < 100) { idString.append("00").append(number); } else if (number < 1000) { idString.append('0').append(number); } else { idString.append(number); } } /** * 阻塞到下一个毫秒,直到获得新的时间戳 * * @param lastTimestamp 上次生成ID的时间截 * @return 当前时间戳 */ private static long tilNextMillis(long lastTimestamp) { long timestamp = timeGen(); while (timestamp <= lastTimestamp) { timestamp = timeGen(); } return timestamp; } /** * 返回以毫秒为单位的当前时间 * * @return 当前时间(毫秒) */ private static long timeGen() { return System.currentTimeMillis(); } }

NumberUtil

import java.util.HashMap;
import java.util.Map;
import java.util.Random;

/**
 * 进制转换工具类
 * @author huangsq
 * @version 1.0, 2018-02-19
 */
public final class NumberUtil {

	static final String CHAR62 = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
	static final char[] digits = CHAR62.toCharArray();

	static final Map<Character, Integer> digitMap = new HashMap<Character, Integer>();

	/**
	 * 支持的最大进制数
	 */
	public static final int MAX_RADIX = digits.length;

	/**
	 * 支持的最小进制数
	 */
	public static final int MIN_RADIX = 2;
	/**
	 * 32进制
	 */
	public static final int RADIX32 = 32;
	/**
	 * 36进制
	 */
	public static final int RADIX36 = 36;

	/**
	 * 默认除法运算精度
	 */
	public static final int DEF_DIV_SCALE = 17;

	/**
	 * 随机数
	 */
	private static final Random random = new Random();

	static {
		for (int i = 0; i < digits.length; i++) {
			digitMap.put(digits[i], (int) i);
		}
	}

	private NumberUtil() {
	}

	/**
	 * 将长整型数值转换为62进制数
	 * @param num
	 * @return
	 */
	public static String toString(long num) {
		return toString(num, MAX_RADIX);
	}

	/**
	 * 将长整型数值转换为32进制数
	 * @param num
	 * @return
	 */
	public static String toString32(long num) {
		return toString(num, RADIX32);
	}

	/**
	 * 将长整型数值转换为36进制数
	 * @param num
	 * @return
	 */
	public static String toString36(long num) {
		return toString(num, RADIX36);
	}

	/**
	 * 将长整型数值转换为指定的进制数(最大支持62进制,字母数字已经用尽)
	 * @param num
	 * @param radix
	 * @return
	 */
	public static String toString(long num, int radix) {
		if (radix < MIN_RADIX || radix > MAX_RADIX)
			radix = 10;
		if (radix == 10)
			return Long.toString(num);

		final int size = 65;
		int charPos = 64;

		char[] buf = new char[size];
		boolean negative = (num < 0);

		if (!negative) {
			num = -num;
		}

		while (num <= -radix) {
			buf[charPos--] = digits[(int) (-(num % radix))];
			num = num / radix;
		}
		buf[charPos] = digits[(int) (-num)];

		if (negative) {
			buf[--charPos] = '-';
		}

		return new String(buf, charPos, (size - charPos));
	}

	/**
	 * 将62进制字符串转换为长整型数字
	 * @param str 数字字符串
	 * @param radix 进制数
	 * @return
	 */
	public static long toNumber(String str) {
		return toNumber(str, MAX_RADIX);
	}

	/**
	 * 将32进制字符串转换为长整型数字
	 * @param str 数字字符串
	 * @param radix 进制数
	 * @return
	 */
	public static long toNumber32(String str) {
		return toNumber(str, RADIX32);
	}

	/**
	 * 将36进制字符串转换为长整型数字
	 * @param str 数字字符串
	 * @param radix 进制数
	 * @return
	 */
	public static long toNumber36(String str) {
		return toNumber(str, RADIX36);
	}

	/**
	 * 将字符串转换为长整型数字
	 * @param str 数字字符串
	 * @param radix 进制数
	 * @return
	 */
	public static long toNumber(String str, int radix) {
		if (str == null) {
			throw new NumberFormatException("null");
		}

		if (radix < MIN_RADIX) {
			throw new NumberFormatException("radix " + radix + " less than " + MIN_RADIX);
		}
		if (radix > MAX_RADIX) {
			throw new NumberFormatException("radix " + radix + " greater than " + MAX_RADIX);
		}

		long result = 0;
		boolean negative = false;
		int len = str.length();
		int i = 0;
		long limit = -Long.MAX_VALUE;
		long multmin;
		Integer digit;

		if (len > 0) {
			char firstChar = str.charAt(0);
			if (firstChar < '0') {
				if (firstChar == '-') {
					negative = true;
					limit = Long.MIN_VALUE;
				} else if (firstChar != '+') {
					throw forInputString(str);
				}

				if (len == 1) {
					throw forInputString(str);
				}
				i++;
			}
			multmin = limit / radix;
			while (i < len) {
				digit = digitMap.get(str.charAt(i++));
				if (digit == null) {
					throw forInputString(str);
				}
				if (digit < 0) {
					throw forInputString(str);
				}
				if (result < multmin) {
					throw forInputString(str);
				}
				result *= radix;
				if (result < limit + digit) {
					throw forInputString(str);
				}
				result -= digit;
			}
		} else {
			throw forInputString(str);
		}
		return negative ? result : -result;
	}

	/**
	 * 随机生成指定长度的字符串,由数字、小写字母和大写字母组成
	 * @param length 字符串长度
	 * @return
	 */
	public static String random(int length) {
		return random(length, RADIX36);
	}

	/**
	 * 随机生成指定长度的字符串,由数字、小写字母和大写字母组成
	 * @param length 字符串长度
	 * @param radix 进制数
	 * @return
	 */
	public static String random(int length, int radix) {
		char[] cs = new char[length];
		for (int i = 0; i < length; i++) {
			cs[i] = digits[random.nextInt(radix)];
		}
		return new String(cs);
	}

	private static NumberFormatException forInputString(String s) {
		return new NumberFormatException("For input string: \"" + s + "\"");
	}

}

欢迎留言分享您认为更优秀的分布式ID算法!

你可能感兴趣的:(Java,分布式,ID)