转载自 漫画:什么是SnowFlake算法

方法一:UUID

UUID是通用唯一识别码 (Universally Unique Identifier),在其他语言中也叫GUID,可以生成一个长度32位的全局唯一识别码。

String uuid = UUID.randomUUID().toString()

结果示例:

046b6c7f-0b8a-43b9-b35d-6489e6daee91

为什么无序的UUID会导致入库性能变差呢?

这就涉及到 B+树索引的分裂:

众所周知,关系型数据库的索引大都是B+树的结构,拿ID字段来举例,索引树的每一个节点都存储着若干个ID。

如果我们的ID按递增的顺序来插入,比如陆续插入8,9,10,新的ID都只会插入到最后一个节点当中。当最后一个节点满了,会裂变出新的节点。这样的插入是性能比较高的插入,因为这样节点的分裂次数最少,而且充分利用了每一个节点的空间。

但是,如果我们的插入完全无序,不但会导致一些中间节点产生分裂,也会白白创造出很多不饱和的节点,这样大大降低了数据库插入的性能。

方法二:数据库自增主键

假设名为table的表有如下结构:

id        feild

35        a

每一次生成ID的时候,访问数据库,执行下面的语句:

begin;

REPLACE INTO table ( feild )  VALUES ( 'a' );

SELECT LAST_INSERT_ID();

commit;

REPLACE INTO 的含义是插入一条记录,如果表中唯一索引的值遇到冲突,则替换老数据。

这样一来,每次都可以得到一个递增的ID。

为了提高性能,在分布式系统中可以用DB proxy请求不同的分库,每个分库设置不同的初始值,步长和分库数量相等:

这样一来,DB1生成的ID是1,4,7,10,13....,DB2生成的ID是2,5,8,11,14.....

————————————

初识SnowFlake

snowflake算法所生成的ID结构是什么样子呢?我们来看看下图:

SnowFlake所生成的ID一共分成四部分:

1.第一位

占用1bit,其值始终是0,没有实际作用。

2.时间戳

占用41bit,精确到毫秒,总共可以容纳约69 年的时间。

3.工作机器id

占用10bit,其中高位5bit是数据中心ID(datacenterId),低位5bit是工作节点ID(workerId),做多可以容纳1024个节点。

4.序列号

占用12bit,这个值在同一毫秒同一节点上从0开始不断累加,最多可以累加到4095。

SnowFlake算法在同一毫秒内最多可以生成多少个全局唯一ID呢?只需要做一个简单的乘法:

同一毫秒的ID数量 = 1024 X 4096 =  4194304

这个数字在绝大多数并发场景下都是够用的。

SnowFlake的代码实现

