电子商务网站建设的概要设计,大学网站开发策划,wordpress外观设置,网站建设职业怎么样之前我们讲到了分库分表#xff0c;现在考虑这样一个问题#xff1a;在单库单表时#xff0c;业务 ID 可以依赖数据库的自增主键实现#xff0c;现在我们把存储拆分到了多处#xff0c;如果还是用数据库的自增主键#xff0c;势必会导致主键重复。
那么我们应该如何解决…之前我们讲到了分库分表现在考虑这样一个问题在单库单表时业务 ID 可以依赖数据库的自增主键实现现在我们把存储拆分到了多处如果还是用数据库的自增主键势必会导致主键重复。
那么我们应该如何解决主键问题呢本文就来看下生成唯一主键相关的知识。
生成主键有哪些方案
如果用最简单的方式来生成唯一主键可以怎么做呢一个最直接的方案是使用单独的自增数据表存储拆分以后创建一张单点的数据表比如现在需要生成订单 ID我们创建下面一张数据表
CREATE TABLE IF NOT EXISTS order_sequence(order_id INT UNSIGNED AUTO_INCREMENT,PRIMARY KEY ( order_id )
)ENGINEInnoDB DEFAULT CHARSETutf8;当每次需要生成唯一 ID 时就去对应的这张数据表里新增一条记录使用返回的自增主键 ID 作为业务 ID。
这个方案实现起来简单但问题也很明显。首先性能无法保证在并发比较高的情况下如果通过这样的数据表来创建自增 ID生成主键很容易成为性能瓶颈。第二存在单点故障如果生成自增 ID 的数据库挂掉那么会直接影响创建功能。
在实际开发中实现唯一主键有多种方案可选下面介绍几种常见的实现思路分别是使用 UUID、使用 Snowflake 算法以及配置自增区间在内存中分配的方式。
使用 UUID 能否实现
UUID 大家都很熟悉在 Java 语言中就内置了 UUID 的工具类实现可以很容易地生成一个 UUID
public String getUUID(){UUID uuidUUID.randomUUID();return uuid.toString();
}那么是否可以应用 UUID 生成唯一主键呢
UUID 虽然很好地满足了全局唯一这个要求但是并不适合作为数据库存储的唯一主键。我们输出一个 UUID 看一下比如135c8321-bf10-46d3-9980-19ba588554e8这是一个 36 位的字符串。
首先 UUID 作为数据库主键太长了会导致比较大的存储开销另外一个UUID 是无序的如果使用 UUID 作为主键会降低数据库的写入性能。
以 MySQL 为例MySQL 建议使用自增 ID 作为主键我们知道 MySQL InnoDB 引擎支持索引底层数据结构是 B 树如果主键为自增 ID 的话那么 MySQL 可以按照磁盘的顺序去写入如果主键是非自增 ID在写入时需要增加很多额外的数据移动将每次插入的数据放到合适的位置上导致出现页分裂降低数据写入的性能。
基于 Snowflake 算法
Snowflake 是 Twitter 开源的分布式 ID 生成算法由 64 位的二进制数字组成一共分为 4 部分下面是示意图 其中 第 1 位默认不使用作为符号位总是 0保证数值是正数 41 位时间戳表示毫秒数我们计算一下41 位数字可以表示 241 毫秒换算成年结果是 69 年多一点一般来说这个数字足够在业务中使用了 10 位工作机器 ID支持 210 也就是 1024 个节点 12 位序列号作为当前时间戳和机器下的流水号每个节点每毫秒内支持 212 的区间也就是 4096 个 ID换算成秒相当于可以允许 409 万的 QPS如果在这个区间内超出了 4096则等待至下一毫秒计算。
Twitter 给出了 Snowflake 算法的示例具体实现应用了大量的位运算可以点击具体的代码库查看。
Snowflake 算法可以作为一个单独的服务部署在多台机器上产生的 ID 是趋势递增的不需要依赖数据库等第三方系统并且性能非常高理论上 409 万的 QPS 是一个非常可观的数字可以满足大部分业务场景其中的机器 ID 部分可以根据业务特点来分配比较灵活。
Snowflake 算法优点很多但有一个不足那就是存在时钟回拨问题时钟回拨是什么呢
因为服务器的本地时钟并不是绝对准确的在一些业务场景中比如在电商的整点抢购中为了防止不同用户访问的服务器时间不同则需要保持服务器时间的同步。为了确保时间准确会通过 NTP 的机制来进行校对NTPNetwork Time Protocol指的是网络时间协议用来同步网络中各个计算机的时间。
如果服务器在同步 NTP 时出现不一致出现时钟回拨那么 SnowFlake 在计算中可能出现重复 ID。除了 NTP 同步闰秒也会导致服务器出现时钟回拨不过时钟回拨是小概率事件在并发比较低的情况下一般可以忽略。关于如何解决时钟回拨问题可以进行延迟等待直到服务器时间追上来为止感兴趣的同学可以查阅相关资料了解下。
数据库维护区间分配
下面我们介绍一种基于数据库维护自增ID区间结合内存分配的策略这也是淘宝的 TDDL 等数据库中间件使用的主键生成策略。
使用这种方式的步骤如下。 首先在数据库中创建 sequence 表其中的每一行用于记录某个业务主键当前已经被占用的 ID 区间的最大值。
sequence 表的主要字段是 name 和 value其中 name 是当前业务序列的名称value 存储已经分配出去的 ID 最大值。
CREATE TABLE sequence (id bigint(20) NOT NULL AUTO_INCREMENT COMMENT Id,name varchar(64) NOT NULL COMMENT sequence name,value bigint(32) NOT NULL COMMENT sequence current value,PRIMARY KEY (id),UNIQUE KEY unique_name (name)
) ENGINEInnoDB DEFAULT CHARSETutf8; 接下来插入一条行记录当需要获取主键时每台服务器主机从数据表中取对应的 ID 区间缓存在本地同时更新 sequence 表中的 value 最大值记录。
现在我们新建一条记录比如设置一条 order 更新的规则插入一行记录如下
INSERT INTO sequence (name,value) values(order_sequence,1000);i当服务器在获取主键增长区段时首先访问对应数据库的 sequence 表更新对应的记录占用一个对应的区间。比如我们这里设置步长为 200原先的 value 值为 1000更新后的 value 就变为了 1200。 取到对应的 ID 区间后在服务器内部进行分配涉及的并发问题可以依赖乐观锁等机制解决。
有了对应的 ID 增长区间在本地就可以使用 AtomicInteger 等方式进行 ID 分配。
不同的机器在相同时间内分配出去的 ID 可能不同这种方式生成的唯一 ID不保证严格的时间序递增但是可以保证整体的趋势递增在实际生产中有比较多的应用。
为了防止单点故障sequence 表所在的数据库通常会配置多个从库实现高可用。
除了上面的几种方案实际开发中还可以应用 Redis 作为解决方案即通过 Redis Incr 命令来实现感兴趣的同学可以去了解一下。
总结
本文主要分享了实现唯一主键的几种思路也就是我们通常说的分布式发号器主要有使用 UUID、使用 Snowflake 算法以及数据库存储区间结合内存分配的方式。
现在再来总结一下一个生产环境中可用的主键生成器应该具备哪些特性呢
首先是生成的主键必须全局唯一不能出现重复 ID这对于主键来说是最基础的需求。
第二需要满足有序性也就是单调递增或者也可以满足一段时间内的递增这是出于业务上的考虑。一方面在写入数据库时有序的主键可以保证写入性能另一方面很多时候都会使用主键来进行一些业务处理比如通过主键排序等。如果生成的主键是乱序的就无法体现一段时间内的创建顺序。
再一个是性能要求要求尽可能快的生成主键同时满足高可用。因为存储拆分后业务写入强依赖主键生成服务假设生成主键的服务不可用订单新增、商品创建等都会阻塞这在实际项目中是绝对不可以接受的。
你可以联系实际工作在你负责的项目中涉及唯一主键的模块是否也考虑了这些特性以及具体是如何实现的欢迎留言分享。