分布式 ID 生成算法 — SnowFlake
一、概述
分布式 ID 生成算法的有很多种,Twitter 的 SnowFlake 就是其中经典的一种。
SnowFlake 算法生成 ID 的结果是一个 64bit 大小的整数,它的结构如下图:

- 1 位,不用。二进制中最高位为 1 的都是负数,但是我们生成的 id 一般都使用整数,所以这个最高位固定是 0。
- 41 位,用来记录时间戳(毫秒)。41 位可以表示 2^41 个数字;如果只用来表示正整数(计算机中正数包含 0),可以表示的数值范围是:0 至 2^41−1,也就是说 41 位可以表示 2^41 个毫秒的值,转化成单位年则是 2^41 / (1000 * 60 * 60 * 24 * 365) = 69年。
- 10 位,用来记录工作机器 id,可以部署在 2^10 = 1024个节点,包括 5 位 datacenterId 和 5 位 workerId ;5 位(bit)可以表示的最大正整数是2^5-1 = 31,即可以用 0、1、2、3、....31 这 32 个数字,来表示不同的 datecenterId 或 workerId。
- 12 位,序列号,用来记录同毫秒内产生的不同 id。12位(bit)可以表示的最大正整数是 2^12-1 =4095,即可以用 0、1、2、3、....4095 这 4096 个数字,来表示同一机器同一时间截(毫秒)内产生的 4096 个ID序号。
由于在 Java 中 64 bit 的整数是 Long 类型,所以在 Java 中 SnowFlake 算法生成的 ID 就是 Long 来存储的。
SnowFlake 可以保证:
- 所有生成的 ID 按时间趋势递增;
- 整个分布式系统内不会产生重复id(因为有 datacenterId 和 workerId 来做区分);
二、SnowFlake 算法的 JAVA 实现
public class SnowFlake {
/**
* 起始的时间戳
*/
private final static long START_STMP = 1480166465631L;
/**
* 每一部分占用的位数
*/
private final static long SEQUENCE_BIT = 12; //序列号占用的位数
private final static long MACHINE_BIT = 5; //机器标识占用的位数
private final static long DATACENTER_BIT = 5;//数据中心占用的位数
/**
* 每一部分的最大值
*/
private final static long MAX_DATACENTER_NUM = ~(-1L << DATACENTER_BIT);
private final static long MAX_MACHINE_NUM = ~(-1L << MACHINE_BIT);
private final static long MAX_SEQUENCE = ~(-1L << SEQUENCE_BIT);
/**
* 每一部分向左的位移
*/
private final static long MACHINE_LEFT = SEQUENCE_BIT;
private final static long DATACENTER_LEFT = SEQUENCE_BIT + MACHINE_BIT;
private final static long TIMESTMP_LEFT = DATACENTER_LEFT + DATACENTER_BIT;
private long datacenterId; //数据中心
private long machineId; //机器标识
private long sequence = 0L; //序列号
private long lastStmp = -1L;//上一次时间戳
public SnowFlake(long datacenterId, long machineId) {
if (datacenterId > MAX_DATACENTER_NUM || datacenterId < 0) {
throw new IllegalArgumentException("datacenterId can't be greater than MAX_DATACENTER_NUM or less than 0");
}
if (machineId > MAX_MACHINE_NUM || machineId < 0) {
throw new IllegalArgumentException("machineId can't be greater than MAX_MACHINE_NUM or less than 0");
}
this.datacenterId = datacenterId;
this.machineId = machineId;
}
/**
* 产生下一个ID
*
* @return
*/
public synchronized long nextId() {
long currStmp = getNewstmp();
if (currStmp < lastStmp) {
throw new RuntimeException("Clock moved backwards. Refusing to generate id");
}
if (currStmp == lastStmp) {
//相同毫秒内,序列号自增
sequence = (sequence + 1) & MAX_SEQUENCE;
//同一毫秒的序列数已经达到最大
if (sequence == 0L) {
currStmp = getNextMill();
}
} else {
//不同毫秒内,序列号置为0
sequence = 0L;
}
lastStmp = currStmp;
return (currStmp - START_STMP) << TIMESTMP_LEFT //时间戳部分
| datacenterId << DATACENTER_LEFT //数据中心部分
| machineId << MACHINE_LEFT //机器标识部分
| sequence; //序列号部分
}
private long getNextMill() {
long mill = getNewstmp();
while (mill <= lastStmp) {
mill = getNewstmp();
}
return mill;
}
private long getNewstmp() {
return System.currentTimeMillis();
}
public static void main(String[] args) {
SnowFlake snowFlake = new SnowFlake(2, 3);
long start = System.currentTimeMillis();
for (int i = 0; i < 1000; i++) {
System.out.println(snowFlake.nextId());
}
System.out.println(System.currentTimeMillis() - start);
}
}
三、SnowFlake 算法的生产实践
通过前面的讲述我们知道 SnowFlake 算法能产生全局唯一 ID 主要是通过 5 位 datacenterId 和 5 位 workerId 来做区分,而在生产实践中,我们常常将机器 ID(hostid)来当成 datacenterId,将机器上运行的实例ID(pid)来当成 workerId。
如今的分布式部署都在强调无状态化,那么给每台机器绑定一个 hostid 显然不太现实,假设又是在容器化环境下,没有固定的 IP,并且容器实例每次重新启动后的唯一 ID 还不一致。综上,基于机器 ID 的思路不可行。所以经常的做法是,利用 ZK/Redis/DB 等作为一个全局的 datacenterId/workerId 发号器,由发号器来分配唯一的 datacenterId/workerId 。(可以参考百度分布式唯一 ID 生成器 UidGenerator)

