xy-server/game/gear/SnowFlake53.ts
2025-04-23 09:34:08 +08:00

141 lines
6.3 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import moment, { months } from "moment";
import SKLogger from "./SKLogger";
/**
* SnowFlake 53位雪花算法
*
* SnowFlake的结构如下(共53bits每部分用-分开):
* 1bit不用 31bit 时间戳 5bit 数据标识id 16bit 序列号id
*
* - 1位标识二进制中最高位为1的都是负数但是我们生成的id一般都使用整数所以这个最高位固定是0
* - 31位时间截(秒级)注意31位时间截不是存储当前时间的时间截而是存储时间截的差值当前时间截 - 开始时间截得到的值这里的的开始时间截一般是我们的id生成器开始使用的时间由我们程序来指定的如下下面程序IdWorker类的startTime属性。41位的时间截可以使用69年年T = (1L << 41) / (1000L * 60 * 60 * 24 * 365) = 69
* - 5位的数据机器位可以部署在32个节点5位workerId
* - 16位序列毫秒内的计数16位的计数顺序号支持每个节点每毫秒(同一机器,同一时间截)产生65535个ID序号
* - 加起来刚好53位为一个Long型。
* SnowFlake的优点是
* - 整体上按照时间自增排序
* - 并且整个分布式系统内不会产生ID碰撞(由数据中心ID和机器ID作区分)
* - 并且效率较高经测试SnowFlake每秒能够产生26万ID左右。
*/
export default class SnowFlack53 {
// 开始时间截 (2021-01-01 12:00:00),这个可以设置开始使用该系统的时间 1609473600
private readonly twepoch: bigint = 1609473600n;
// 位数划分 [数据标识id(5bit 31)、机器id(5bit 31)](合计共支持1024个节点)、序列id(12bit 4095)
private readonly workerIdBits: bigint = 5n; // 标识Id
private readonly sequenceBits: bigint = 16n; // 序列Id
// 支持的最大十进制id
// 这个移位算法可以很快的计算出几位二进制数所能表示的最大十进制数
// -1 左移5位后与 -1 异或
private readonly maxWorkerId: bigint = -1n ^ (-1n << this.workerIdBits);
// 生成序列的掩码这里为65535
private readonly sequenceMask: bigint = -1n ^ (-1n << this.sequenceBits);
// 机器ID向左移16位 数据标识id向左移17位(12+5) 时间截向左移21位(5+16)
private readonly workerIdShift: bigint = this.sequenceBits;
private readonly dataCenterIdShift: bigint = this.sequenceBits + this.workerIdBits;
private readonly timestampLeftShift: bigint = this.dataCenterIdShift;
// 工作机器ID(0~31) 毫秒内序列(0~65535)
private sequence: bigint = 0n;
// 上次生成ID的时间截这个是在内存中系统时钟回退+重启后呢)
private lastTimestamp: bigint = -1n;
private readonly workerId: bigint;
private readonly dataCenterId: any;
/**
* 构造函数
* 运行在内阁
* @param {bigint} workerId 工作ID (0~31)
* @param {bigint} sequence 毫秒内序列 (0~65535)
*/
constructor(workerId: bigint) {
if (workerId > this.maxWorkerId || workerId < 0n) {
throw new Error(
`机器ID不能大于${this.maxWorkerId}或小于0!`,
)
}
this.workerId = workerId;
return this;
}
/**
* 获得下一个ID (该方法是线程安全的)
*
* @returns {number} SnowflakeId 返回53位id
*/
public nextId(): number {
let timestamp = this.timeGen();
// 如果当前时间小于上一次ID生成的时间戳说明系统时钟回退过这个时候应当抛出异常
const diff = timestamp - this.lastTimestamp;
if (diff < 0n) {
throw new Error(
`Clock moved backwards. Refusing to generate id for ${-diff} milliseconds`
);
}
// 如果是同一时间生成的,则进行毫秒内序列
if (diff === 0n) {
this.sequence = (this.sequence + 1n) & this.sequenceMask;
// 毫秒内序列溢出
if (this.sequence === 0n) {
// 阻塞到下一个,获得新的时间戳
timestamp = this.tilNextMillis(this.lastTimestamp);
}
} else {
// 时间戳改变,毫秒内序列重置
this.sequence = 0n;
}
// 保存上次生成ID的时间截
this.lastTimestamp = timestamp;
// 移位并通过或运算拼到一起组成64位的ID
// 将各 bits 位数据移位后或运算合成一个大的64位二进制数据
let nextId: BigInt = (
((timestamp - this.twepoch) << this.timestampLeftShift) | // 时间数据左移21
(this.workerId << this.workerIdShift) | // 机器id左移 16
this.sequence
);
let result: number = parseInt(nextId.toString());
if (Number.isSafeInteger(result)) {
return result;
} else {
let info = `分布式ID超出JS整数安全范围`;
SKLogger.warn(info);
throw new Error(info);
}
}
/**
* 阻塞到下一个秒,直到获得新的时间戳
* @param {bigint} lastTimestamp 上次生成ID的时间截
* @return {bigint} 当前时间戳
*/
private tilNextMillis(lastTimestamp: bigint): bigint {
let timestamp = this.timeGen();
while (timestamp <= lastTimestamp) {
timestamp = this.timeGen();
}
return timestamp;
}
/**
* 返回以秒为单位的当前时间
* @return {bigint} 当前时间(秒)
*/
private timeGen(): bigint {
let result = BigInt(Math.floor(new Date().valueOf() / 1000));
return result;
}
// 获得分步式自增ID日期
public getDate(id: bigint): string {
let timestamp = ((id >> this.timestampLeftShift) + this.twepoch) * 1000n;
let temp = parseInt(timestamp.toString())
let result = moment(temp).format("YYYY-MM-DD hh:mm:ss");
return result;
}
// 获得分步式自增ID机器ID
public getWorkerId(id: bigint): number {
let workerId = (id >> this.sequenceBits) & this.maxWorkerId;
let result = parseInt(workerId.toString());
return result;
}
// 获得分步式自增ID序列号ID
public getSequence(id: bigint): number {
let sequence = id & this.sequenceMask;
let result = parseInt(sequence.toString());
return result;
}
}