为什么在京东面试中需要理解分布式ID的重要性?深度解析分布式ID的实现方法与应用场景
今天,我将与大家分享一位读者在京东面试中遇到的一个问题:“为什么需要分布式ID?在你的项目中,你是如何实现的?”在这篇文章中,我将探讨分布式ID的相关知识,包括其基本要求及常见解决方案。
分布式ID的基础知识
ID的定义
在日常开发中,我们需要使用ID对系统中的数据进行唯一标识。例如,用户ID对应唯一的用户,商品ID对应具体的商品,订单ID则对应特定的订单。在生活中,我们也使用各种ID,例如身份证ID对应唯一的个人,地址ID对应特定的地点。
简而言之,ID是数据的唯一标识符。
分布式ID的概念
分布式ID是在分布式系统中使用的ID。与现实生活中的ID不同,分布式ID是计算机系统中的一个概念。
让我举一个分库分表的例子。在某个项目中,我们最初使用的是单机MySQL。然而,项目上线一个月后,随着用户数量的增加,系统数据量迅速膨胀,单机MySQL已经无法支持了,因此需要进行分库分表(推荐使用Sharding-JDBC)。
在分库后,数据分散在不同服务器的数据库中,数据库的自增主键无法满足全局唯一性的要求。**在这种情况下,我们如何为不同的数据节点生成全局唯一的主键呢?**这时,分布式ID的生成就显得尤为重要。
分布式ID的基本要求
分布式ID是分布式系统中的关键组成部分,广泛应用于各个地方。一个基本的分布式ID生成方案需要满足以下要求:
- 全局唯一性:分布式ID必须在全局范围内唯一。
- 高性能:ID的生成速度要快,并且对本地资源的消耗要小。
- 高可用性:生成ID的服务要尽可能保持高可用性。
- 易于使用:应当方便接入,能够快速上手。
此外,理想的分布式ID还应具备以下特性:
- 安全性:ID中不应包含任何敏感信息。
- 有序递增:当将ID存储到数据库时,ID的有序性能够提升数据库的写入效率,且我们还可能通过ID进行排序。
- 业务含义:生成的ID如果能与具体业务相关联,将有助于问题定位和开发透明化。
- 独立部署:分布式ID生成服务应当独立于其他业务相关服务,尽管这会增加网络调用的消耗,但在需求较高的场景下,独立的发号器服务是必要的。
常见的分布式ID解决方案
数据库
主键自增方式
此方式是最简单的,通过关系型数据库的自增主键生成唯一ID。例如,在MySQL中,可以通过以下步骤实现:
- 创建一个数据库表:
CREATE TABLE `sequence_id` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`stub` char(10) NOT NULL DEFAULT '',
PRIMARY KEY (`id`),
UNIQUE KEY `stub` (`stub`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
- 使用
replace into
插入数据并获取ID:
BEGIN;
REPLACE INTO sequence_id (stub) VALUES ('stub');
SELECT LAST_INSERT_ID();
COMMIT;
这种方式的优缺点明显:
- 优点:实现简单,ID有序递增,存储空间消耗小。
- 缺点:并发支持有限,存在数据库单点故障风险(可通过数据库集群解决,但会增加复杂性),ID缺乏业务含义,获取ID时需访问数据库(增加压力,速度较慢)。
数据库号段模式
由于主键自增模式在ID需求较大时存在性能瓶颈,我们可以采用号段模式,批量获取并存储在内存中,从而提高性能。
以MySQL为例,步骤如下:
- 创建数据库表:
CREATE TABLE `sequence_id_generator` (
`id` int(10) NOT NULL,
`current_max_id` bigint(20) NOT NULL COMMENT '当前最大id',
`step` int(10) NOT NULL COMMENT '号段的长度',
`version` int(20) NOT NULL COMMENT '版本号',
`biz_type` int(20) NOT NULL COMMENT '业务类型',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
- 插入一行数据:
INSERT INTO `sequence_id_generator` (`id`, `current_max_id`, `step`, `version`, `biz_type`)
VALUES
(1, 0, 100, 0, 101);
- 通过SELECT获取批量唯一ID:
SELECT `current_max_id`, `step`, `version` FROM `sequence_id_generator` WHERE `biz_type` = 101
- 更新以获取新的ID:
UPDATE sequence_id_generator SET current_max_id = current_max_id + 100, version = version + 1 WHERE version = 0 AND `biz_type` = 101
SELECT `current_max_id`, `step`, `version` FROM `sequence_id_generator` WHERE `biz_type` = 101
数据库的号段模式相较自增主键方式,减少了对数据库的访问次数,减轻了数据库的压力。为了避免单点故障,可以使用主从模式提高可用性。
NoSQL
在大多数情况下,使用Redis来生成分布式ID也非常流行。通过Redis的incr
命令,可以轻松实现ID的原子递增:
127.0.0.1:6379> set sequence_id_biz_type 1
OK
127.0.0.1:6379> incr sequence_id_biz_type
(integer) 2
127.0.0.1:6379> get sequence_id_biz_type
"2"
为了提高可用性和并发性能,可以采用Redis Cluster或开源的Codis集群方案。
算法
UUID
UUID(通用唯一标识符)包含32个16进制数字。可以通过JDK轻松生成UUID:
UUID uuid = UUID.randomUUID();
虽然UUID可以保证唯一性,但其存储空间大(128位),且无序性会影响数据库性能,因此在使用MySQL作为主键时并不常见。
Snowflake(雪花算法)
Snowflake是Twitter开源的分布式ID生成算法,由64位二进制数字组成,具有良好的性能和灵活性。它将ID分为几个部分,包括时间戳、机器ID及序列号。
开源框架
- UidGenerator(百度):基于雪花算法的唯一ID生成器,提供定制的ID组成结构。
- Leaf(美团):支持号段模式和雪花算法,解决了多个业务线的ID生成问题。
- Tinyid(滴滴):基于数据库号段模式,具有双号段缓存机制以提高性能。
结论
通过本文,我总结了分布式ID生成的常见解决方案。除了以上介绍,像ZooKeeper这样的中间件也可以被用来生成唯一ID。没有完美的解决方案,选择最合适的方式需结合实际项目需求。
在面试中,面试官可能会结合具体的业务场景来考察你对分布式ID设计的理解,你可以参考这篇文章:分布式ID设计指南。