分布式ID生成方案总结整理
1、为什么需要分布式ID?
对于单体系统来说,主键ID可能会常用主键自动的方式进行设置,这种ID生成方法在单体项目是可行的,但是对于分布式系统,分库分表之后,就不适应了,比如订单表数据量太大了,分成了多个库,如果还采用数据库主键自增的方式,就会出现在不同库id一致的情况,虽然是不符合业务的
2、业务系统对分布式ID有什么要求?
- 全局唯一性:ID是作为唯一的标识,不能出现重复
- 趋势递增:互联网比较喜欢MySQL数据库,而MySQL数据库默认使用InnoDB存储引擎,其使用的是聚集索引,使用有序的主键ID有利于保证写入的效率
- 单调递增:保证下一个ID大于上一个ID,这种情况可以保证事务版本号,排序等特殊需求实现
- 信息安全:前面说了ID要递增,但是最好不要连续,如果ID是连续的,容易被恶意爬取数据,指定一系列连续的,所以ID递增但是不规则是最好的
3、分布式ID生成方案
- UUID
- 数据库自增
- 号段模式
- Redis实现
- 雪花算法(SnowFlake)
- 百度Uidgenerator
- 美团Leaf
- 滴滴TinyID
3.1 UUID
UUID (Universally Unique Identifier),通用唯一识别码的缩写。UUID的标准型式包含32个16进制数字,以连字号分为五段,形式为8-4-4-4-12的36个字符,示例: 863e254b-ae34-4371-87da-204b71d46a7b
。UUID理论上的总数为1632=2128,约等于3.4 x 10^38。
- 优点
- 性能非常高,本地生成的,不依赖于网络
- 缺点
- 不易存储,16 字节128位,36位长度的字符串
- 信息不安全,基于MAC地址生成UUID的算法可能会造成MAC地址泄露,暴露使用者的位置
- uuid的无序性可能会引起数据位置频繁变动,影响性能
3.2、数据库自增
在分布式环境也可以使用mysql的自增实现分布式ID的生成,如果分库分表了,当然不是简单的设置好auto_increment_increment
和 auto_increment_offset
即可,在分布式系统中我们可以多部署几台机器,每台机器设置不同的初始值,且步长和机器数相等。比如有两台机器。设置步长step为2,Server1的初始值为1(1,3,5,7,9,11…)、Server2的初始值为2(2,4,6,8,10…)。这是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 :当前最大的id
- step :代表号段的步长
- version :版本号,就像MVCC一样,可以理解为乐观锁
等ID都用了,再去数据库获取,然后更改最大值
update id_generator set max_id = #{max_id+step}, 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、 雪花算法(SnowFlake)
Snowflake,雪花算法是由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的唯一ID(long)。默认采用上图字节分配方式:
- 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.md
3.6、 美团Leaf
Leaf这个名字是来自德国哲学家、数学家莱布尼茨的一句话: >There are no two
identical leaves in the world > “世界上没有两片相同的树叶”
Leaf 提供两种生成的ID的方式:号段模式(Leaf-segment)和snowflake模式(Leaf-snowflake)。你可以同时开启两种方式,也可以指定开启某种方式,默认两种方式为关闭状态。
- Leafsegment数据库方案
其实就是前面介绍的号段模式的改进,可以引用美团技术博客的介绍:
第一种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 |
+-------------+--------------+------+-----+-------------------+-----------------------------+
- Leafsnowflake方案
Leafsnowflake是在雪花算法上改进来的,引用官网技术博客介绍:
Leaf-snowflake方案完全沿用snowflake方案的bit位设计,即是“1+41+10+12”的方式组装ID号。对于workerID的分配,当服务集群数量较小的情况下,完全可以手动配置。Leaf服务规模较大,动手配置成本太高。所以使用Zookeeper持久顺序节点的特性自动对snowflake节点配置wokerID。Leaf-snowflake是按照下面几个步骤启动的:
- 启动Leaf-snowflake服务,连接Zookeeper,在leaf_forever父节点下检查自己是否已经注册过(是否有该顺序子节点)。
- 如果有注册过直接取回自己的workerID(zk顺序节点生成的int类型ID号),启动服务。
- 如果没有注册过,就在该父节点下面创建一个持久顺序节点,创建成功后取回顺序号当做自己的workerID号,启动服务。
这种方案解决了前面提到的雪花算法的缺陷,官网没解释,不过Leafsnowflake对其进行改进,官网的流程图
详细介绍请看官网:https://tech.meituan.com/2017/04/21/mt-leaf.html
3.7、 滴滴TinyID
Tinyid是用Java开发的一款分布式id生成系统,基于数据库号段算法实现。Tinyid扩展了leaf-segment算法,支持了多数据库和tinyid-client
Tinyid也是基于号段算法实现,系统实现图如下:
- 优点:方便集成,有成熟的方案和解决实现
- 缺点:依赖 DB的稳定性,需要采用集群主从备份的方式提高 DB的可用性
滴滴TinyID wiki:https://github.com/didi/tinyid/wiki
分布式ID生成方案总结整理的更多相关文章
- 一种基于Orleans的分布式Id生成方案
基于Orleans的分布式Id生成方案,因Orleans的单实例.单线程模型,让这种实现变的简单,贴出一种实现,欢迎大家提出意见 public interface ISequenceNoGenerat ...
- 搞懂分布式技术12:分布式ID生成方案
搞懂分布式技术12:分布式ID生成方案 ## 转自: 58沈剑 架构师之路 2017-06-25 一.需求缘起 几乎所有的业务系统,都有生成一个唯一记录标识的需求,例如: 消息标识:message-i ...
- 分布式id生成方案总结
本文已经收录自 JavaGuide (60k+ Star[Java学习+面试指南] 一份涵盖大部分Java程序员所需要掌握的核心知识.) 本文授权转载自:https://juejin.im/post/ ...
- 分布式ID生成方案
系统唯一ID是设计一个系统的时候常常会遇到的问题,也常常为这个问题而纠结. 生成ID的方法有很多,适应不同的场景.需求以及性能要求.所以有些比较复杂的系统会有多个ID生成的策略. 0. 分布式ID要求 ...
- 分布式ID生成方案汇总
1.目标 1.1.全局唯一 不能出现重复的ID,全局唯一是最基本的要求. 1.2.趋势有序 业务上分页查询需求,排序需求,如果ID直接有序,则不必建立更多的索引,增加查询条件. 而且Mysql Inn ...
- 分布式ID详解(5种分布式ID生成方案)
分布式架构会涉及到分布式全局唯一ID的生成,今天我就来详解分布式全局唯一ID,以及分布式全局唯一ID的实现方案@mikechen 什么是分布式系统唯一ID 在复杂分布式系统中,往往需要对大量的数据和消 ...
- 一线大厂的分布式唯一ID生成方案是什么样的?
本人免费整理了Java高级资料,涵盖了Java.Redis.MongoDB.MySQL.Zookeeper.Spring Cloud.Dubbo高并发分布式等教程,一共30G,需要自己领取.传送门:h ...
- 分库分表的 9种分布式主键ID 生成方案,挺全乎的
<sharding-jdbc 分库分表的 4种分片策略> 中我们介绍了 sharding-jdbc 4种分片策略的使用场景,可以满足基础的分片功能开发,这篇我们来看看分库分表后,应该如何为 ...
- 分布式唯一ID生成方案是什么样的?(转)
一.前言 分布式系统中我们会对一些数据量大的业务进行分拆,如:用户表,订单表.因为数据量巨大一张表无法承接,就会对其进行分库分表. 但一旦涉及到分库分表,就会引申出分布式系统中唯一主键ID的生成问题, ...
随机推荐
- java学习第一天.day03
运行程序数据存储 ASCII Unicode(万国码) A在码表的顺序是65,a在码表的顺序是97,1代表49 变量 定义一个变量声明数据类型是开辟一个空间存储数据,java对数据的定义比较严格,比如 ...
- 开发H5程序或者小程序的时候,后端Web API项目在IISExpress调试中使用IP地址,便于开发调试
在我们开发开发H5程序或者小程序的时候,有时候需要基于内置浏览器或者微信开发者工具进行测试,这个时候可以采用默认的localhost进行访问后端接口,一般来说没什么问题,如果我们需要通过USB基座方式 ...
- Java SE 16 新增特性
Java SE 16 新增特性 作者:Grey 原文地址:Java SE 16 新增特性 源码 源仓库: Github:java_new_features 镜像仓库: GitCode:java_new ...
- [多校 NOIP 联合模拟 20201130 T4] ZZH 的旅行(斜率优化dp,启发式合并,平衡树)
题面 题目背景 因为出题人天天被 ZZH(Zou ZHen) 吊打,所以这场比赛的题目中出现了 ZZH . 简要题面 数据范围 题解 (笔者写两个log的平衡树和启发式合并卡过的,不足为奇) 首先,很 ...
- K8S服务滚动升级
对于Kubernetes集群来说,一个service可能有多个pod,滚动升级(Rolling update)就是指每次更新部分Pod,而不是在同一时刻将该Service下面的所有Pod shutdo ...
- 典型C内存空间分布图
下图是一个典型的C内存空间分布图 这是Linux下32位环境的用户空间内存分布情况 内核空间 :一部分核心软件独立于普通应用程序,运行在较高的特权级别上,驻留在被保护的内存空间上,拥有访问硬件设备的所 ...
- openstack中Neutron组件简解
一.Neutron概述 Neutron 的设计目标是实现"网络即服务(Networking as a Service)".为了达到这一目标,在设计上遵循了基于 SDN 实现网络虚拟 ...
- bat脚本关闭、等待时间、启动程序、
@echo off ::关闭/杀死进程 @taskkill /f /IM Hos.exe ::等待10秒 start /min /w mshta vbscript:setTimeout("w ...
- kubectl插件管理工具krew
文章转载自:https://blog.51cto.com/loong576/2452592 一.k8s核心组件 Kubernetes 主要由以下几个核心组件组成: etcd 保存了整个集群的状态: a ...
- KVM命令参数
# virt-install --help usage: virt-install --name NAME --memory MB STORAGE INSTALL [options] 从指定安装源创建 ...