//初始时间截 (2017-01-01)
private static final long INITIAL_TIME_STAMP = 1483200000000L;//机器id所占的位数
private static final long WORKER_ID_BITS = 5L;//数据标识id所占的位数
private static final long DATACENTER_ID_BITS = 5L;//支持的最大机器id,结果是31 (这个移位算法可以很快的计算出几位二进制数所能表示的最大十进制数)
private static final long MAX_WORKER_ID = ~(-1L << WORKER_ID_BITS);//支持的最大数据标识id,结果是31
private static final long MAX_DATACENTER_ID = ~(-1L << DATACENTER_ID_BITS);//序列在id中占的位数
private final long SEQUENCE_BITS = 12L;//机器ID的偏移量(12)
private final long WORKERID_OFFSET = SEQUENCE_BITS;//数据中心ID的偏移量(12+5)
private final long DATACENTERID_OFFSET = SEQUENCE_BITS + SEQUENCE_BITS;//时间截的偏移量(5+5+12)
private final long TIMESTAMP_OFFSET = SEQUENCE_BITS + WORKER_ID_BITS + DATACENTER_ID_BITS;//生成序列的掩码,这里为4095 (0b111111111111=0xfff=4095)
private final long SEQUENCE_MASK = ~(-1L << SEQUENCE_BITS);//工作节点ID(0~31)
private long workerId;//数据中心ID(0~31)
private long datacenterId;//毫秒内序列(0~4095)
private long sequence = 0L;//上次生成ID的时间截
private long lastTimestamp = -1L;/*** 构造函数** @param workerId     工作ID (0~31)* @param datacenterId 数据中心ID (0~31)*/
public SnowFlakeIdGenerator(long workerId, long datacenterId) {if (workerId > MAX_WORKER_ID || workerId < 0) {throw new IllegalArgumentException(String.format("WorkerID 不能大于 %d 或小于 0", MAX_WORKER_ID));}if (datacenterId > MAX_DATACENTER_ID || datacenterId < 0) {throw new IllegalArgumentException(String.format("DataCenterID 不能大于 %d 或小于 0", MAX_DATACENTER_ID));}this.workerId = workerId;this.datacenterId = datacenterId;
}/*** 获得下一个ID (用同步锁保证线程安全)** @return SnowflakeId*/
public synchronized long nextId() {long timestamp =  System.currentTimeMillis();//如果当前时间小于上一次ID生成的时间戳,说明系统时钟回退过这个时候应当抛出异常if (timestamp < lastTimestamp) {throw new RuntimeException("当前时间小于上一次记录的时间戳!");}//如果是同一时间生成的,则进行毫秒内序列if (lastTimestamp == timestamp) {sequence = (sequence + 1) & SEQUENCE_MASK;//sequence等于0说明毫秒内序列已经增长到最大值if (sequence == 0) {//阻塞到下一个毫秒,获得新的时间戳timestamp = tilNextMillis(lastTimestamp);}}//时间戳改变,毫秒内序列重置else {sequence = 0L;}//上次生成ID的时间截lastTimestamp = timestamp;//移位并通过或运算拼到一起组成64位的IDreturn ((timestamp - INITIAL_TIME_STAMP) << TIMESTAMP_OFFSET)| (datacenterId << DATACENTERID_OFFSET)| (workerId << WORKERID_OFFSET)| sequence;
}/*** 阻塞到下一个毫秒,直到获得新的时间戳** @param lastTimestamp 上次生成ID的时间截* @return 当前时间戳*/
protected long tilNextMillis(long lastTimestamp) {long timestamp = System.currentTimeMillis();while (timestamp <= lastTimestamp) {timestamp = System.currentTimeMillis();}return timestamp;
}public static void main(String[] args) {final SnowFlakeIdGenerator idGenerator = new SnowFlakeIdGenerator(1, 1);//线程池并行执行10000次ID生成ExecutorService executorService = Executors.newCachedThreadPool();;for (int i = 0; i < 10000; i++) {executorService.execute(new Runnable() {@Overridepublic void run() {long id = idGenerator.nextId();System.out.println(id);}});}executorService.shutdown();
}

这段代码改写自网上的SnowFlake算法实现,有几点需要解释一下:

1.获得单一机器的下一个序列号,使用Synchronized控制并发,而非CAS的方式,是因为CAS不适合并发量非常高的场景。

2.如果当前毫秒在一台机器的序列号已经增长到最大值4095,则使用while循环等待直到下一毫秒。

3.如果当前时间小于记录的上一个毫秒值,则说明这台机器的时间回拨了,抛出异常。但如果这台机器的系统时间在启动之前回拨过,那么有可能出现ID重复的危险。

SnowFlake的优势和劣势

SnowFlake算法的优点:

1.生成ID时不依赖于DB,完全在内存生成,高性能高可用。

2.ID呈趋势递增,后续插入索引树的时候性能较好。

SnowFlake算法的缺点:

依赖于系统时钟的一致性。如果某台机器的系统时钟回拨,有可能造成ID冲突,或者ID乱序。

