最近在研究分布式框架的组件和整体设计思路。所有的问题,一旦涉及分布式难度就呈几何倍数的提升。包括最常见的ID生成也是,单机情况下,使用数据库自增ID、UUID都是简单易行的选择

但在分布式环境下,就需要考虑同业务部署多套以后,ID重复的问题。使用数据库则数据库容易成为瓶颈,使用UUID又没有顺序,数据库集成又会遇到递增步长等问题。最后,数据库(也可使用redis)号段生成器和snowFlake就成为了目前分布式ID生成器的主流

我所知大部分互联网公司的分布式ID生成器,其实都是一个网络服务或集群,单独部署。其他应用程序通过网络去获取分布式的全局唯一ID。使用网络服务的方式,好处显而易见,就是方便集中管理,只要生成器设计的没问题,基本ID就能保证整体趋势是递增的。坏处就是获取效率被明显降低了

另外针对我司来说,由于项目的性质,采用分布式ID生成器,对开发和上线部署及其后期的运维都会带来一定的麻烦。毕竟上线后,项目的管理权就不在我们手上了,所以为了保证分布式ID生成器的稳定性,尽量不采取分布式ID生成中心的策略。于是,留给我的选择就只剩下了SnowFlakeID(雪花ID)了。

什么是SnowFlakeID

SnowFlake是twitter公司内部分布式项目采用的ID生成算法,开源后广受国内大厂的好评。由这种算法生成的ID,我们就叫做SnowFlakeID

SnowFlakeID的最大的特性就是天然去中心化,通过时间戳、工作机器编号两个变量进行配置后,通过SnowFlake算法会生成唯一的递增ID。在任何机器上,只要保证工作机器编号不同,就可以确保生成的ID唯一,且整体趋势是递增的

Snowflake的结构如下(每部分用-分开):

0 - 0000000000 0000000000 0000000000 0000000000 0 - 0000000000 - 000000000000

第一段1位为未使用,永远固定为0

第二段41位为毫秒级时间(41位的长度可以使用69年)

第三段10位为workerId(10位的长度最多支持部署1024个节点)

第三段12位为毫秒内的计数(12位的计数顺序号支持每个节点每毫秒产生4096个ID序号)

如果按照1024的满节点(1个节点就是1个部署的服务)计算,每毫秒可生成的ID序号有1024*4096=4194304个,足以满足现在绝大多数的业务情况

算法的核心如下

 ((当前时间 - 服务时间) << timestampLeftShift)
| (机器ID << workerIdShift)
| sequence;

服务时间指的是服务的开发时间,即第一个正式ID产生的时间。由于SnowFlakeID最长可用69年(因为只有41个bit,41个bit的最大值换算成年就是69年)。所以服务时间越贴近上线时间,则该算法可用时间越长。

其中sequence为递增序列,当前时间戳和上一ID生成时间戳一致时,sequence就递增1,直到4096为止。

SnowFlake有什么问题

SnowFlake很好,分布式、去中心化、无第三方依赖。但它并不是完美的,由于SnowFlake强依赖时间戳,所以时间的变动会造成SnowFlake的算法产生错误。

时钟回拨:最常见的问题就是时钟回拨导致的ID重复问题,在SnowFlake算法中并没有什么有效的解法,仅是抛出异常。时钟回拨涉及两种情况①实例停机→时钟回拨→实例重启→计算ID ②实例运行中→时钟回拨→计算ID

手动配置:另一个就是workerId(机器ID)是需要部署时手动配置,而workerId又不能重复。几台实例还好,一旦实例达到一定量级,管理workerId将是一个复杂的操作。

如何优化

时钟回拨改进避免

ID生成器一旦不可用,可能造成所有数据库相关新增业务都不可用,影响太大。所以时钟回拨的问题必须解决。

