一、雪花算法是什么?
分布式中生成唯一性ID的一种算法 。
为啥不用数据库的自增主键呢?
-
唯一性: 如果数据库数据特别多,你会同一张表建立不同节点上,数据也在不同节点上存,那么如果俩ID都是 001 违背主键 定义吗?
-
顺序性: 雪花算法计算出来ID 有顺序 如果你了解数据库 B+树 ,对于索引来说 字段是
数字类型 ,有顺序, 唯一在查找以及插入效率很高 而UUID是字符串没顺序不适合做数据库主键了
二、雪花算法的构成
雪花算法 由64位构成ID,对应java数据类型的话 long类型 这里位是二进制位
- 最左一位(图中没有标出)都是 0 因为二进制中
最左符号位 1代表负数 0是正数而我们生成ID肯定是正数所以是0 - timestamp : 时间戳 我们一般情况给初始时间 用系统当前时间 减去 初始时间 这个差值的时间戳作为ID的时间戳也就是timestamp占用41位。
至于为啥差值作为时间戳 1.减小时间戳长度 2.时钟回拨处理 - instance :这个表示机器个数 分布式系统中 多个节点 可以左边5位是机器的ID 右边5位 数据中心ID(机房ID)加起来是10位
- sequence:序列号 这个主要用途 并发执行代码 有时候获取时间一样的 那么区分ID 用序列号自增进行区分

时钟回拨: 一种情况管理员手动把时间调整当前系统之前时间,这样的话生成ID和之前ID可能冲突了。虽然上边时间戳插值减轻该问题但是插值仍有可能为正值,
三、雪花算法实现思路
3.1 如何把 时间戳,机器ID,序列号等合在一起变成一个long类型数字?
3.2 如果并发访问 同一时间对于 要生成ID多于2的12次方个 也就是多余4096个ID如何处理?
3.3 发生时钟回拨如何处理?
3.1的思路是
-
首先移动好说的 。数据中心ID datacenterID (属于instance) 假设 1L 号 用long表示可以 (二进制)
0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0001
将它左移动12位(序列号最长位)代码datacenterID << 12结果
0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 00010000 0000 0000 -
机器ID 左移动 12位(序列号) + 5位(数据中心ID)= 17
workID << 17
0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0010 0000 0000 0000 0000 -
我们随便用时间戳差值 1110 0101 1010 0001 0001 10111 11 那么他图中右移12 + 5 + 5 = 22位
0000 0000 0000 00001110 0101 1010 0001 0001 10111 1100 0000 0000 0000 0000 0000 -
序列号最右边所以不用移动
0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 00000000 0000 0000 0000 0000
接下来如何合并时间戳,机器ID,数据中心ID,序列号呢?
可以将两个long类型 按位或 进行合并 (按位或 只要有1 结果1 否则是0)
拿数据中心ID和机器ID举例子
- 数据ID 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 000
10000 0000 0000 - 机器ID 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 00
10 0000 0000 0000 0000
(或 | )结果 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0010 0001 0000 0000 0000
这样进行合并了 3.1问题解决了。
3.2 问题思路
0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 1111 1111 1111 1111
该时间再生成ID就是
0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0001 0000 0000 0000 0000
对于这个我们构建一个类似于掩码数字 二进制表示
0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 1111 1111 1111 1111
进行按位与 (&)两个都为1结果是1 否则为0
上边两个& 结果刚好 0 这个结果可以判断是否溢出
对于构建掩码那个数字 可以 -1 ^ (-1 << 12) ^ 是异或符号 两个二进制位 不同为1 相同为0
-1 的二进制如下
1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111
3.3 问题思路
- 可以记录上一次时间字段,如果当前时间 < 上一次记录时间 发送时钟回拨 抛异常提示
- 可以进行等待,时间过了上一次记录时间进行ID生成
- 也可以采用更高时间精度来生成时间戳
Clock clock = Clock.systemUTC();
long currentTimestamp = clock.millis();
三、Java代码实现
代码如下(示例):
package com;
public class SnowFlakeUtil{
//起始时间戳
private static long startTimeStamp;
//机器ID
private static long workID;
//数据中心ID
private static long dataCenterID;
//序列号
private static long sequence;
//数据中心ID移动位数
private static long dataCenterIndex;
//机器ID移动位数
private static long workIDIndex;
//时间戳移动位数
private static long timeStampIndex;
//记录上一次时间戳
private static long lastTimeStamp;
//序列号掩码
private static long sequenceMask;
//序列号长度12位
private static long sequenceLength;
//初始化数据
static {
startTimeStamp = 1577808000000L;
//设置机器编号 1
workID = 1L;
//设置数据中心ID 1
dataCenterID = 1L;
//起始序列号 0开始
sequence = 0L;
//数据中心位移位数
dataCenterIndex = 12L;
//机器ID位移位数
workIDIndex = 17L;
//时间戳位移位数
timeStampIndex = 22L;
//记录上次时间戳
lastTimeStamp = -1L;
//序列号长度
sequenceLength = 12L;
//序列号掩码
sequenceMask = -1L ^ (-1L << sequenceLength);
}
public synchronized static long getID(){
//获得当前时间
long now = System.currentTimeMillis();
//当前系统时间小于上一次记录时间
if (now < lastTimeStamp){
throw new RuntimeException("时钟回拨异常");
}
//相同时间 要序列号进制增量
if (now == lastTimeStamp){
//防止溢出
sequence = (sequence + 1) & sequenceMask;
if (sequence == 0L){
//溢出处理
try {
Thread.

本文详细介绍了雪花算法的工作原理,包括其构成、实现思路,特别是在处理并发、时钟回拨等问题上的策略。还提供了Java和Springboot的代码实现示例,以及如何设置机器ID和数据中心ID以适应前端存储需求。
1770

被折叠的 条评论
为什么被折叠?



