高并发下订单号生成应满足的要求:
- 唯一性。
- 可排序性。
- 分布式支持
提供的方案大致可以分为两类
- UUID类型
- 字符串拼接类型
1.UUID
这里用的是Java的UUID类(java.util.UUID)
生成的格式为:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
UUID生成算法用到了日期时间、时钟序列以及硬件地址
使用方法:
public class JavaUUID {
private JavaUUID(){};
private final static Character emptyChar = null;
public static String getUUID(){
UUID u = UUID.randomUUID();
return u.toString().replace('-',emptyChar);
}
}
2.字符串拼接类型
其中最典型的就是twitter的snowflake算法了,其长度为一个long(8字节64位)如1073564473528561651,以下为各个位的使用
- 1位,符号位,默认为0。但是我们生成的id一般都使用正数。
-
41位,用来记录时间戳(毫秒)。
41位可以表示241−1个数字,如果只用来表示正整数(计算机中正数包含0),可以表示的数值范围是:0 至 241−1,减1是因为可表示的数值范围是从0开始算的,而不是1。也就是说41位可以表示241−1个毫秒的值, 转化成单位年则是(241−1)/(1000∗60∗60∗24∗365)=69年 -
10位,用来记录工作机器id和数据中心id。可以部署在210=1024个节点,包括5位datacenterId和5位workerId
-
5位(bit) 可以表示的最大正整数是25−1=31,即可以用0、1、2、3、….31这32个数字,来表示不同的datecenterId或workerId
-
12位,Sequence序列号,用来记录同毫秒内产生的不同id。12位(bit)可以表示的最大正整数是212−1=4095,即可以用0、1、2、3、….4094这4095个数字,来表示同一机器同一时间截(毫秒)内产生的4095个ID序号
public class IdWorker {
private final long twepoch = 1288834974657L;
private final long workerIdBits = 5L;
private final long datacenterIdBits = 5L;
private final long maxWorkerId = ~(-1L << workerIdBits);
private final long maxDatacenterId = ~(-1L << datacenterIdBits);
private final long sequenceBits = 12L;
private final long workerIdShift = sequenceBits;
private final long datacenterIdShift = sequenceBits + workerIdBits;
private final long timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits;
private final long sequenceMask = ~(-1L << sequenceBits);
private long workerId;
private long datacenterId;
private long sequence = 0L;
private long lastTimestamp = -1L;
public IdWorker(long workerId,long datacenterId,long sequence){
//worker过多 2^5=32 即[0,31]
if(workerId>maxWorkerId){
throw new IllegalArgumentException(String.format("worker Id can't be greater than %d or less than 0", maxWorkerId));
}
//datacenter过多 2^5=32 即[0,31]
if (datacenterId > maxDatacenterId || datacenterId < 0) {
throw new IllegalArgumentException(String.format("datacenter Id can't be greater than %d or less than 0", maxDatacenterId));
}
this.workerId = workerId;
this.datacenterId = datacenterId;
//原scala代码支持默认参数,java不支持所以用null吧
this.sequence = sequence;
}
public synchronized long nextId() {
long timestamp = timeGen();
if (timestamp < lastTimestamp) {
//时间戳小于当前时间戳,服务器事间发生回滚
throw new RuntimeException(String.format("Clock moved backwards.Refusing to generate id for %d milliseconds", lastTimestamp - timestamp));
}
//用于最后的sequence增长以及毫米更替的sequence重置
if (lastTimestamp == timestamp) {
sequence = (sequence + 1) & sequenceMask;
if (sequence == 0) {
//使同时进入nextId返回的订单时间戳与完成事件更加接近
timestamp = tilNextMillis(lastTimestamp);
}
} else {
sequence = 0L;
}
lastTimestamp = timestamp;
return ((timestamp - twepoch) << timestampLeftShift) | (datacenterId << datacenterIdShift) | (workerId << workerIdShift) | sequence;
}
private long tilNextMillis(long lastTimestamp) {
long timestamp = timeGen();
while (timestamp <= lastTimestamp) {
timestamp = timeGen();
}
return timestamp;
}
private long timeGen() {
return System.currentTimeMillis();
}
}
其余的类似支付宝流水单号应该也是类似的基于时间+机器号+累加号。除了内存实现也可以使用数据库,例如普通数据库的递增属性,或者Redis的indr方法