首页 > 其他分享 >分布式主键生成设计策略

分布式主键生成设计策略

时间:2022-10-09 16:36:41浏览次数:82  
标签:ID MySQL 主键 生成 节点 分布式


1 写作目的

这几天被虾皮裁员的消息刷屏了,实话实说互联网的行情确实很差,各处都在裁员。而且最近在忙试用期转正答辩,还不错,光荣的成为了一个大厂的正式员工,很庆幸在这么不稳定的情况下还能有自己的一寸方土。还扯别的干什么呢?开卷吧。

注意:本文只讲分布式主键的设计实现原理,不讲具体代码的实现细节。默认都不是单节点。

2 为什么要用分布式主键

在我们业务量不大的时候,单库单表完全可以支持现在的业务,数据再大一点读写分离也算OK。但是随着数据量的增长,单库单表终究是抗不住的。那就需要分库分表。分库分表后肯定不能依赖分表中的自增主键。因此需要一个 生成全局唯一ID的 主键生成策略。

3 分布式主键的基本要求

全局唯一:不管什么主键,都需要全局唯一。
高性能高可用:分布式主键服务本身就是一个底层的服务,很多服务都依赖于 这个服务,如果底层服务都不稳定,那么上游的服务就更谈不上稳定。
递增:大部分数据存储使用MySQL存储数据,为了快速存储和检索,分布式主键生成策略总趋势要求是递增的。

4 常见的分布式主键生成策略

4.1 UUID

在Java中自带的UUID就具有唯一性。如下面代码所示。生成的结果不是数字,是字符串。那

public static void main(String[] args) {
String uuid = UUID.randomUUID().toString().replaceAll("-","");
System.out.println(uuid);//e48126eb0e6646638b043fca85f38818
}

优点

  • 简单,没有网络消耗。

缺点

  • 不保证大趋势递增,不利于检索,对索引的构建和维护成本比较大。
  • 长度过长,不利于存储。
  • 没有具体的业务含义。

4.2 MySQL

4.2.1 自增主键

4.2.1.1单点MySQL(单Master)

基于数据库的自增主键充当分布式ID服务器。
具体实现
1)创建一个SEQUENCE_ID表(id,value)
2)每次请求该分布式主键主键服务时向SEQUENCE_ID 插入一条数据,并返回自增主键作为分布式主键。

优点

  • 实现简单,自增有序。

缺点

  • DB单点存在宕机风险,扛不住高并发场景。
4.2.1.2集群MySQL(多Master)

集群也是基于数据库的自增实现分布式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; -- 步长

设计方式如下图所示。那么整体还是有序的。不过每一个DB承担的流量就是1/2。

分布式主键生成设计策略_数据库


优点

  • 多Master相比单Master会好很多

缺点

  • 不利于后期扩容

4.2.2 区间号段(☆☆☆☆☆)

我们以单节点为例,在数据库中维护一个SEQUENCE_ID表。表中定义的是一个order主键生成规则和user主键生成规则。简单对表做一下解释,biz_code表示一种主键,max_id表示已经分配的最大ID,step表示步长,用于每次生成一批主键。

biz_code

max_id

step

desc

update_time

order

11000

1000

订单表

2022-09-06

user

1000

100

用户表

2022-09-09

在上面数据库的原始数据之上,此时上线了3个节点的订单服务。
当用户创建订单时,Order_Node1节点会请求主键服务器生成一批主键,此时数据库表记录为

biz_code

max_id

step

desc

update_time

order

11000

1000

订单表

2022-09-06

[max_id + 1 , max_id + step]这批主键会返回给Order_Node1,即[11001,12000],此时会更新表的数据为

biz_code

max_id

step

desc

update_time

order

12000

1000

订单表

2022-09-07

每个节点的内存中存的是一批主键ID,当时候完毕后再去申请一批数据,效率极高

分布式主键生成设计策略_数据库_02

