IdGenerator.java

/*
 * Copyright 2018 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.sandy.ecp.framework.util;

/**
 * twitter的snowflake 移植到Java: (a) id构成: 42位的时间前缀 + 10位的节点标识 + 12位的sequence避免并发的数字(12位不够用时强制得到新的时间前缀) 注意这里进行了小改动:
 * snowkflake是5位的datacenter加5位的机器id; 这里变成使用10位的机器id (b)
 * 对系统时间的依赖性非常强,需关闭ntp的时间同步功能。当检测到ntp时间调整后,将会拒绝分配id
 */
public class IdGenerator {

	private final long workerId;
	private final long epoch = 1546266464884L; 	// 时间起始标记点,作为基准,一般取系统的最近时间		// 1483200001000L
	private final long workerIdBits = 10L; 		// 机器标识位数
	private final long maxWorkerId = -1L ^ -1L << this.workerIdBits;// 机器ID最大值: 1023
	private long sequence = 0L; 				// 0,并发控制
	private final long sequenceBits = 12L; 		// 毫秒内自增位

	private final long workerIdShift = this.sequenceBits; 			// 12
	private final long timestampLeftShift = this.sequenceBits + this.workerIdBits;	// 22
	private final long sequenceMask = -1L ^ -1L << this.sequenceBits; 				// 4095,111111111111,12位
	private long lastTimestamp = -1L;

	public IdGenerator(final long workerId) {
		if (workerId > this.maxWorkerId || workerId < 0) {
			throw new IllegalArgumentException(
					String.format("worker Id can't be greater than %d or less than 0", this.maxWorkerId));
		}
		this.workerId = workerId;
	}

	public synchronized long nextId() {
		long timestamp = this.timeGen();
		if (this.lastTimestamp == timestamp) { 						// 如果上一个timestamp与新产生的相等,则sequence加一(0-4095循环); 对新的timestamp,sequence从0开始
			this.sequence = this.sequence + 1 & this.sequenceMask;
			if (this.sequence == 0) {
				timestamp = this.tilNextMillis(this.lastTimestamp);	// 重新生成timestamp
			}
		} else {
			this.sequence = 0;
		}

		if (timestamp < this.lastTimestamp) {
			throw new IllegalArgumentException(
					String.format("clock moved backwards.Refusing to generate id for %d milliseconds",
							(this.lastTimestamp - timestamp)));
		}

		this.lastTimestamp = timestamp;
		return timestamp - this.epoch << this.timestampLeftShift | this.workerId << this.workerIdShift | this.sequence;
	}

	private long tilNextMillis(final long lastTimestamp) {
		long timestamp = this.timeGen();
		while (timestamp <= lastTimestamp) {
			timestamp = this.timeGen();
		}
		return timestamp;
	}

	private long timeGen() {
		return System.currentTimeMillis();
	}
}