个人博客网站设计模板技能培训学校
news/
2025/9/29 23:26:40/
文章来源:
个人博客网站设计模板,技能培训学校,c 企业网站开发,中安(深圳)建设公司成员一、为什么要用分布式 ID#xff1f;
在说分布式 ID 的具体实现之前#xff0c;我们来简单分析一下为什么用分布式 ID#xff1f;分布式 ID 应该满足哪些特征#xff1f;
1、什么是分布式 ID#xff1f;
拿 MySQL 数据库举个栗子#xff1a;
在我们业务数据量不大的时…一、为什么要用分布式 ID
在说分布式 ID 的具体实现之前我们来简单分析一下为什么用分布式 ID分布式 ID 应该满足哪些特征
1、什么是分布式 ID
拿 MySQL 数据库举个栗子
在我们业务数据量不大的时候单库单表完全可以支撑现有业务数据再大一点搞个 MySQL 主从同步读写分离也能对付。
但随着数据日渐增长主从同步也扛不住了就需要对数据库进行分库分表但分库分表后需要有一个唯一 ID 来标识一条数据数据库的自增 ID 显然不能满足需求特别一点的如订单、优惠券也都需要有唯一 ID做标识。此时一个能够生成全局唯一 ID的系统是非常必要的。那么这个全局唯一 ID就叫分布式 ID。 2、那么分布式 ID 需要满足那些条件
全局唯一必须保证 ID 是全局性唯一的基本要求高性能高可用低延时ID 生成响应要块否则反倒会成为业务瓶颈高可用100%的可用性是骗人的但是也要无限接近于 100%的可用性好接入要秉着拿来即用的设计原则在系统设计和实现上要尽可能的简单趋势递增最好趋势递增这个要求就得看具体业务场景了一般不严格要求分布式 ID 都有哪些生成方式
今天主要分析一下以下 9 种分布式 ID 生成器方式以及优缺点
UUID数据库自增 ID数据库多主模式号段模式Redis雪花算法SnowFlake滴滴出品TinyID百度 Uidgenerator美团Leaf
那么它们都是如何实现以及各自有什么优缺点 1、基于 UUID
在 Java 的世界里想要得到一个具有唯一性的 ID首先被想到可能就是UUID毕竟它有着全球唯一的特性。那么UUID可以做分布式 ID吗答案是可以的但是并不推荐
public static void main(String[] args) { String uuid UUID.randomUUID().toString().replaceAll(-,);System.out.println(uuid);}UUID的生成简单到只有一行代码输出结果 c2b8c2b9e46c47e3b30dca3b0d447718但 UUID 却并不适用于实际的业务需求。像用作订单号UUID这样的字符串没有丝毫的意义看不出和订单相关的有用信息而对于数据库来说用作业务主键 ID它不仅是太长还是字符串存储性能差查询也很耗时所以不推荐用作分布式 ID。
优点
生成足够简单本地生成无网络消耗具有唯一性
缺点
无序的字符串不具备趋势自增特性没有具体的业务含义长度过长 16 字节 128 位36 位长度的字符串存储以及查询对 MySQL 的性能消耗较大MySQL 官方明确建议主键要尽量越短越好作为数据库主键 UUID 的无序性会导致数据位置频繁变动严重影响性能。
2、基于数据库自增 ID
基于数据库的auto_increment自增 ID 完全可以充当分布式 ID具体实现需要一个单独的 MySQL 实例用来生成 ID建表结构如下
CREATE DATABASE SEQ_ID;
CREATE TABLE SEQID.SEQUENCE_ID (id bigint(20) unsigned NOT NULL auto_increment, value char(10) NOT NULL default ,PRIMARY KEY (id),
) ENGINEMyISAM;insert into SEQUENCE_ID(value) VALUES (values);当我们需要一个 ID 的时候向表中插入一条记录返回主键 ID但这种方式有一个比较致命的缺点访问量激增时 MySQL 本身就是系统的瓶颈用它来实现分布式服务风险比较大不推荐
优点
实现简单ID 单调自增数值类型查询速度快
缺点
DB 单点存在宕机风险无法扛住高并发场景
3、基于数据库集群模式
前边说了单点数据库方式不可取那对上边的方式做一些高可用优化换成主从模式集群。害怕一个主节点挂掉没法用那就做双主模式集群也就是两个 Mysql 实例都能单独的生产自增 ID。
那这样还会有个问题两个 MySQL 实例的自增 ID 都从 1 开始会生成重复的 ID 怎么办
解决方案设置起始值和自增步长
MySQL_1 配置
set auto_increment_offset 1; -- 起始值
set auto_increment_increment 2; -- 步长MySQL_2 配置
set auto_increment_offset 2; -- 起始值
set auto_increment_increment 2; -- 步长这样两个 MySQL 实例的自增 ID 分别就是 1、3、5、7、9 2、4、6、8、10 那如果集群后的性能还是扛不住高并发咋办就要进行 MySQL 扩容增加节点这是一个比较麻烦的事。
水平扩展的数据库集群有利于解决数据库单点压力的问题同时为了 ID 生成特性将自增步长按照机器数量来设置。
增加第三台MySQL实例需要人工修改一、二两台MySQL 实例的起始值和步长把第三台机器的 ID起始生成位置设定在比现有最大自增 ID的位置远一些但必须在一、二两台MySQL 实例ID 还没有增长到第三台 MySQL 实例的起始 ID值的时候否则自增 ID就要出现重复了必要时可能还需要停机修改。
优点
解决 DB 单点问题
缺点
不利于后续扩容而且实际上单个数据库自身压力还是大依旧无法满足高并发场景。
4、基于数据库的号段模式
号段模式是当下分布式 ID 生成器的主流实现方式之一号段模式可以理解为从数据库批量的获取自增 ID每次从数据库取出一个号段范围例如 (1,1000] 代表 1000 个 ID具体的业务服务将本号段生成 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 是一个乐观锁每次都更新 version保证并发时数据的正确性
idbiz_typemax_idstepversion1101100020000
等这批号段 ID 用完再次向数据库申请新号段对max_id字段做一次update操作update max_id max_id stepupdate 成功则说明新号段获取成功新的号段范围是(max_id ,max_id step]。
update id_generator set max_id #{max_idstep}, version version 1 where version # {version} and biz_type XXX由于多业务端可能同时操作所以采用版本号version乐观锁方式更新这种分布式 ID生成方式不强依赖于数据库不会频繁的访问数据库对数据库的压力小很多。
5、基于 Redis 模式
Redis也同样可以实现原理就是利用redis的 incr命令实现 ID 的原子性自增。
127.0.0.1:6379 set seq_id 1 // 初始化自增 ID 为 1
OK
127.0.0.1:6379 incr seq_id // 增加 1并返回递增后的数值
(integer) 2用redis实现需要注意一点要考虑到 redis 持久化的问题。redis有两种持久化方式RDB和AOF RDB会定时打一个快照进行持久化假如连续自增但redis没及时持久化而这会 Redis 挂掉了重启 Redis 后会出现 ID 重复的情况。 AOF会对每条写命令进行持久化即使Redis挂掉了也不会出现 ID 重复的情况但由于 incr 命令的特殊性会导致Redis重启恢复的数据时间过长。
6、基于雪花算法Snowflake模式
雪花算法Snowflake是 twitter 公司内部分布式项目采用的 ID 生成算法开源后广受国内大厂的好评在该算法影响下各大公司相继开发出各具特色的分布式生成器。
Snowflake生成的是 Long 类型的 ID一个 Long 类型占 8 个字节每个字节占 8 比特也就是说一个 Long 类型占 64 个比特。
Snowflake ID 组成结构正数位占 1 比特 时间戳占 41 比特 机器 ID占 5 比特 数据中心占 5 比特 自增值占 12 比特总共 64 比特组成的一个 Long 类型。
第一个 bit 位1bitJava 中 long 的最高位是符号位代表正负正数是 0负数是 1一般生成 ID 都为正数所以默认为 0。时间戳部分41bit毫秒级的时间不建议存当前时间戳而是用当前时间戳 - 固定开始时间戳的差值可以使产生的 ID 从更小的值开始41 位的时间戳可以使用 69 年(1L 41) / (1000L * 60 * 60 * 24 * 365) 69 年工作机器 id10bit也被叫做workId这个可以灵活配置机房或者机器号组合都可以。序列号部分12bit自增值支持同一毫秒内同一个节点可以生成 4096 个 ID
根据这个算法的逻辑只需要将这个算法用 Java 语言实现出来封装为一个工具方法那么各个业务应用可以直接使用该工具方法来获取分布式 ID只需保证每个业务应用有自己的工作机器 id 即可而不需要单独去搭建一个获取分布式 ID 的应用
Java 版本的Snowflake算法实现
/*** Twitter 的 SnowFlake 算法,使用 SnowFlake 算法生成一个整数然后转化为 62 进制变成一个短地址 URL** https://github.com/beyondfengyu/SnowFlake*/
public class SnowFlakeShortUrl {/*** 起始的时间戳*/private final static long START_TIMESTAMP 1480166465631L;/*** 每一部分占用的位数*/private final static long SEQUENCE_BIT 12; //序列号占用的位数private final static long MACHINE_BIT 5; //机器标识占用的位数private final static long DATA_CENTER_BIT 5; //数据中心占用的位数/*** 每一部分的最大值*/private final static long MAX_SEQUENCE -1L ^ (-1L SEQUENCE_BIT);private final static long MAX_MACHINE_NUM -1L ^ (-1L MACHINE_BIT);private final static long MAX_DATA_CENTER_NUM -1L ^ (-1L DATA_CENTER_BIT);/*** 每一部分向左的位移*/private final static long MACHINE_LEFT SEQUENCE_BIT;private final static long DATA_CENTER_LEFT SEQUENCE_BIT MACHINE_BIT;private final static long TIMESTAMP_LEFT DATA_CENTER_LEFT DATA_CENTER_BIT;private long dataCenterId; //数据中心private long machineId; //机器标识private long sequence 0L; //序列号private long lastTimeStamp -1L; //上一次时间戳private long getNextMill() {long mill getNewTimeStamp();while (mill lastTimeStamp) {mill getNewTimeStamp();}return mill;}private long getNewTimeStamp() {return System.currentTimeMillis();}/*** 根据指定的数据中心 ID 和机器标志 ID 生成指定的序列号** param dataCenterId 数据中心 ID* param machineId 机器标志 ID*/public SnowFlakeShortUrl(long dataCenterId, long machineId) {if (dataCenterId MAX_DATA_CENTER_NUM || dataCenterId 0) {throw new IllegalArgumentException(DtaCenterId cant be greater than MAX_DATA_CENTER_NUM or less than 0);}if (machineId MAX_MACHINE_NUM || machineId 0) {throw new IllegalArgumentException(MachineId cant be greater than MAX_MACHINE_NUM or less than 0);}this.dataCenterId dataCenterId;this.machineId machineId;}/*** 产生下一个 ID** return*/public synchronized long nextId() {long currTimeStamp getNewTimeStamp();if (currTimeStamp lastTimeStamp) {throw new RuntimeException(Clock moved backwards. Refusing to generate id);}if (currTimeStamp lastTimeStamp) {//相同毫秒内序列号自增sequence (sequence 1) MAX_SEQUENCE;//同一毫秒的序列数已经达到最大if (sequence 0L) {currTimeStamp getNextMill();}} else {//不同毫秒内序列号置为 0sequence 0L;}lastTimeStamp currTimeStamp;return (currTimeStamp - START_TIMESTAMP) TIMESTAMP_LEFT //时间戳部分| dataCenterId DATA_CENTER_LEFT //数据中心部分| machineId MACHINE_LEFT //机器标识部分| sequence; //序列号部分}public static void main(String[] args) {SnowFlakeShortUrl snowFlake new SnowFlakeShortUrl(2, 3);for (int i 0; i (1 4); i) {//10 进制System.out.println(snowFlake.nextId());}}
}
7、百度uid-generator
uid-generator是由百度技术部开发项目 GitHub 地址 https://github.com/baidu/uid-generator
uid-generator是基于Snowflake算法实现的与原始的snowflake算法不同在于uid-generator支持自定义时间戳、工作机器 ID和 序列号 等各部分的位数而且uid-generator中采用用户自定义workId的生成策略。
uid-generator需要与数据库配合使用需要新增一个WORKER_NODE表。当应用启动时会向数据库表中去插入一条数据插入成功后返回的自增 ID 就是该机器的workId数据由 hostport 组成。
对于uid-generator ID 组成结构
workId占用了 22 个 bit 位时间占用了 28 个 bit 位序列化占用了 13 个 bit 位需要注意的是和原始的snowflake不太一样时间的单位是秒而不是毫秒workId也不一样而且同一应用每次重启就会消费一个workId。 参考文献 https://github.com/baidu/uid-generator/blob/master/README.zh_cn.md 8、美团Leaf
Leaf由美团开发github 地址https://github.com/Meituan-Dianping/Leaf
Leaf同时支持号段模式和snowflake算法模式可以切换使用。
号段模式
先导入源码 https://github.com/Meituan-Dianping/Leaf 在建一张表leaf_alloc
DROP TABLE IF EXISTS leaf_alloc;CREATE TABLE leaf_alloc (biz_tag varchar(128) NOT NULL DEFAULT COMMENT 业务 key,max_id bigint(20) NOT NULL DEFAULT 1 COMMENT 当前已经分配了的最大 id,step int(11) NOT NULL COMMENT 初始步长也是动态调整的最小步长,description varchar(256) DEFAULT NULL COMMENT 业务 key 的描述,update_time timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 数据库维护的更新时间,PRIMARY KEY (biz_tag)
) ENGINEInnoDB;然后在项目中开启号段模式配置对应的数据库信息并关闭snowflake模式
leaf.namecom.sankuai.leaf.opensource.test
leaf.segment.enabletrue
leaf.jdbc.urljdbc:mysql://localhost:3306/leaf_test?useUnicodetruecharacterEncodingutf8characterSetResultsutf8
leaf.jdbc.usernameroot
leaf.jdbc.passwordrootleaf.snowflake.enablefalse
#leaf.snowflake.zk.address
#leaf.snowflake.port启动leaf-server 模块的 LeafServerApplication项目就跑起来了
号段模式获取分布式自增 ID 的测试 url http//localhost8080/api/segment/get/leaf-segment-test
监控号段模式http://localhost:8080/cache
snowflake 模式
Leaf的 snowflake 模式依赖于ZooKeeper不同于原始 snowflake算法也主要是在workId的生成上Leaf中workId是基于ZooKeeper的顺序 Id 来生成的每个应用在使用Leaf-snowflake时启动时都会都在Zookeeper中生成一个顺序 Id相当于一台机器对应一个顺序节点也就是一个workId。
leaf.snowflake.enabletrue
leaf.snowflake.zk.address127.0.0.1
leaf.snowflake.port2181snowflake 模式获取分布式自增 ID 的测试 urlhttp://localhost:8080/api/snowflake/get/test
9、滴滴Tinyid
Tinyid由滴滴开发Github 地址https://github.com/didi/tinyid。
Tinyid是基于号段模式原理实现的与Leaf如出一辙每个服务获取一个号段1000,2000]、2000,3000]、3000,4000] Tinyid提供http和tinyid-client两种方式接入
Http 方式接入
1导入 Tinyid 源码
git clone https://github.com/didi/tinyid.git
2创建数据表
CREATE TABLE tiny_id_info (id bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT 自增主键,biz_type varchar(63) NOT NULL DEFAULT COMMENT 业务类型唯一,begin_id bigint(20) NOT NULL DEFAULT 0 COMMENT 开始 id仅记录初始值无其他含义。初始化时 begin_id 和 max_id 应相同,max_id bigint(20) NOT NULL DEFAULT 0 COMMENT 当前最大 id,step int(11) DEFAULT 0 COMMENT 步长,delta int(11) NOT NULL DEFAULT 1 COMMENT 每次 id 增量,remainder int(11) NOT NULL DEFAULT 0 COMMENT 余数,create_time timestamp NOT NULL DEFAULT 2010-01-01 00:00:00 COMMENT 创建时间,update_time timestamp NOT NULL DEFAULT 2010-01-01 00:00:00 COMMENT 更新时间,version bigint(20) NOT NULL DEFAULT 0 COMMENT 版本号,PRIMARY KEY (id),UNIQUE KEY uniq_biz_type (biz_type)
) ENGINEInnoDB AUTO_INCREMENT1 DEFAULT CHARSETutf8 COMMENT id 信息表;CREATE TABLE tiny_id_token (id int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT 自增 id,token varchar(255) NOT NULL DEFAULT COMMENT token,biz_type varchar(63) NOT NULL DEFAULT COMMENT 此 token 可访问的业务类型标识,remark varchar(255) NOT NULL DEFAULT COMMENT 备注,create_time timestamp NOT NULL DEFAULT 2010-01-01 00:00:00 COMMENT 创建时间,update_time timestamp NOT NULL DEFAULT 2010-01-01 00:00:00 COMMENT 更新时间,PRIMARY KEY (id)
) ENGINEInnoDB AUTO_INCREMENT1 DEFAULT CHARSETutf8 COMMENT token 信息表;INSERT INTO tiny_id_info (id, biz_type, begin_id, max_id, step, delta, remainder, create_time, update_time, version)
VALUES(1, test, 1, 1, 100000, 1, 0, 2018-07-21 23:52:58, 2018-07-22 23:19:27, 1);INSERT INTO tiny_id_info (id, biz_type, begin_id, max_id, step, delta, remainder, create_time, update_time, version)
VALUES(2, test_odd, 1, 1, 100000, 2, 1, 2018-07-21 23:52:58, 2018-07-23 00:39:24, 3);INSERT INTO tiny_id_token (id, token, biz_type, remark, create_time, update_time)
VALUES(1, 0f673adf80504e2eaa552f5d791b644c, test, 1, 2017-12-14 16:36:46, 2017-12-14 16:36:48);INSERT INTO tiny_id_token (id, token, biz_type, remark, create_time, update_time)
VALUES(2, 0f673adf80504e2eaa552f5d791b644c, test_odd, 1, 2017-12-14 16:36:46, 2017-12-14 16:36:48);3配置数据库
datasource.tinyid.namesprimary
datasource.tinyid.primary.driver-class-namecom.mysql.jdbc.Driver
datasource.tinyid.primary.urljdbc:mysql://ip:port/databaseName?autoReconnecttrueuseUnicodetruecharacterEncodingUTF-8
datasource.tinyid.primary.usernameroot
datasource.tinyid.primary.password1234564启动tinyid-server后测试
获取分布式自增 ID: http://localhost:9999/tinyid/id/nextIdSimple?bizTypetesttoken0f673adf80504e2eaa552f5d791b644c
返回结果: 3批量获取分布式自增 ID:
http://localhost:9999/tinyid/id/nextIdSimple?bizTypetesttoken0f673adf80504e2eaa552f5d791b644cbatchSize10
返回结果: 4,5,6,7,8,9,10,11,12,13Java 客户端方式接入
重复 Http 方式的23操作
引入依赖 dependencygroupIdcom.xiaoju.uemc.tinyid/groupIdartifactIdtinyid-client/artifactIdversion${tinyid.version}/version/dependency配置文件
tinyid.server localhost:9999
tinyid.token 0f673adf80504e2eaa552f5d791b644ctest 、tinyid.token是在数据库表中预先插入的数据test 是具体业务类型tinyid.token表示可访问的业务类型
// 获取单个分布式自增 ID
Long id TinyId . nextId( test );// 按需批量分布式自增 ID
List Long ids TinyId . nextId( test , 10 );
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/922339.shtml
如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!