Commit 0ab5b812 authored by xiaoguang.xu's avatar xiaoguang.xu

Merge branch 'new_id_generator' into feature/0.2.0

parents d55ba119 e745ae77
/*
* Copyright (c) 2017 Baidu, Inc. All Rights Reserve.
*
* 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 cn.quantgroup.tech.util.id;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
import org.springframework.util.Assert;
import java.math.BigInteger;
/**
* Allocate 64 bits for the UID(long)<br>
* sign (fixed 1bit) -> deltaSecond -> workerId -> sequence(within the same second)
*
* @author yutianbao
*/
public class BitsAllocator {
/**
* Total 64 bits
* dataCenterIdBits + workerIdBits + sequenceBits
*/
public static final int TOTAL_BITS = 1 << 6;
private final int timestampBits;
private final int dataCenterIdBits;
private final int workerIdBits;
private final int sequenceBits;
/**
* Max value for dataCenterId & workerId & sequence
*/
private final long maxDeltaSeconds;
private final long maxDataCenterId;
private final long maxWorkerId;
private final long maxSequence;
/**
* Shift for dataCenterId & workerId & sequence
*/
private final int timestampShift;
private final int dataCenterIdShift;
private final int workerIdShift;
/**
* Constructor with timestampBits, workerIdBits, sequenceBits<br>
* The highest bit used for sign, so <code>63</code> bits for timestampBits, workerIdBits, sequenceBits
*/
public BitsAllocator(int timestampBits, int dataCenterIdBits, int workerIdBits, int sequenceBits) {
// make sure allocated 64 bits
int allocateTotalBits = dataCenterIdBits + workerIdBits + sequenceBits;
Assert.isTrue(allocateTotalBits + 1 < TOTAL_BITS, "allocate greater than 64 bits");
// initialize bits
this.timestampBits = timestampBits;
this.dataCenterIdBits = dataCenterIdBits;
this.workerIdBits = workerIdBits;
this.sequenceBits = sequenceBits;
// initialize max value
this.maxDeltaSeconds = ~(-1L << timestampBits);
this.maxDataCenterId = ~(-1L << dataCenterIdBits);
this.maxWorkerId = ~(-1L << workerIdBits);
this.maxSequence = ~(-1L << sequenceBits);
// initialize shift
this.timestampShift = dataCenterIdBits + workerIdBits + sequenceBits;
this.dataCenterIdShift = workerIdBits + sequenceBits;
this.workerIdShift = sequenceBits;
}
/**
* Allocate bits for UID according to delta seconds & workerId & sequence<br>
* <b>Note that: </b>The highest bit will always be 0 for sign
*
* @param deltaSeconds
* @param workerId
* @param sequence
* @return
*/
public long allocate(long deltaSeconds, long dataCenterId, long workerId, long sequence) {
return (deltaSeconds << timestampShift) | (dataCenterId << dataCenterIdShift) | (workerId << workerIdShift) | sequence;
}
public BigInteger allocateBigInteger(long deltaSeconds, long dataCenterId, long workerId, long sequence) {
return BigInteger.ZERO.or(BigInteger.valueOf(deltaSeconds).shiftLeft(timestampShift))
.or(BigInteger.valueOf(dataCenterId).shiftLeft(dataCenterIdShift))
.or(BigInteger.valueOf(workerId).shiftLeft(workerIdShift))
.or(BigInteger.valueOf(sequence));
}
public int getTimestampBits() {
return timestampBits;
}
public int getDataCenterIdBits() {
return dataCenterIdBits;
}
public int getWorkerIdBits() {
return workerIdBits;
}
public int getSequenceBits() {
return sequenceBits;
}
public long getMaxDeltaSeconds() {
return maxDeltaSeconds;
}
public long getMaxDataCenterId() {
return maxDataCenterId;
}
public long getMaxWorkerId() {
return maxWorkerId;
}
public long getMaxSequence() {
return maxSequence;
}
public int getTimestampShift() {
return timestampShift;
}
public int getWorkerIdShift() {
return workerIdShift;
}
@Override
public String toString() {
return ToStringBuilder.reflectionToString(this, ToStringStyle.SHORT_PREFIX_STYLE);
}
}
\ No newline at end of file
package cn.quantgroup.tech.util.id;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.time.DateFormatUtils;
import org.apache.commons.lang3.time.DateUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;
import java.math.BigInteger;
import java.text.ParseException;
import java.util.Date;
import java.util.concurrent.TimeUnit;
/**
* Represents an implementation of {@link UidGenerator}
* <p>
* The unique id has 64bits (long), default allocated as blow:<br>
* <li>sign: The highest bit is 0
* <li>delta seconds: The next 28 bits, represents delta seconds since a customer epoch(2016-05-20 00:00:00.000).
* Supports about 8.7 years until to 2024-11-20 21:24:16
* <li>worker id: The next 22 bits, represents the worker's id which assigns based on database, max id is about 420W
* <li>sequence: The next 13 bits, represents a sequence within the same second, max for 8192/s<br><br>
* <p>
* <p>
* <pre>{@code
* +------+----------------------+----------------+-----------+
* | sign | delta seconds | worker node id | sequence |
* +------+----------------------+----------------+-----------+
* 1bit 28bits 22bits 13bits
* }</pre>
* <p>
* You can also specified the bits by Spring property setting.
* <li>timeBits: default as 28
* <li>workerBits: default as 22
* <li>seqBits: default as 13
* <li>epochStr: Epoch date string format 'yyyy-MM-dd'. Default as '2016-05-20'<p>
* <p>
* <b>Note that:</b> The total bits must be 64 -1
*
* @author yutianbao
*/
@Component
public class DefaultUidGenerator implements UidGenerator, InitializingBean {
public static final String DATETIME_PATTERN = "yyyy-MM-dd HH:mm:ss";
public static final String DAY_PATTERN = "yyyy-MM-dd";
private static final Logger LOGGER = LoggerFactory.getLogger(DefaultUidGenerator.class);
/**
* Bits allocate
*/
@Value("${uid.timeBits:28}")
protected int timeBits;
@Value("${uid.dataCenterIdBits:9}")
protected int dataCenterIdBits;
@Value("${uid.workerBits:13}")
protected int workerBits;
@Value("${uid.seqBits:13}")
protected int seqBits;
/**
* Customer epoch, unit as second. For example 2018-03-01 (ms: 1463673600000)
*/
protected String epochStr;
protected long epochSeconds;
/**
* Stable fields after spring bean initializing
*/
protected BitsAllocator bitsAllocator;
protected long workerId;
protected long dataCenterId;
/**
* Volatile fields caused by nextId()
*/
protected long sequence = 0L;
protected long lastSecond = -1L;
/**
* Spring property
*/
@Autowired
protected DisposableWorkerIdAssigner workerIdAssigner;
@Override
public void afterPropertiesSet() throws Exception {
// initialize bits allocator
bitsAllocator = new BitsAllocator(timeBits, dataCenterIdBits, workerBits, seqBits);
workerIdAssigner = new DisposableWorkerIdAssigner();
// initialize worker id
workerId = workerIdAssigner.assignWorkerId(dataCenterId, bitsAllocator);
Assert.isTrue(workerId < bitsAllocator.getMaxWorkerId(), "workerId is too big");
Assert.isTrue(dataCenterId < bitsAllocator.getMaxDataCenterId(), "dataCenterId is too big");
LOGGER.info("Initialized bits dataCenterBits:{}, workerBits:{}, seqBits:{}", dataCenterIdBits, workerBits, seqBits);
LOGGER.info("Initialized nodes, workerId:{}, dataCenterId:{}", workerId, dataCenterId);
}
@Override
public String getUID(String preFix) throws UidGenerateException {
try {
return nextId(preFix);
} catch (Exception e) {
LOGGER.error("Generate unique id exception. ", e);
throw new UidGenerateException(e);
}
}
// TODO: 2018/3/5 反序列化 uid
@Override
public String parseUID(String uidStr) {
BigInteger bigInteger = new BigInteger(uidStr);
int totalBits = bigInteger.bitLength();
long dataCenterIdBits = bitsAllocator.getDataCenterIdBits();
long workerIdBits = bitsAllocator.getWorkerIdBits();
long sequenceBits = bitsAllocator.getSequenceBits();
if (totalBits < 64) {
totalBits = 64;
long uid = bigInteger.longValue();
long sequence = (uid << (totalBits - sequenceBits)) >>> (totalBits - sequenceBits);
long workerId = (uid << (totalBits - workerIdBits - sequenceBits)) >>> (totalBits - workerIdBits);
long dataCenterId = (uid << (totalBits - dataCenterIdBits - workerIdBits - sequenceBits)) >>> (totalBits - dataCenterIdBits);
long deltaSeconds = uid >>> (dataCenterIdBits + workerIdBits + sequenceBits);
Date thatTime = new Date(TimeUnit.SECONDS.toMillis(epochSeconds + deltaSeconds));
String thatTimeStr = DateFormatUtils.format(thatTime, DATETIME_PATTERN);
return String.format("{\"UID\":\"%d\",\"timestamp\":\"%s\",\"dataCenterId\":\"%d\",\"workerId\":\"%d\",\"sequence\":\"%d\"}",
uid, thatTimeStr, dataCenterId, workerId, sequence);
} else {
BigInteger workerBig = getBigIntegerFromLength(workerIdBits).shiftLeft((int) sequenceBits).and(bigInteger);
System.out.println(workerBig);
long sequence = getBigIntegerFromLength(sequenceBits).and(bigInteger).longValue();
long workerId = getBigIntegerFromLength(workerIdBits).and(bigInteger.shiftRight((int)sequenceBits)).longValue();
long dataCenterId = getBigIntegerFromLength(dataCenterIdBits).and(bigInteger.shiftRight((int)sequenceBits+(int)workerIdBits)).longValue();
long deltaSeconds = bigInteger.shiftRight((int) dataCenterIdBits + (int) workerIdBits + (int) sequenceBits).longValue();
Date thatTime = new Date(TimeUnit.SECONDS.toMillis(epochSeconds + deltaSeconds));
String thatTimeStr = DateFormatUtils.format(thatTime, DATETIME_PATTERN);
return String.format("{\"UID\":\"%d\",\"timestamp\":\"%s\",\"dataCenterId\":\"%d\",\"workerId\":\"%d\",\"sequence\":\"%d\"}",
bigInteger, thatTimeStr, dataCenterId, workerId, sequence);
}
}
private BigInteger getBigIntegerFromLength(long n) {
return BigInteger.valueOf(-1).shiftLeft((int) n).not();
}
/**
* Get UID
*
* @return UID
* @throws UidGenerateException in the case: Clock moved backwards; Exceeds the max timestamp
*/
protected synchronized String nextId(String preFix) {
long currentSecond = getCurrentSecond();
// Clock moved backwards, wait for newest time
if (currentSecond < lastSecond) {
getNextSecond(lastSecond);
}
// At the same second, increase sequence
if (currentSecond == lastSecond) {
sequence = (sequence + 1) & bitsAllocator.getMaxSequence();
// Exceed the max sequence, we wait the next second to generate uid
if (sequence == 0) {
currentSecond = getNextSecond(lastSecond);
}
// At the different second, sequence restart from zero
} else {
sequence = 0L;
}
lastSecond = currentSecond;
// 当前时间小于设定的最大时间,即总位数在 64 位以下,用 long 生成数字
if (currentSecond - epochSeconds <= bitsAllocator.getMaxDeltaSeconds()) {
return preFix + bitsAllocator.allocate(currentSecond - epochSeconds, dataCenterId, workerId, sequence);
}
return preFix + bitsAllocator.allocateBigInteger(currentSecond - epochSeconds, dataCenterId, workerId, sequence);
}
/**
* Get next millisecond
*/
private long getNextSecond(long lastTimestamp) {
long timestamp = getCurrentSecond();
while (timestamp <= lastTimestamp) {
timestamp = getCurrentSecond();
}
return timestamp;
}
/**
* Get current second
*/
private long getCurrentSecond() {
return TimeUnit.MILLISECONDS.toSeconds(System.currentTimeMillis());
}
public void setTimeBits(int timeBits) {
if (timeBits > 0) {
this.timeBits = timeBits;
}
}
public void setWorkerBits(int workerBits) {
if (workerBits > 0) {
this.workerBits = workerBits;
}
}
public void setSeqBits(int seqBits) {
if (seqBits > 0) {
this.seqBits = seqBits;
}
}
@Value("${uid.epochStr:2018-03-01}")
public void setEpochStr(String epochStr) {
if (StringUtils.isNotBlank(epochStr)) {
this.epochStr = epochStr;
try {
this.epochSeconds = TimeUnit.MILLISECONDS.toSeconds(DateUtils.parseDate(epochStr, new String[]{DAY_PATTERN}).getTime());
} catch (ParseException e) {
e.printStackTrace();
}
}
}
}
/*
* Copyright (c) 2017 Baidu, Inc. All Rights Reserve.
*
* 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 cn.quantgroup.tech.util.id;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
@Component
public class DisposableWorkerIdAssigner {
private static final Logger LOGGER = LoggerFactory.getLogger(DisposableWorkerIdAssigner.class);
private static final String REDIS_WORK_ID_KEY = "GLOBAL:WORK:ID:";
@Autowired
private StringRedisTemplate redisTemplate;
/**
* Assign worker id base on database.<p>
* If there is host name & port in the environment, we considered that the node runs in Docker container<br>
* Otherwise, the node runs on an actual machine.
*
* @param dataCenterId
* @param bitsAllocator
* @return assigned worker id
*/
public long assignWorkerId(long dataCenterId, BitsAllocator bitsAllocator) {
return redisTemplate.opsForValue().increment(REDIS_WORK_ID_KEY + dataCenterId, 1) % bitsAllocator.getMaxWorkerId();
}
}
/*
* Copyright (c) 2017 Baidu, Inc. All Rights Reserve.
*
* 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 cn.quantgroup.tech.util.id;
/**
* UidGenerateException
*
* @author yutianbao
*/
public class UidGenerateException extends RuntimeException {
/**
* Serial Version UID
*/
private static final long serialVersionUID = -27048199131316992L;
/**
* Default constructor
*/
public UidGenerateException() {
super();
}
/**
* Constructor with message & cause
*
* @param message
* @param cause
*/
public UidGenerateException(String message, Throwable cause) {
super(message, cause);
}
/**
* Constructor with message
*
* @param message
*/
public UidGenerateException(String message) {
super(message);
}
/**
* Constructor with message format
*
* @param msgFormat
* @param args
*/
public UidGenerateException(String msgFormat, Object... args) {
super(String.format(msgFormat, args));
}
/**
* Constructor with cause
*
* @param cause
*/
public UidGenerateException(Throwable cause) {
super(cause);
}
}
package cn.quantgroup.tech.util.id;
/**
* Represents a unique id generator.
*
* @author yutianbao
*/
public interface UidGenerator {
/**
* Get a unique ID
*
* @return UID
* @throws UidGenerateException
*/
String getUID(String preFix) throws UidGenerateException;
/**
* Parse the UID into elements which are used to generate the UID. <br>
* Such as timestamp & workerId & sequence...
*
* @param uid
* @return Parsed info
*/
String parseUID(String uid);
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment