📣读完这篇文章里你能收获到
二进制64位长整型数字:1bit保留 + 41bit时间戳 + 10bit机器 + 12bit序列号
简单说就是时间被调整回到了之前的时间,由于雪花算法重度依赖机器的当前时间,所以一旦发生时间回拨,将有可能导致生成的 ID 可能与此前已经生成的某个 ID 重复(前提是刚好在同一毫秒生成 ID 时序列号也刚好一致),这就是雪花算法最经常讨论的问题——时间回拨
在雪花算法原本的实现中,针对这种问题,算法本身只是返回错误,由应用另行决定处理逻辑,如果是在一个并发不高或者请求量不大的业务系统中,错误等待或者重试的策略问题不大,但是如果是在一个高并发的系统中,这种策略显得过于粗暴
将当前线程阻塞3ms,之后再获取时间,看时间是否比上一次请求的时间大,如果大了,说明恢复正常了,则不用管如果还小,说明真出问题了,则抛出异常,缺点仍然如3.1所描述
当使用雪花算法出现时间回拨时,不想抛异常,又希望能继续保持全局唯一性、趋势递增、信息安全,可以了解第四点,基于时间序列的方案
我这里介绍的是一种基于修改扩展位的思路,基于时钟序列的雪花算法
二进制64位长整型数字:1bit保留 + 41bit时间戳 + 3位时钟序列 + 7bit机器 + 12bit序列号
.Net Demo 其他语言参考流程自行改造
///
/// 获取下一个ID
///
///
public long NextId()
{lock (_lock){//当前系统时间戳var currentTimestamp = TimeGen();//出现时间回拨 当前系统时间小于最后更新时间if (currentTimestamp < _lastTimestamp){// _clockSequence自增,和CLOCK_SEQUENCE_MASK相与一下,去掉高位_clockSequence = (_clockSequence + 1) & CLOCK_SEQUENCE_MASK;}// 如果上次生成时间和当前时间相同,在同一毫秒内if (_lastTimestamp == currentTimestamp){// sequence自增,和SEQUENCE_MASK相与一下,去掉高位_sequence = (_sequence + 1) & SEQUENCE_MASK;//判断是否溢出,也就是每毫秒内超过1024,当为1024时,与sequenceMask相与,sequence就等于0if (_sequence == 0){//等待到下一毫秒currentTimestamp = TilNextMillis(_lastTimestamp);}}else{//如果和上次生成时间不同,重置sequence,就是下一毫秒开始,sequence计数重新从0开始累加,_sequence = 0;}_lastTimestamp = currentTimestamp;return ((currentTimestamp - TWEPOCH) << TIMESTAMP_LEFT_SHIFT) | _clockSequence << CLOCK_SEQUENCE_SHIFT | (WorkerId << WORKER_ID_SHIFT) | _sequence;}
}
‘
这个是由时间戳位数决定的,原生雪花算法时间戳占41位,也就是支持最大的时间戳为2^41(2199023255552),而1年的总毫秒数为3600 * 1000 * 24 * 365 = 31,536,000,000,因此2^41 / 1年的总毫秒数≈69.7年
其实衍生出另一个问题,41位能表示的最大的时间戳为2^41(2199023255552)对应的时间应该是2039-09-07 23:47:35,距离现在只有不到20年的时间,为什么算出来的是69年呢?
其实时间戳的算法是1970年1月1日到指点时间所经过的毫秒或秒数,那咱们把开始时间从2021年开始,就可以延长41位时间戳能表达的最大时间,所以这里实际指的是相对自定义开始时间的时间戳