四、SnowFlake 算法的问题思考
1. 时间回拨问题
由于机器的时间是动态的调整的,有可能会出现时间跑到之前几毫秒,如果这个时候获取到了这种时间,则会出现数据重复。
这个问题的解决方案是采用“历史时间”。在进程启动后,我们会将当前时间(实际处理采用了延迟10ms启动),作为该业务这台机器进程的时间戳中的起始时间字段,后续的自增是在序列号自增到最大值时,时间戳增 1,而序列号重新归为 0。
2. 机器 id 分配及回收
目前机器 id 需要每台机器不一样,这样的方式分配需要有方案进行处理,同时也要考虑,如果该机器宕机了,对应的 datacenterId/workerId 分配后的回收问题。
这个问题的解决方案是:每个实例启动,扩容,直接从 ZK/Redis/DB 等发号器取一个 id 作为 datacenterId/workerId,下线不销毁;并且维护一个活动节点队列,在地址空间耗尽的时候,指针指回队列头部,当分配的 id 存在于活动节点队列则跳过取下一个可用空间,达到复用原地址空间的目的。

分布式 ID 生成算法 — SnowFlake的更多相关文章
- 理解分布式id生成算法SnowFlake
理解分布式id生成算法SnowFlake https://segmentfault.com/a/1190000011282426#articleHeader2 分布式id生成算法的有很多种,Twitt ...
- 美团技术分享:深度解密美团的分布式ID生成算法
本文来自美团技术团队“照东”的分享,原题<Leaf——美团点评分布式ID生成系统>,收录时有勘误.修订并重新排版,感谢原作者的分享. 1.引言 鉴于IM系统中聊天消息ID生成算法和生成策略 ...
- Twitter的SnowFlake分布式id生成算法
二进制相关知识回顾 1.所有的数据都是以二进制的形式存储在硬盘上.对于一个字节的8位到底是什么类型 计算机是如何分辨的呢? 其实计算机并不负责判断数据类型,数据类型是程序告诉计算机该如何解释内存块. ...
- java 分布式id生成算法
import java.lang.management.ManagementFactory; import java.net.InetAddress; import java.net.NetworkI ...
- SnowFlake --- 分布式id生成算法
转载自:https://segmentfault.com/a/1190000011282426 概述 SnowFlake算法生成id的结果是一个64bit大小的整数,它的结构如下图: 1位,不用.二进 ...
- SnowFlake分布式ID生成及反解析
概述 分布式id生成算法的有很多种,Twitter的SnowFlake就是其中经典的一种,SnowFlake算法生成id的结果是一个64bit大小的整数,它的结构如下图: 1位,不用.二进制中最高位为 ...
- 细聊分布式ID生成方法
细聊分布式ID生成方法 https://mp.weixin.qq.com/s?__biz=MjM5ODYxMDA5OQ==&mid=403837240&idx=1&sn=ae9 ...
- Leaf:美团分布式ID生成服务开源
Leaf是美团基础研发平台推出的一个分布式ID生成服务,名字取自德国哲学家.数学家莱布尼茨的一句话:“There are no two identical leaves in the world.”L ...
- 分布式系统的唯一id生成算法你了解吗?
在分库分表之后你必然要面对的一个问题,就是id咋生成? 因为要是一个表分成多个表之后,每个表的id都是从1开始累加自增长,那肯定不对啊. 举个例子,你的订单表拆分为了1024张订单表,每个表的id都从 ...
随机推荐
- vue 重置data
Object.assign(this.$data, this.$options.data())
- 如何正确运用break、continue和return
break.continue和return 一.break 作用:跳出(离break语句最近的)循环 栗子: for(int i = 1;i <= 100;i++){ System.out.pr ...
- phpstorm ext-json is missing in composer.json
- matlab随机系数矩阵产生以及矩阵的可视化函数
clc; clear all; close all; n = 100;%所产生矩阵的大小 A= sprandsym(n,0.015,0.1,1);%产生系数矩阵函数: spy(A)矩阵图形化相当于im ...
- C#中RDLC报表常用表达式(字符串和转换)
字符串函数 (1)使用串联运算符和 Visual Basic 常量可将多个字段组合在一起.以下表达式返回两个字段,它们分别位于同一文本框的不同行中:=Fields!FirstName.Value &a ...
- CentOS7 实战源码安装mysql5.7.17数据库服务器
CentOS7 实战源码安装mysql5.7.17数据库服务器 简介:实战演练mysql数据库服务器的搭建 mysql简介: mysql是一个开源的关系型数据库管理系统,现在是oracle公司旗下的 ...
- Jenkins 持续集成实现 Android 自动化打包
打 debug 包流程: git pull 分支最新代码 Android Studio:Build - Generate Signed APK 从 IDE 里可以看到,实际上该操作是执行了 assem ...
- 扫盲:Kotlin 的泛型
引子 相信总是有很多同学,总是在抱怨泛型无论怎么学习,都只是停留在一个简单使用的水平,所以一直为此而备受苦恼. Kotlin 作为一门能和 Java 相互调用的语言,自然也支持泛型,不过 Kotlin ...
- java Swing组件随着窗口拖动等比移动或等比放大
实现原理很简单, 1清空布局(使用绝对布局) 2添加监听器(监听窗口是否被拖动) 3在监听器里面动态调整 组件的位置 效果如下: 拖动之后效果: 代码实现: import java.awt.Event ...
- C#—连接SQLserver数据库,并执行查询语句代码
//字段ArticleID,ArticleName,ArticleNumber,Unit,Weight,Price,Currency,IsIuggage,IsQuarantine string str ...