优点

  • 并发压力不会再MySQL主键服务器侧。
  • 容灾性好,我一次给你一批,即使主键服务器挂了,Node内存中的主键可以继续使用,Nod不应该order_node对外提供服务。

缺点

  • 每当请求主键服务器申请一批主键时,TP999数据会偶尔出现尖刺。因为当Clinet大量的请求到Order_Node时因没有主键去申请主键时会导致这一批的请求时间会hang住。
  • 分布式主键生成设计策略_主键_03

双Buffer优化

针对 区间号段 中出现尖刺的情况,使用双Buffer进行优化。如下图所示,我首先申请一批主键,当使用到该批次的10%时,后台起一个线程申请下一批主键,这样一种预加载的情况可以时主键串联起来,有效解决上面的请求RT抖动情况。

分布式主键生成设计策略_java_04

4.3 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

优点

  • 极大降低了主键服务器MySQL的流量

缺点

  • Redis如果使用RDB进行持久化,那么数据会存在丢失的风险。即 incr 后的数据丢失,则再次生成的主键会重复
  • 依赖第三方服务,系统的复杂性会增加

4.4 SnowFlake雪花算法

算法简介

雪花算法(Snowflake)是twitter公司内部分布式项目采用的ID生成算法,开源后广受国内大厂的好评,在该算法影响下各大公司相继开发出各具特色的分布式生成器,不过设计思路还是相同和想通的。

分布式主键生成设计策略_分布式_05


如上图(图片源自网络,如有侵权联系删除)所示。整个ID由4部分组成。

  • 第一个bit位(1bit):一般生成ID都为正数,所以默认为0。
  • 时间戳部分(41bit):毫秒级的时间,不建议存当前时间戳,而是用(当前时间戳 - 固定开始时间戳)的差值,可以使产生的ID从更小的值开始;41位的时间戳可以使用69年,(1L << 41) / (1000L * 60 * 60 * 24 * 365) = 69年
  • 工作机器id(10bit):也被叫做workId,这个可以灵活配置,机房或者机器号组合都可以。
  • 序列号部分(12bit),自增值支持同一毫秒内同一个节点可以生成4096个ID

核心思想

多种唯一的值 进行拼接,使其更唯一。
Snowflake的第二部分是时间戳,时间戳是唯一的,但是如果多个节点同时生产则会参数相同的时间戳,那怎么办?接着加唯一的值,工作机器的ID唯一呀。那么就出现了第三部分的值。但是如果在一个进程中可能两个线程同时请求,那么会产生相同的(时间戳+工作机器ID),那就继续加唯一值,在加上最后的序列号,从而保证全局唯一。

  • 第一个bit位(1bit):一般生成ID都为正数,所以默认为0。
  • 时间戳部分(41bit):保证单节点下请求唯一,但是多节点内请求会生成相同的时间戳。
  • 工作机器id(10bit):时间戳 + 工作机器 保证多节点同同时请求可以生成不同的主键ID。但是多节点下多线程还是存在重复。
  • 序列号部分(12bit),解决多节点下多线程生成ID重复的问题。

其中这种思想还挺常见的。
比如我们使用配置文件或者注册中心都有namespce的概念,namespcae和配置文件名称公共确定一个文件。再比如java中的包名。
都是通过多个唯一拼接成 一个唯一。

优点

  • 高性能,生成的主键贼多

缺点

  • 生成的主键之间跨度大,不密集,比如我想看一天的订单量,那么根据主键ID相减就没办法
  • 该算法强依赖时间,存在时钟回拨问题

时钟回拨问题常见的解决方案

