Twitter雪花算法SnowFlake改造:兼容JS的53bit分布式ID生成器

作者:狼烟四起2024.03.22 21:18浏览量:6

简介:针对JavaScript数字表示限制,改造Twitter的雪花算法SnowFlake,实现一个兼容JS截短位数的53bit分布式ID生成器,确保在JS环境中生成的ID不会溢出,并保持全局唯一性。

在分布式系统中,生成全局唯一的ID是一个常见的需求。Twitter的雪花算法(SnowFlake)是一个广受欢迎的解决方案,它通过组合时间戳、机器标识和序列号来生成64bit的ID。然而,在JavaScript环境中,由于数字的最大安全整数限制(Number.MAX_SAFE_INTEGER,即2^53-1),64bit的ID可能会导致溢出问题。因此,我们需要对SnowFlake算法进行改造,使其兼容JS的53bit表示限制。

首先,我们需要了解SnowFlake算法的基本结构。一个64bit的SnowFlake ID通常包含以下几个部分:

  • 1bit符号位(未使用,始终为0)
  • 41bit时间戳(毫秒级)
  • 10bit机器标识(数据中心ID+工作节点ID)
  • 12bit序列号(毫秒内的计数)

为了兼容JS的53bit限制,我们需要减少ID的位数。一种简单的方法是将时间戳部分从41bit减少到38bit,这样整个ID就变为53bit,完全可以在JS中安全表示。但是,这样做会限制ID的时间范围,因此需要谨慎考虑。

改造后的算法结构如下:

  • 1bit符号位(未使用,始终为0)
  • 38bit时间戳(毫秒级,可表示约69年)
  • 10bit机器标识(数据中心ID+工作节点ID)
  • 4bit序列号(毫秒内的计数,限制为16个)

下面是一个简单的JavaScript实现示例:

```javascript
class SnowFlake {
constructor(datacenterId, workerId) {
if (datacenterId > 31 || datacenterId < 0) {
throw new Error(‘Datacenter ID must be between 0 and 31’);
}
if (workerId > 31 || workerId < 0) {
throw new Error(‘Worker ID must be between 0 and 31’);
}
this.twepoch = 1288834974657n; // 起始时间戳(毫秒级),可根据需求调整
this.datacenterIdBits = 5n;
this.workerIdBits = 5n;
this.maxDatacenterId = -1n ^ (-1n << this.datacenterIdBits);
this.maxWorkerId = -1n ^ (-1n << this.workerIdBits);
this.sequenceBits = 4n;
this.sequenceMask = -1n ^ (-1n << this.sequenceBits);
this.timestampLeftShift = this.sequenceBits + this.workerIdBits + this.datacenterIdBits;
this.datacenterIdShift = this.sequenceBits + this.workerIdBits;
this.workerIdShift = this.sequenceBits;
this.timestamp = 0n;
this.datacenterId = datacenterId;
this.workerId = workerId;
this.sequence = 0n;
this.lastTimestamp = -1n;
}

_timeGen(): bigint {
return BigInt(Date.now());
}

_tillNextMillis(lastTimestamp: bigint): bigint {
let timestamp = this._timeGen();
while (timestamp <= lastTimestamp) {
timestamp = this._timeGen();
}
return timestamp;
}

_nextId(): bigint {
let timestamp = this._timeGen();

  1. if (this.lastTimestamp === timestamp) {
  2. this.sequence = (this.sequence + 1n) & this.sequenceMask;
  3. if (this.sequence === 0n) {
  4. timestamp = this._tillNextMillis(this.lastTimestamp);
  5. }
  6. } else {
  7. this.sequence = 0n;
  8. }
  9. if (timestamp < this.lastTimestamp) {
  10. throw new Error('Clock moved backwards');
  11. }
  12. this.lastTimestamp = timestamp;
  13. return ((timestamp - this.twepoch) << this.timestampLeftShift) |
  14. (this.