使用SnowFlake的理由
按照时间自增,可排序。
并且整个分布式系统内不会产生ID碰撞(由数据中心ID和机器ID作区分)。
经测试 MacBook Pro (15-inch, 2018) 每秒可产生136万左右的ID。
twitter开源的地址:twitter-archive/snowflake
SnowFlake的结构如下(共64bits,每部分用-分开):
0 - 0000000000 0000000000 0000000000 0000000000 0 - 00000 - 00000 - 000000000000
| ———————-|———————- –|– –|– —–|——
1bit不用 41bit 时间戳 数据标识id 机器id 序列号id
- 1位标识,二进制中最高位为1的都是负数,但是我们生成的id一般都使用整数,所以这个最高位固定是0
- 41位时间戳,41位时间截不是存储当前时间的时间截,而是存储时间截的差值(当前时间截 - 开始时间截得到的值),这里的的开始时间截,一般是我们的id生成器开始使用的时间,由我们程序来指定的(如下下面程序IdWorker类的startTime属性)。41位的时间截,可以使用69年,年T = (1L << 41) / (1000L * 60 * 60 * 24 * 365) = 69
- 10位的数据机器位,可以部署在1024个节点,包括5位dataCenterId和5位workerId
- 12位序列,毫秒内的计数,12位的计数顺序号支持每个节点每毫秒(同一机器,同一时间截)产生4096个ID序号
- 加起来刚好64位,为一个Long型。
实现
思路还是很简单的,直接写结果+注释吧。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110
| class SnowFlake {
constructor(workerId, dataCenterId) { this.startTime = BigInt(new Date().getTime()); this.workerIdBits = 5n; this.dataCenterIdBits = 5n; this.sequenceBits = 12n;
this.maxWorkerId = -1n ^ (-1n << this.workerIdBits); this.maxDataCenterId = -1n ^ (-1n << this.dataCenterIdBits); this.sequenceMask = -1n ^ (-1n << this.sequenceBits);
this.workerIdShift = this.sequenceBits; this.dataCenterIdShift = this.sequenceBits + this.workerIdBits; this.timestampLeftShift = this.dataCenterIdShift + this.dataCenterIdBits;
this.sequence = 0n;
this.lastTimestamp = -1n;
const { maxWorkerId, maxDataCenterId } = this; if (workerId > maxWorkerId || workerId < 0n) { throw new Error( `workerId 不能大于 ${maxWorkerId} 或小于 0` ); } if (dataCenterId > maxDataCenterId || dataCenterId < 0n) { throw new Error( `dataCenterId 不能大于 ${maxDataCenterId} 或小于 0` ); } this.workerId = workerId; this.dataCenterId = dataCenterId; return this; }
nextId() { let timestamp = this.timeGen(); const diff = timestamp - this.lastTimestamp; if (diff < 0n) { throw new Error( `出现时钟回拨。拒绝生成 ${-diff} 毫秒的ID` ); }
if (diff === 0n) { this.sequence = (this.sequence + 1n) & this.sequenceMask; if (this.sequence === 0n) { timestamp = this.tilNextMillis(this.lastTimestamp); } } else { this.sequence = 0n; }
this.lastTimestamp = timestamp;
return ( ((timestamp - this.startTime) << this.timestampLeftShift) | (this.dataCenterId << this.dataCenterIdShift) | (this.workerId << this.workerIdShift) | this.sequence ); }
tilNextMS(lastTimestamp) { let timestamp = this.timeGen(); while (timestamp <= lastTimestamp) { timestamp = this.timeGen(); } return timestamp; }
timeGen() { return BigInt(+new Date()); } }
|
测试一下
1 2 3 4 5 6 7 8 9
| (function () { console.time('id'); const idWorker = new SnowFlake(1n, 1n); const tempArr = []; for (let i = 0; i < 100000; i++) { tempArr.tempArr(idWorker.nextId()); } console.timeEnd('id'); })()
|