漫画:什么是SnowFlake算法相关推荐

  1. 漫画解读SnowFlake算法

    非常感谢程序员小灰 ----- 第二天 ----- 方法一:UUID UUID是通用唯一识别码 (Universally Unique Identifier),在其他语言中也叫GUID,可以生成一个长 ...

  2. 分布式唯一id:snowflake算法思考

    匠心零度 转载请注明原创出处,谢谢! 缘起 为什么会突然谈到分布式唯一id呢?原因是最近在准备使用RocketMQ,看看官网介绍: 一句话,消息可能会重复,所以消费端需要做幂等.为什么消息会重复后续R ...

  3. java 唯一id生成算法_分布式全局唯一ID生成方案之snowflake算法

    已有的方案: 可大致分为: 完全依赖关系/非关系型数据库递增的方案 完全不依赖数据源作为生成因子的UUID 半依赖数据源作为生成因子的snowflake 为什么推荐snowflake? 这个问题,可以 ...

  4. snowflake算法 php,Snowflake —— 分布式全局唯一 id 生成算法

    简介 Snowflake 是 Twitter 提出一种的分布式唯一序列号生成算法,理论上单节点 1 毫秒可以生成 4096 个(每秒四百万个)唯一序列,这个序列是个 long 类型的数字,在数据库中的 ...

  5. mysql snowflake_一篇文章彻底搞懂snowflake算法及百度美团的最佳实践

    写在前面的话 一提到分布式ID自动生成方案,大家肯定都非常熟悉,并且立即能说出自家拿手的几种方案,确实,ID作为系统数据的重要标识,重要性不言而喻,而各种方案也是历经多代优化,请允许我用这个视角对分布 ...

  6. 根据twitter的snowflake算法生成唯一ID

    C#版本 /// <summary>/// 根据twitter的snowflake算法生成唯一ID/// snowflake算法 64 位/// 0---0000000000 000000 ...

  7. java生成唯一有序序列号_分布式唯一 ID 之 Snowflake 算法

    SegmentFault 社区专栏:全栈修仙之路作者:semlinker No.1 Snowflake 简介 1.1 什么是 Snowflake Snowflake is a service used ...

  8. 关于snowflake算法的几个问题

    本文来说下关于snowflake算法的几个问题 文章目录 SnowFlake算法 SnowFlake算法实现代码 雪花算法的优缺点 本文小结 SnowFlake算法 SnowFlake 算法,是 Tw ...

  9. 基于Twitter的Snowflake算法实现的分布式ID生成器

    /*** 基于Twitter的Snowflake算法实现的分布式ID生成器* ------------------------------------------------------------- ...

最新文章

  1. 全球16家超级独角兽公司,为什么中国能占7家,印度只有1家?未来哪些行业最可能诞生独角兽?
  2. 用python解析html[SGMLParser]
  3. ECMAScript 6 学习笔记(一)
  4. 嵌入式arm linux 文件系统登入密码的修改笔记
  5. python学习笔记(十一)——正则表达式
  6. nowcoder 202F-平衡二叉树
  7. linux grep命令详解_Linux 上USB 调试神器lsusb命令详解
  8. CRMEB小程序安装说明
  9. Python:通过SNMP协议获取华为交换机的ARP地址表
  10. 一文带你读懂!华为云在ACMUG技术沙龙上都透露了些啥?
  11. 阿里云centos7上yum安装并连接mysql
  12. HTTP代理,正向反向,代理的作用
  13. 对 COMP 通证经济模型的一般性评价
  14. java通过控制台实现打字小游戏
  15. 截取图片DEMO. JAVA Windows FFmpeg
  16. vue项目在ie9中碰到的问题——axios请求拒绝访问
  17. Beats:使用 Elastic Stack 记录 Python 应用日志
  18. ***常用工具下载大全
  19. IT知识百科:什么是基站?
  20. FPGA之道(81)静态时序分析(七)根据时序报告修改设计(基于ISE的UCF文件语法)

热门文章

  1. Java Stack 类
  2. [JavaWeb-JavaScript]JavaScript特殊语法
  3. 丁可以组什么词_有哪些量词可以用来描述生意经?
  4. django html跳转页面跳转页面,Django html单击打开另一个html页面
  5. word List 48
  6. Linux下的磁盘空间管理
  7. AT2645 [ARC076D] Exhausted?(Hall定理推论/线段树+扫描线)
  8. Codeforces Round #598 (Div. 3) F. Equalizing Two Strings 思维 + 逆序对
  9. P1537 弹珠 背包可行性dp
  10. 【CF1189D】Add on a Tree【结论】【构造】