1、为什么需要分布式ID对于单体系统来说主键ID可能会常用主键自动的方式进行设置这种ID生成方法在单体项目是可行的但是对于分布式系统分库分表之后就不适应了比如订单表数据量太大了分成了多个库如果还采用数据库主键自增的方式就会出现在不同库id一致的情况虽然是不符合业务的2、业务系统对分布式ID有什么要求全局唯一性ID是作为唯一的标识不能出现重复趋势递增互联网比较喜欢MySQL数据库而MySQL数据库默认使用InnoDB存储引擎其使用的是聚集索引使用有序的主键ID有利于保证写入的效率单调递增保证下一个ID大于上一个ID这种情况可以保证事务版本号排序等特殊需求实现信息安全前面说了ID要递增但是最好不要连续如果ID是连续的容易被恶意爬取数据指定一系列连续的所以ID递增但是不规则是最好的3、分布式ID生成方案UUID数据库自增号段模式Redis实现雪花算法SnowFlake百度Uidgenerator美团Leaf滴滴TinyID3.1 UUIDUUID Universally Unique Identifier通用唯一识别码的缩写。UUID的标准型式包含32个16进制数字以连字号分为五段形式为8-4-4-4-12的36个字符示例 863e254b-ae34-4371-87da-204b71d46a7b。UUID理论上的总数为16322128约等于3.4 x 10^38。优点性能非常高本地生成的不依赖于网络缺点不易存储16 字节128位36位长度的字符串信息不安全基于MAC地址生成UUID的算法可能会造成MAC地址泄露暴露使用者的位置uuid的无序性可能会引起数据位置频繁变动影响性能3.2、数据库自增在分布式环境也可以使用mysql的自增实现分布式ID的生成如果分库分表了当然不是简单的设置好auto_increment_increment和 auto_increment_offset即可在分布式系统中我们可以多部署几台机器每台机器设置不同的初始值且步长和机器数相等。比如有两台机器。设置步长step为2Server1的初始值为11357911…、Server2的初始值为2246810…。这是Flickr团队在2010年撰文介绍的一种主键生成策略Ticket Servers: Distributed Unique Primary Keys on the Cheap 假设有N台机器step就要设置为N如图进行设置这种方案看起来是可行的但是如果要扩容步长step等要重新设置假如只有一台机器步长就是1比如1,2,3,4,5,6这时候如果要进行扩容就要重新设置机器2可以挑一个偶数的数字这个数字在扩容时间内数据库自增要达不到这个数的然后步长就是2机器1要重新设置step为2然后还是以一个奇数开始进行自增。这个过程看起来不是很杂但是如果机器很多的话那就要花很多时间去维护重新设置这种实现的缺陷ID没有了单调递增的特性只能趋势递增有些业务场景可能不符合数据库压力还是比较大每次获取ID都需要读取数据库只能通过多台机器提高稳定性和性能3.3、号段模式这种模式也是现在生成分布式ID的一种方法实现思路是会从数据库获取一个号段范围比如[1,1000]生成1到1000的自增ID加载到内存中建表结构如CREATE TABLE id_generator ( id int(10) NOT NULL, max_id bigint(20) NOT NULL COMMENT 当前最大id, step int(20) NOT NULL COMMENT 号段的布长, biz_type int(20) NOT NULL COMMENT 业务类型, version int(20) NOT NULL COMMENT 版本号, PRIMARY KEY (id) )biz_type 不同业务类型max_id 当前最大的idstep 代表号段的步长version 版本号就像MVCC一样可以理解为乐观锁等ID都用了再去数据库获取然后更改最大值update id_generator set max_id #{max_idstep}, version version 1 where version # {version} and biz_type XXX优点有比较成熟的方案像百度Uidgenerator美团Leaf缺点依赖于数据库实现3.4、 Redis实现Redis分布式ID实现主要是通过提供像INCR 和 INCRBY 这样的自增原子命令由于Redis单线程的特点可以保证ID的唯一性和有序性这种实现方式如果并发请求量上来后就需要集群不过集群后又要和传统数据库一样设置分段和步长优缺点优点Redis性能相对比较好又可以保证唯一性和有序性缺点需要依赖Redis来实现系统需要引进Redis组件3.4、 雪花算法SnowFlakeSnowflake雪花算法是由Twitter开源的分布式ID生成算法以划分命名空间的方式将64-bit位分割成多个部分每个部分代表不同的含义64位在java中Long类型是64位的所以java程序中一般使用Long类型存储第一部分第一位占用1bit始终是0是一个符号位不使用第二部分第2位开始的41位是时间戳。41-bit位可表示241个数每个数代表毫秒那么雪花算法可用的时间年限是(241)/(1000606024365)69 年的时间第三部分10-bit位可表示机器数即2^10 1024台机器。通常不会部署这么多台机器第四部分12-bit位是自增序列可表示2^12 4096个数。觉得一毫秒个数不够用也可以调大点优点雪花算法生成的ID是趋势递增不依赖数据库等第三方系统生成ID的效率非常高稳定性好可以根据自身业务特性分配bit位比较灵活缺点雪花算法强依赖机器时钟如果机器上时钟回拨会导致发号重复或者服务会处于不可用状态。如果恰巧回退前生成过一些ID而时间回退后生成的ID就有可能重复。3.5、 百度Uidgenerator百度的UidGenerator是百度开源基于Java语言实现的唯一ID生成器是在雪花算法 snowflake 的基础上做了一些改进。引用官网的解释UidGenerator是Java实现的, 基于Snowflake算法的唯一ID生成器。UidGenerator以组件形式工作在应用项目中, 支持自定义workerId位数和初始化策略, 从而适用于docker等虚拟化环境下实例自动重启、漂移等场景。 在实现上, UidGenerator通过借用未来时间来解决sequence天然存在的并发限制; 采用RingBuffer来缓存已生成的UID, 并行化UID的生产和消费, 同时对CacheLine补齐避免了由RingBuffer带来的硬件级「伪共享」问题. 最终单机QPS可达600万。Snowflake算法描述指定机器 同一时刻 某一并发序列是唯一的。据此可生成一个64 bits的唯一IDlong。默认采用上图字节分配方式sign(1bit)固定1bit符号标识即生成的UID为正数。delta seconds (28 bits)当前时间相对于时间基点2016-05-20的增量值单位秒最多可支持约8.7年worker id (22 bits)机器id最多可支持约420w次机器启动。内置实现为在启动时由数据库分配默认分配策略为用后即弃后续可提供复用策略。sequence (13 bits)每秒下的并发序列13 bits可支持每秒8192个并发。详细的可以参考官网解释链接https://github.com/baidu/uid-generator/blob/master/README.zh_cn.md3.6、 美团LeafLeaf这个名字是来自德国哲学家、数学家莱布尼茨的一句话 There are no twoidentical leaves in the world “世界上没有两片相同的树叶”Leaf 提供两种生成的ID的方式号段模式(Leaf-segment)和snowflake模式(Leaf-snowflake。你可以同时开启两种方式也可以指定开启某种方式默认两种方式为关闭状态。Leaf­segment数据库方案其实就是前面介绍的号段模式的改进可以引用美团技术博客的介绍第一种Leaf-segment方案在使用数据库的方案上做了如下改变 - 原方案每次获取ID都得读写一次数据库造成数据库压力大。改为利用proxy server批量获取每次获取一个segment(step决定大小)号段的值。用完之后再去数据库获取新的号段可以大大的减轻数据库的压力。 - 各个业务不同的发号需求用biz_tag字段来区分每个biz-tag的ID获取相互隔离互不影响。如果以后有性能需求需要对数据库扩容不需要上述描述的复杂的扩容操作只需要对biz_tag分库分表就行表结构设计-------------------------------------------------------------------------------------- | Field | Type | Null | Key | Default | Extra | -------------------------------------------------------------------------------------- | biz_tag | varchar(128) | NO | PRI | | | | max_id | bigint(20) | NO | | 1 | | | step | int(11) | NO | | NULL | | | desc | varchar(256) | YES | | NULL | | | update_time | timestamp | NO | | CURRENT_TIMESTAMP | on update CURRENT_TIMESTAMP | --------------------------------------------------------------------------------------Leaf­snowflake方案Leafsnowflake是在雪花算法上改进来的引用官网技术博客介绍Leaf-snowflake方案完全沿用snowflake方案的bit位设计即是“1411012”的方式组装ID号。对于workerID的分配当服务集群数量较小的情况下完全可以手动配置。Leaf服务规模较大动手配置成本太高。所以使用Zookeeper持久顺序节点的特性自动对snowflake节点配置wokerID。Leaf-snowflake是按照下面几个步骤启动的启动Leaf-snowflake服务连接Zookeeper在leaf_forever父节点下检查自己是否已经注册过是否有该顺序子节点。如果有注册过直接取回自己的workerIDzk顺序节点生成的int类型ID号启动服务。如果没有注册过就在该父节点下面创建一个持久顺序节点创建成功后取回顺序号当做自己的workerID号启动服务。这种方案解决了前面提到的雪花算法的缺陷官网没解释不过Leaf­snowflake对其进行改进官网的流程图详细介绍请看官网https://tech.meituan.com/2017/04/21/mt-leaf.html3.7、 滴滴TinyIDTinyid是用Java开发的一款分布式id生成系统基于数据库号段算法实现。Tinyid扩展了leaf-segment算法支持了多数据库和tinyid-clientTinyid也是基于号段算法实现系统实现图如下优点方便集成有成熟的方案和解决实现缺点依赖 DB的稳定性需要采用集群主从备份的方式提高 DB的可用性原文https://www.cnblogs.com/mzq123/p/16840232.html