造成时钟回拨的原因多种多样,可能是闰秒回拨,可能是NTP同步,还可能是服务器时间手动调整。总之就是时间回到了过去。针对回退时间的多少可以进行不同的策略改进。一般有以下几种方案:

  1. 少量服务器部署ID生成器实例,关闭NTP服务器,严格管理服务器。这种方案不需要从代码层面解决,完全人治。
  2. 针对回退时间断的情况,如闰秒回拨仅回拨了1s,可以在代码层面通过判断暂停一定时间内的ID生成器使用。虽然少了几秒钟可用时间,但时钟正常后,业务即可恢复正常。
if (refusedSeconds <= 5) {
try {
//时间偏差大小小于5ms,则等待两倍时间
wait(refusedSeconds << 1);//wait
} catch (InterruptedException e) {
e.printStackTrace();
}
currentSecond = getCurrentSecond();
}else {//时钟回拨较大
//用其他策略修复时钟问题
}
  1. 实例启动后,改用内存生成时间。该方案为baidu开源的UidGenerator使用的方案,由于实例启动后,时间不再从服务器获取,所以不管服务器时钟如何回拨,都影响不了SnowFlake的执行。如下代码中lastSecond变量是一个AtomicLong类型,用以代替系统时间
 List<Long> uidList = uidProvider.provide(lastSecond.incrementAndGet());
  1. 以上2和3都是解决时钟实例运行中→时钟回拨→计算ID的情况。而实例停机→时钟回拨→实例重启→计算ID的情况,可以通过实例启动的时候,采用未使用过的workerId来完成。只要workerId和此前生成ID的workerId不一致,即便时间戳有误,所生成的ID也不会重复。UidGenerator采取的就是这种方案,但这种方案又必须依赖一个存储中心,不管是redis、mysql、zookeeper都可以,但必须存储着此前使用过的workerId,不能重复。尤其是在分布式部署Id生成器的情况下,更要注意用一个存储中心解决此问题。
  2. UidGenerator代码可上Githubhttps://github.com/zer0Black/uid-generator查看

手动配置如何变为自动

其实该处的方案和时钟回拨的第四个方案是一致的,每次重启实例的时候,自动的查找workerId使用,不依赖手动配置。且自查找的workerId不会重复。方便管理。

参考文档

  1. UidGenerator文档
  2. 一口气说出 9种 分布式ID生成方式,面试官有点懵了
  3. Leaf——美团点评分布式ID生成系统