其实很多大厂基于雪花算法开源的分布式ID解决方案一方面偏重于64的设计,另一方面偏重于时钟回拨出现后的解决方案优化。

  • 回拨时间很短(<100ms)
    当请求系统时如果发现这个时钟回拨的时间段很小,则可以使其睡一会而到达之前最新的时间节点而继续往下执行。
  • 回拨时间适中(>100ms && < 1s)
    维护最近的一秒(1000毫秒)每毫秒请求的最大值到Redis中(time,maxId),如果发生时钟回拨则取该毫秒的最大值+1。
  • 回拨时间较长(>1s && < 5s)
    如下图所示,当请求Snowflake1时发现时钟回拨,则可以抛一个异常给客户端,客户端则进行其他节点的访问,负载均衡去实现。
  • 分布式主键生成设计策略_分布式_06

  • 回拨时间很长(>5s )
    直接报警下线吧,都不能用了

5 总结

阿里的TDDL和美团的Leaf 有一部分是基于MySQL的区间号段实现的。目前主流的分布式主键方案主要有两种:

  1. 基于MySQL的区间号段实现
  2. 基于雪花算法SnowFlake基础之上的一些开源框架方案

6 参考

​一口气说出9种分布式ID生成方式,面试官有点懵了​

​3小时带你吃透雪花算法Snowflake源码、美团Leaf、数据库自增ID


标签:ID,MySQL,主键,生成,节点,分布式
From: https://blog.51cto.com/cbeann/5740696

相关文章

  • spring boot项目使用mybatis-plus代码生成实例
    前言mybatis-plus官方地址https://baomidou.commybatis-plus是mybatis的增强,不对mybatis做任何改变,涵盖了代码生成,自定义ID生成器,快速实现CRUD,自动分页,逻辑删除等功能......
  • 如何使用 Delphi/Lazarus 代码在 FastReport VCL 中生成二维码?
    FastReportVCL是用于在软件中集成商务智能的现代解决方案。它提供了可视化模板设计器,可以访问最受欢迎的数据源,报告引擎,预览,将过滤器导出为30多种格式,并可以部署到云,Web,电......
  • 《分布式服务架构:原理、设计与实战》 免费电子版
    /*免责声明:全部内容都属于是段友分享,我只是属于整理。**/   /*  写在前边,个人觉得****弄一个积分下载,就是在自掘坟墓。表面上看起来是可以为个人赚积分,实际砍掉分享交......
  • java 生成GUID,可以用UUID类来生成GUID
    全局唯一标识符(GUID,GloballyUniqueIdentifier)是一种由算法生成的二进制长度为128位的数字标识符,一般用16进制表示。在理想情况下,任何计算机和计算机集群都不会生成......
  • Python 生成的页面中文乱码问题
    第一保证程序源文件中的中文的编码格式,如我们把源文件的编码设置成utf8的。reload(sys)sys.setdefaultencoding(‘utf-8’)第二,告诉浏览器,我们需要用什么格式来展示......
  • 分布式事务
     学习一下分布式事务。 这篇文章尽可能的压缩篇幅,不做过多的介绍,像什么是事务就不介绍了。 # # 什么是分布式事务 分布式事务一定来源于多数据源。如果只有一个数据......
  • 生成函数题
    城市规划\[设G(n)表示n个点的有标号无向图数量,我们知道,G(n)=2^{\binom{n}{2}}\\设F(n)表示n个点的有标号无向联通图数量,显然\\\text{我们枚举一号店所在的联通块大小,......
  • spring boot项目使用mybatis-plus代码生成实例
    前言mybatis-plus官方地址https://baomidou.commybatis-plus是mybatis的增强,不对mybatis做任何改变,涵盖了代码生成,自定义ID生成器,快速实现CRUD,自动分页,逻辑删除等功......
  • 详解负载均衡技术及分布式架构
     面对大量用户访问、高并发请求,海量数据,可以使用高性能的服务器、大型数据库,存储设备,高性能Web服务器,采用高效率的编程语言比如(Go,Scala)等,当单机容量达到极限时,我......
  • leetcode 22 括号生成 js 实现
    22.括号生成难度中等数字 n 代表生成括号的对数,请你设计一个函数,用于能够生成所有可能的并且 有效的 括号组合示例1:输入:n=3输出:["((()))","(()())","(()......