项目是分布式的架构,须要设计一款分布式全局ID,参照了多种方案,博主最后基于snowflake的算法设计了一款自用ID生成器。具备如下优点:java
- 保证分布式场景下生成的ID是全局惟一的
- 生成的全局ID总体上是呈自增趋势的,也就是说总体是粗略有序的
- 高性能,能快速产生ID,本机(I7-6400HQ)单线程能够达到每秒生成近40万个ID
- 只占64bit位空间,能够根据业务需求扩展在前缀或后缀拼接业务标志位转化为字符串。
是一个优秀的分布式Id生成方案,是Scala实现的,这次项目就是基于snowflake算法基础上设计的Java优化版算法
全局惟一ID生成结构以下(每部分用-分开):数据库
import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; public final class IdGenerate { // ==============================Fields=========================================== /** 开始时间截 (2018-01-01) */ private final long twepoch = 1514736000000L; /** 机器id所占的位数 */ private final long workerIdBits = 8L; /** 序列在id中占的位数 */ private final long sequenceBits = 12L; /** 毫秒级别时间截占的位数 */ private final long timestampBits = 41L; /** 生成发布方式所占的位数 */ private final long getMethodBits = 2L; /** 支持的最大机器id,结果是255 (这个移位算法能够很快的计算出几位二进制数所能表示的最大十进制数) */ private final long maxWorkerId = -1L ^ (-1L << workerIdBits); /** 生成序列向左移8位(8) */ private final long sequenceShift = workerIdBits; /** 时间截向左移20位(12+8) */ private final long timestampShift = sequenceBits + workerIdBits; /** 生成发布方式向左移61位(41+12+8) */ private final long getMethodShift = timestampBits + sequenceBits + workerIdBits; /** 工做机器ID(0~255) */ private long workerId = 0L; /** 生成序列的掩码,这里为4095 (0b111111111111=0xfff=4095) */ private final long sequenceMask = -1L ^ (-1L << sequenceBits); /** 毫秒内序列(0~4095) */ private long sequence = 0L; /** 上次生成ID的时间截 */ private long lastTimestamp = -1L; /** 2位生成发布方式,0表明嵌入式发布、1表明中心服务器发布模式、2表明rest发布方式、3表明保留未用 */ private long getMethod = 0L; /** 成发布方式的掩码,这里为3 (0b11=0x3=3) */ private long maxGetMethod = -1L ^ (-1L << getMethodBits); /** 重入锁*/ private Lock lock = new ReentrantLock(); //==============================Constructors===================================== /** * 构造函数 * @param 发布方式 0表明嵌入式发布、1表明中心服务器发布模式、2表明rest发布方式、3表明保留未用 (0~3) * @param workerId 工做ID (0~255) */ public IdGenerate(long getMethod, long workerId) { if (getMethod > maxGetMethod || getMethod < 0) { throw new IllegalArgumentException(String.format("getMethod can't be greater than %d or less than 0", maxGetMethod)); } if (workerId > maxWorkerId || workerId < 0) { throw new IllegalArgumentException(String.format("worker Id can't be greater than %d or less than 0", maxWorkerId)); } this.getMethod = getMethod; this.workerId = workerId; } public long[] nextId(int nums) { long[] ids = new long[nums]; for (int i = 0; i < nums; i++) { ids[i] = nextId(); } return ids; } // ==============================Methods========================================== /** * 得到下一个ID (该方法是线程安全的) * @return SnowflakeId */ public long nextId() { long timestamp = timeGen(); //若是当前时间小于上一次ID生成的时间戳,说明系统时钟回退过这个时候应当抛出异常 if (timestamp < lastTimestamp) { throw new RuntimeException( String.format("Clock moved backwards. Refusing to generate id for %d milliseconds", lastTimestamp - timestamp)); } //若是是同一时间生成的,则进行毫秒内序列 if (lastTimestamp == timestamp) { lock.lock(); try { sequence = (sequence + 1) & sequenceMask; //毫秒内序列溢出 if (sequence == 0) { //阻塞到下一个毫秒,得到新的时间戳 timestamp = tilNextMillis(lastTimestamp); } }finally { lock.unlock(); } } //时间戳改变,毫秒内序列重置 else { sequence = 0L; } //上次生成ID的时间截 lastTimestamp = timestamp; //移位并经过或运算拼到一块儿组成64位的ID return (getMethod << getMethodShift) // 生成方式占用2位,左移61位 | ((timestamp - twepoch) << timestampShift) // 时间差占用41位,最多69年,左移20位 | (sequence << sequenceShift) // 毫秒内序列,取值范围0-4095 | workerId; // 工做机器,取值范围0-255 } public String nextString() { return Long.toString(nextId()); } public String[] nextString(int nums) { String[] ids = new String[nums]; for (int i = 0; i < nums; i++) { ids[i] = nextString(); } return ids; } public String nextCode(String prefix) { StringBuilder sb = new StringBuilder(prefix); long id = nextId(); sb.append(id); return sb.toString(); } /** * 此方法能够在前缀上增长业务标志 * @param prefix * @param nums * @return */ public String[] nextCode(String prefix, int nums) { String[] ids = new String[nums]; for (int i = 0; i < nums; i++) { ids[i] = nextCode(prefix); } return ids; } public String nextHexString() { return Long.toHexString(nextId()); } /** * 阻塞到下一个毫秒,直到得到新的时间戳 * @param lastTimestamp 上次生成ID的时间截 * @return 当前时间戳 */ protected long tilNextMillis(long lastTimestamp) { long timestamp = timeGen(); while (timestamp <= lastTimestamp) { timestamp = timeGen(); } return timestamp; } /** * 返回以毫秒为单位的当前时间 * @return 当前时间(毫秒) */ protected long timeGen() { return System.currentTimeMillis(); } //==============================Test============================================= /** * 测试 * * */ public static void main(String[] args) { IdGenerate idGenerate = new IdGenerate(0, 0); int count = 100000;//线程数=count*count final long[][] times = new long[count][100]; Thread[] threads = new Thread[count]; for (int i = 0; i < threads.length; i++) { final int ip = i; threads[i] = new Thread() { @Override public void run() { for (int j = 0; j <100; j++) { long t1 = System.nanoTime();//该函数是返回纳秒的。1毫秒=1纳秒*1000000 idGenerate.nextId();//测试 long t = System.nanoTime() - t1; times[ip][j] = t;//求平均 } } }; } long lastMilis = System.currentTimeMillis(); //逐个启动线程 for (int i = 0; i < threads.length; i++) { threads[i].start(); } for (int i = 0; i < threads.length; i++) { try { threads[i].join(); } catch (InterruptedException e) { e.printStackTrace(); } } /** * 一、QPS:系统每秒处理的请求数(query per second) 二、RT:系统的响应时间,一个请求的响应时间,也能够是一段时间的平均值 三、最佳线程数量:恰好消耗完服务器瓶颈资源的临界线程数 对于单线程:QPS=1000/RT 对于多线程:QPS=1000*线程数量/RT */ long time = System.currentTimeMillis() - lastMilis; System.out .println("QPS: " + (1000*count /time)); long sum = 0; long max = 0; for (int i = 0; i < times.length; i++) { for (int j = 0; j < times[i].length; j++) { sum += times[i][j]; if (times[i][j] > max) max = times[i][j]; } } System.out.println("Sum(ms)"+time); System.out.println("AVG(ms): " + sum / 1000000 / (count*100)); System.out.println("MAX(ms): " + max / 1000000); } }
环境:CPU 双核I7—6400HQ 系统win10
单线程下每秒产生近40万个全局ID安全
Ps:我的水平有限,若有错误,还请批评指正。服务器