高并发下订单号生成应满足的要求:

  1. 唯一性。
  2. 可排序性。
  3. 分布式支持

提供的方案大致可以分为两类

  1. UUID类型
  2. 字符串拼接类型

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.字符串拼接类型

其中最典型的就是twittersnowflake算法了,其长度为一个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方法

KAI Java