分布式SnowFlakeID(雪花ID)原理和改进优化的更多相关文章

  1. SnowflakeId雪花ID算法,分布式自增ID应用

    概述 snowflake是Twitter开源的分布式ID生成算法,结果是一个Long型的ID.其核心思想是:使用41bit作为毫秒数,10bit作为机器的ID(5个bit是数据中心,5个bit的机器I ...

  2. SnowFlakeId 分布式雪花id算法

    package com.jn.baseservice.utils; import com.jn.baseservice.common.Number; import lombok.Getter; imp ...

  3. 使用雪花算法为分布式下全局ID、订单号等简单解决方案考虑到时钟回拨

    1.snowflake简介         互联网快速发展的今天,分布式应用系统已经见怪不怪,在分布式系统中,我们需要各种各样的ID,既然是ID那么必然是要保证全局唯一,除此之外,不同当业务还需要不同 ...

  4. Twitter分布式自增ID算法snowflake原理解析(Long类型)

    Twitter分布式自增ID算法snowflake,生成的是Long类型的id,一个Long类型占8个字节,每个字节占8比特,也就是说一个Long类型占64个比特(0和1). 那么一个Long类型的6 ...

  5. 分布式唯一ID:雪花ID Snowflake .Net版

    先抄个雪花ID介绍,雪花算法: 雪花算法的原始版本是scala版,用于生成分布式ID(纯数字,时间顺序),订单编号等. 自增ID:对于数据敏感场景不宜使用,且不适合于分布式场景.GUID:采用无意义字 ...

  6. Twitter分布式自增ID算法snowflake原理解析

    以JAVA为例 Twitter分布式自增ID算法snowflake,生成的是Long类型的id,一个Long类型占8个字节,每个字节占8比特,也就是说一个Long类型占64个比特(0和1). 那么一个 ...

  7. 全球级的分布式数据库 Google Spanner原理

    开发四年只会写业务代码,分布式高并发都不会还做程序员?->>>    Google Spanner简介 Spanner 是Google的全球级的分布式数据库 (Globally-Di ...

  8. Mybatis框架(9)---Mybatis自定义插件生成雪花ID做为表主键项目

    Mybatis自定义插件生成雪花ID做为主键项目 先附上项目项目GitHub地址 spring-boot-mybatis-interceptor 有关Mybatis雪花ID主键插件前面写了两篇博客作为 ...

  9. 实时监控Cat之旅~分布式消息树的实现原理与测试

    大众点评的老吴在InfoQ上讲了Cat之后,有不少同仁开始关注这个实时监控系统,但学习的文章甚少,在GitHub上也是一言代过,给我们这些开发人员留下了N多个疑问,一时间不知道去哪里问,向谁去问了,通 ...

随机推荐

  1. 《图解机器学习-杉山将著》读书笔记---CH3

    CH3 最小二乘学习法 重点提炼 提出最小二乘学习法的缘故: 最小二乘学习法公式 对不同模型进行最小二乘法学习,得到最小二乘公式中的参数theta: 1.线性模型   代入3.1公式,对参数求偏导,偏 ...

  2. 能快速理解Java_集合类_的文章

    目录 @ 这篇文章是我学习完Java集合类做的笔记和总结,如果你想认真细读这篇文章,请做好受虐的准备(建议电脑看),因为这篇文章有点长,ヽ(ー_ー)ノ. 如果在看我这篇文章过程中,发现了错误,望指点. ...

  3. Map2Shp软件字符编码解决方案——彻底杜绝Shape格式乱码

    在使用Shape文件时,如果里面有中文属性信息时,经常会遇到属性信息变为乱码.尤其是ArcGIS10.2.1之后,Esri改变了软件的默认字符编码规则,打开之前保存的Shapefile文件,总会不时遇 ...

  4. Qt Installer Framework翻译(6-0)

    Qt安装程序框架示例 这些示例说明了如何使用组件脚本来自定义安装程序. Change Installer UI Example 使用组件脚本修改安装程序UI. Component Error Exam ...

  5. 关于C++读入数字按位取出与进制转换问题

    这一片博客我就不写具体的一个题了,只是总结一种典型问题——读入数字按位取出. 就拿数字12345举例吧. 是首先,我们要取出个位.这样取出: 12345/1=12345 12345%10=5.    ...

  6. java"小心机"(1)【资源彩蛋!】

    每天进步一点点,距离大腿又近一步! 阅读本文大概需要9分钟 java"小心机"系列文章在此开篇.在这,将会给你带来曾经错过.忽略或感到模糊的知识,也许它很基础,微不足道,但它能修复 ...

  7. airtest通过包名直接打开app的方法

    工具提供直接打开APP的函数 #输入微信包名,打开微信 start_app("com.tencent.mm")

  8. 聊一聊 MySQL 中的数据编辑过程中涉及的两阶段提交

    MySQL 数据库中的两阶段提交,不知道您知道不?这篇文章就简单的聊一聊 MySQL 数据库中的两阶段提交,两阶段提交发生在数据变更期间(更新.删除.新增等),两阶段提交过程中涉及到了 MySQL 数 ...

  9. redis缓存数据库及Python操作redis

    缓存数据库介绍  NoSQL(NoSQL = Not Only SQL ),意即“不仅仅是SQL”,泛指非关系型的数据库,随着互联网web2.0网站的兴起,传统的关系数据库在应付web2.0网站, 特 ...

  10. html中如何清除浮动

    在html中,浮动可以说是比较常用的.在页面的布局中他有着很大的作用,但是浮动中存在的问题也是比较多的.现在我们简单说一下怎么去除浮动 首先我们先简单的看一下浮动: 首先我们先创建一个简单的div,并 ...