JavaScript生成有序GUID或者UUID,這時就想到了雪花算法。
原理介紹:
snowFlake算法最終生成ID的結果為一個64bit大小的整數,結構如下圖:
解釋:
- 1bit。二進制中最高位為1表示負數,但是我們最終生成的ID一般都是整數,所以這個最高位固定為0。
- 41bit。用於記錄時間戳(毫秒)
- 41bit可以表示241-1個數字
- 如果只用來表示正整數(計算機中正數包含0),可以表示的數值范圍是0到241-1,減1是因為可表示的數值范圍從0開始計算,而不是1.
- 即41bit可以表示241-1個毫秒值轉換為年為(241 - 1) / (1000 * 60 * 60 * 24 * 365) = 69.73年
- 10bit。用於記錄機器ID
- 可以用於部署210=1024個節點,包含5bit 的 datacenterId 和5bit 的workerId
- 5bit可以表示的最大正整數為25-1=31 即可以用0、1、2、3....31這32個數字來表示不同的datacenterId 和 workerId
- 12bit。序列號用於記錄相同毫秒內產生的不同ID
- 12bit可以表示的最大正整數為212-1 = 4095,可以用0、1、2、3...4094這4095個數字來表示同一機器同一時間戳(毫秒)內產生的4095個ID序號
snowFlake算法可以保證:所有生成的ID按時間趨勢遞增;整個分布式系統內不會產生重復ID,由於5bit 的 datacenterId 和5bit 的workerId來區分。
算法代碼實現原理解釋:
計算機中負數的二進制是用補碼來表示的。
假設使用int類型來進行存儲數字,int類型的大小是32bit二進制位,4個byte。(1byte = 8bit)
那么十進制中的3在二進制中的表示應該是:
00000000 00000000 00000000 00000011 // 3的二進制原碼
那么數字 -3 在二進制中的表示應該是怎樣的?試想: -3 + 3 = 0 在二進制運算中把 -3 的二進制看成未知數X來求解。
00000000 00000000 00000000 00000011 // 3 原碼
+ xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx // -3 補碼
------------------------------------------------------
00000000 00000000 00000000 00000000
反推X 即 二進制數從最低位開始逐位加1,使溢出的1不斷向高位溢出,直到溢出到第33位,然后由於int類型最多只能保存32位二進制位,所以最高位的1溢出,剩余32位就成了0.
則:
00000000 00000000 00000000 00000011 // 3 原碼
+ 11111111 11111111 11111111 11111101 // -3 補碼
---------------------------------------------------------
1 00000000 00000000 00000000 00000000
總結公式:
- 補碼 = 反碼 + 1
- 補碼 = (原碼 - 1) 再取反碼
workerIdBits = 5L;
maxWorkerId = -1L ^ (-1L << workerIdBits);
-1左移5位高位溢出的舍去后得到a,a與-1異或運算得到最終結果。
11111111 11111111 11111111 11111111 // -1補碼
11111 11111111 11111111 11111111 11100000
---------------------------------------------------------------------
11111111 11111111 11111111 11100000 // 高位溢出舍棄
11111111 11111111 11111111 11111111 // -1補碼
^ 11111111 11111111 11111111 11100000
---------------------------------------------------------------------
00000000 00000000 00000000 00011111
24+23+22+21+20 = 16+8+4+2+1 = 31
-1L ^ (-1L << 5L) = 31 也就是 25-1 = 31, 該寫法是利用位運算計算出5位能表示的最大正整數是多少。
用掩碼mask防止溢出
seq = (seq + 1) & seqMask
這段代碼通過按位與運算保證計算的結果范圍始終是0 - 4095.
按位運算結果:
return ((timestamp - twepoch) << timestampLeftShift) | (datacnterId << datacenterIdShift) | (workerId << workerIdShift) | sequence;
解析:
var twepoch = 1571192786565; // 起始時間戳 用於當前時間戳減去這個時間戳得到偏移量
var workerIdBits = 5; // workId占用的位數5
var datacenterIdBit = 5;// datacenterId占用的位數5
var maxWorkerId = -1 ^ (-1 << workerIdBits); // workId可以使用的最大數值31 var maxDatacenterId = -1 ^ (-1 << datacenterIdBits); // datacenterId可以使用的最大數值31
var sequenceBit = 12;// 序列號占用的位數12
workerIdShift = sequenceBits; // 12
datacenterIdShift = sequenceBits + workerIdBits; // 12+5 = 17
timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits; // 12+5+5 = 22
sequenceMask = -1 ^ (-1 << sequenceBits); // 4095
lastTimestamp = -1;
JavaScript中Number的最大值為Number.MAX_SAFE_INTEGER:9007199254740991。在雪花算法中,有的操作在JS中會溢出,所以選用BigInt實現雪花算法。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Snowflake</title>
</head>
<body>
<script>
var Snowflake = (function() { function Snowflake(_workerId, _dataCenterId, _sequence) { this.twepoch = 1288834974657n; //this.twepoch = 0n;
this.workerIdBits = 5n; this.dataCenterIdBits = 5n; this.maxWrokerId = -1n ^ (-1n << this.workerIdBits); // 值為:31
this.maxDataCenterId = -1n ^ (-1n << this.dataCenterIdBits); // 值為:31
this.sequenceBits = 12n; this.workerIdShift = this.sequenceBits; // 值為:12
this.dataCenterIdShift = this.sequenceBits + this.workerIdBits; // 值為:17
this.timestampLeftShift = this.sequenceBits + this.workerIdBits + this.dataCenterIdBits; // 值為:22
this.sequenceMask = -1n ^ (-1n << this.sequenceBits); // 值為:4095
this.lastTimestamp = -1n; //設置默認值,從環境變量取
this.workerId = 1n; this.dataCenterId = 1n; this.sequence = 0n; if (this.workerId > this.maxWrokerId || this.workerId < 0) { throw new Error('_workerId must max than 0 and small than maxWrokerId-[' + this.maxWrokerId + ']'); } if (this.dataCenterId > this.maxDataCenterId || this.dataCenterId < 0) { throw new Error('_dataCenterId must max than 0 and small than maxDataCenterId-[' + this.maxDataCenterId + ']'); } this.workerId = BigInt(_workerId); this.dataCenterId = BigInt(_dataCenterId); this.sequence = BigInt(_sequence); } Snowflake.prototype.tilNextMillis = function(lastTimestamp) { var timestamp = this.timeGen(); while (timestamp <= lastTimestamp) { timestamp = this.timeGen(); } return BigInt(timestamp); }; Snowflake.prototype.timeGen = function() { return BigInt(Date.now()); }; Snowflake.prototype.nextId = function() { var timestamp = this.timeGen(); if (timestamp < this.lastTimestamp) { throw new Error('Clock moved backwards. Refusing to generate id for ' + (this.lastTimestamp - timestamp)); } if (this.lastTimestamp === timestamp) { 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.twepoch) << this.timestampLeftShift) | (this.dataCenterId << this.dataCenterIdShift) | (this.workerId << this.workerIdShift) |
this.sequence; }; return Snowflake; }()); console.time(); var tempSnowflake = new Snowflake(1n, 1n, 0n); var tempIds = []; for (var i = 0; i < 10000; i++) { var tempId = tempSnowflake.nextId(); console.log(tempId); if (tempIds.indexOf(tempId) < 0) { tempIds.push(tempId); } } console.log(tempIds.length); console.timeEnd(); </script>
</body>
</html>