【黄啊码】百万级别订单量,如何生成唯一订单ID(雪花算法)

606 阅读3分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。 Twitter-SnowFlake算法的产生是源于Twitter为了满足自己业务(每秒上万条消息的请求,每条消息都必须分配一条唯一的id,并且在分布式系统中不同机器产生的id必须不同)的需求。

snowflake的结构如下(每部分用-分开):

0 - 0000000000 0000000000 0000000000 0000000000 0 - 00000 - 00000 - 000000000000 第一位为未使用,接下来的41位为毫秒级时间(41位的长度可以使用69年),然后是5位datacenterId和5位workerId(10位的长度最多支持部署1024个节点) ,最后12位是毫秒内的计数(12位的计数顺序号支持每个节点每毫秒产生4096个ID序号) 一共加起来刚好64位,为一个Long型。(转换成字符串后长度最多19) snowflake生成的ID整体上按照时间自增排序,并且整个分布式系统内不会产生ID碰撞(由datacenter和workerId作区分),并且效率较高。经测试snowflake每秒能够产生26万个ID。

snowflake ID 生成策略图解

image.png

/**

  • ID 生成策略
  • 毫秒级时间41位+机器ID, 10位+毫秒内序列12位。高位始终为0,表示正数。
  • 0 41 51 64
  • +-----------+------+------+
  • |time |pc |inc |
  • +-----------+------+------+
  • 前41bits是以微秒为单位的timestamp。
  • 接着10bits是事先配置好的机器ID。
  • 最后12bits是累加计数器。
  • macheine id(10bits)标明最多只能有1024台机器同时产生ID,sequence number(12bits)也标明1台机器1ms中最多产生4096个ID,

*/ PHP代码

class IdWork
{
    private static $workerId;
    private static $maxWorkerId = 1023; //最大的机器节点, 2^10 - 1
 
    private static $sequence = 0;
    private static $sequenceMask = 4095; //最大的序列节点, 2^12 - 1
 
    private static $workerIdShift = 12; //机器ID左移位数,63 - 51
    private static $timestampLeftShift = 22; //毫秒时间戳左移位数,63 - 41
 
    private static $twepoch = 1288834974657;
    private static $lastTimestamp = -1;
 
    /**
     * @var self
     */
    private static $self = null;
 
    /**
     * @return self
     */
    public static function getInstance()
    {
        if (self::$self == null) {
            self::$self = new self();
        }
        return self::$self;
    }
 
    public function setWorkId($workId)
    {
        if ($workId > self::$maxWorkerId || $workId < 0) {
            throw new \Exception("worker Id can't be greater than ".self::$maxWorkerId." or less than 0");
        }
        self::$workerId = $workId;
        return self::$self;
    }
 
    private function timeGen()
    {
        //获得当前时间戳
        $time = explode(' ', microtime());
        $time2 = substr($time[0], 2, 3);
        return $time[1] . $time2;
    }
 
    private function tilNextMillis($lastTimestamp)
    {
        $timestamp = $this->timeGen();
        while ($timestamp <= $lastTimestamp) {
            $timestamp = $this->timeGen();
        }
        return $timestamp;
    }
 
    public function nextId()
    {
        $timestamp = $this->timeGen();
 
        //如果存在并发调用,则自增sequence
        if (self::$lastTimestamp == $timestamp) {
            self::$sequence = (self::$sequence + 1) & self::$sequenceMask;
 
            //如果sequence自增到4095,也就是4096 & 4095 = 0,重新取时间戳
            if (self::$sequence == 0) {
                $timestamp = $this->tilNextMillis(self::$lastTimestamp);
            }
        } else {
            self::$sequence = 0;
        }
 
        if ($timestamp < self::$lastTimestamp) {
            throw new \Exception("Clock moved backwards.  Refusing to generate id for " . (self::$lastTimestamp - $timestamp) . " milliseconds");
        }
 
        self::$lastTimestamp = $timestamp;
 
        if (PHP_INT_SIZE === 4) {
            return 0;
        }
 
        $nextId = ((sprintf('%.0f', $timestamp) - sprintf(
            '%.0f',
            self::$twepoch
        )) << self::$timestampLeftShift) | (self::$workerId << self::$workerIdShift) | self::$sequence;
 
        return $nextId;
    }
}
 
//用法:
$order_no = IdWork::getInstance()->setWorkId($wordId)->nextId();

Java代码

/**
 **/
public class SnowFlake {
​
    /**
     * 起始时间戳,从2021-12-01开始生成
     */
    private final static long START_STAMP = 1638288000000L;
​
    /**
     * 序列号占用的位数 12
     */
    private final static long SEQUENCE_BIT = 12;
​
    /**
     * 机器标识占用的位数
     */
    private final static long MACHINE_BIT = 10;
​
    /**
     * 机器数量最大值
     */
    private final static long MAX_MACHINE_NUM = ~(-1L << MACHINE_BIT);
​
    /**
     * 序列号最大值
     */
    private final static long MAX_SEQUENCE = ~(-1L << SEQUENCE_BIT);
​
    /**
     * 每一部分向左的位移
     */
    private final static long MACHINE_LEFT = SEQUENCE_BIT;
    private final static long TIMESTAMP_LEFT = SEQUENCE_BIT + MACHINE_BIT;
​
    /**
     * 机器标识
     */
    private long machineId;
    /**
     * 序列号
     */
    private long sequence = 0L;
    /**
     * 上一次时间戳
     */
    private long lastStamp = -1L;
​
    /**
     * 构造方法
     * @param machineId 机器ID
     */
    public SnowFlake(long machineId) {
        if (machineId > MAX_MACHINE_NUM || machineId < 0) {
            throw new RuntimeException("机器超过最大数量");
        }
        this.machineId = machineId;
    }
​
    /**
     * 产生下一个ID
     */
    public synchronized long nextId() {
        long currStamp = getNewStamp();
        if (currStamp < lastStamp) {
            throw new RuntimeException("时钟后移,拒绝生成ID!");
        }
​
        if (currStamp == lastStamp) {
            // 相同毫秒内,序列号自增
            sequence = (sequence + 1) & MAX_SEQUENCE;
            // 同一毫秒的序列数已经达到最大
            if (sequence == 0L) {
                currStamp = getNextMill();
            }
        } else {
            // 不同毫秒内,序列号置为0
            sequence = 0L;
        }
​
        lastStamp = currStamp;
​
        return (currStamp - START_STAMP) << TIMESTAMP_LEFT // 时间戳部分
                | machineId << MACHINE_LEFT             // 机器标识部分
                | sequence;                             // 序列号部分
    }
​
    private long getNextMill() {
        long mill = getNewStamp();
        while (mill <= lastStamp) {
            mill = getNewStamp();
        }
        return mill;
    }
​
    private long getNewStamp() {
        return System.currentTimeMillis();
    }
​
    public static void main(String[] args) {
        // 订单ID生成测试,机器ID指定第0台
        SnowFlake snowFlake = new SnowFlake(0);
        System.out.println(snowFlake.nextId());
    }
}

大厂也有雪花算法的案例,并且开源,比如百度、滴滴 github.com/baidu/uid-g… github.com/didi/tinyid

如果再不懂,可以参考这两篇文章,有关于雪花算法的详解和优化以及时间回拨问题的解决方案 雪花算法中超级好用的ID生成工具 - 知乎 zhuanlan.zhihu.com/p/407498038