前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >分布式全局ID解决方案解析及示例

分布式全局ID解决方案解析及示例

作者头像
用户7353950
发布2024-04-18 13:10:22
1410
发布2024-04-18 13:10:22
举报
文章被收录于专栏:IT技术订阅IT技术订阅
分布式全局ID解决方案旨在为大规模分布式系统中的不同实体(如用户、订单、消息等)提供一种全局唯一、高可用且高效生成的标识符。以下是几种常见的分布式全局ID生成方案及其特点:

1. UUID:

- 基于RFC 4122标准生成的128位(16字节)全局唯一标识符。

- 优点:

- 无需中心化服务或协调即可生成,完全离线可用

- 生成的ID几乎可以确保全球范围内的唯一性,无需担心重复。

- 缺点:

- ID长度较长,存储和传输成本相对较高。

- 非顺序生成,不利于数据库索引优化,影响查询效率,特别是在需要扫描数据段的场景下。

- 无趋势递增性,不利于缓存命中和排序。

2. 数据库自增ID:

- 利用关系型数据库的自增序列或类似机制生成ID。

- 优点:

- 简单易用,保证唯一性。

- ID按顺序生成,有利于数据库索引优化和排序。

- 缺点:

- 需要中心化的数据库服务,单点故障风险高,难以水平扩展。

- 在高并发场景下可能成为瓶颈,且对数据库的可用性和性能有较高依赖。

3. 号段模式:

- 通过预分配一定数量(如1000个)的ID作为一个号段,服务从中心节点(如数据库或专门的服务)批量获取号段,在本地进行分配。

- 优点:

- 减轻了对中心节点的压力,提升ID生成性能。

- ID趋势递增,利于索引优化。

- 缺点:

- 需要管理号段的分配与回收,复杂度稍高。

- 号段耗尽时需要及时从中心节点获取新的号段,否则可能导致ID生成阻塞。

4. 雪花算法(Snowflake):

- 结构化的64位ID,通常包含时间戳、工作机器ID、序列号等组成部分。

- 优点:

- 无需中心节点,支持分布式环境下的独立生成。

- ID趋势递增,利于索引优化。

- 可以根据时间戳判断ID生成的大致时间。

- 缺点:

- 依赖于准确的时间同步,时钟回拨可能导致ID冲突。

- 工作机器ID和序列号的管理需要考虑扩容、容错等因素。

5. 基于Redis的ID生成:

- 利用Redis的原子操作(如`INCR`或`INCRBY`)在一个特定键上递增,实现全局唯一ID的生成。

- 优点:

- 利用Redis的高性能和高可用性,生成速度快。

- 简单易用,支持分布式环境。

- 缺点:

- 对Redis服务的依赖性强,Redis故障可能影响ID生成。

- 如果单个Redis实例成为瓶颈,可能需要引入分片或集群方案,增加复杂性。

6. 专用ID生成服务(如Leaf):

- 使用专门设计的分布式ID生成服务,如美团的Leaf,它通常集成了多种策略,如号段模式、雪花算法等,并提供了额外的功能如容错、监控等。

- 优点:

- 高度可定制,易于适应不同的业务需求。

- 集成了运维、监控、容错等高级功能,简化运维。

- 缺点:

- 需要维护额外的服务组件,增加了系统的复杂性。

- 对特定服务的依赖性较强,服务故障可能影响ID生成。

实际项目应用中,雪花算法及redis实现分布式全局ID这两种方案比较常见,下面给出两者的java代码示例供参考:

雪花算法(Snowflake)Java代码示例: import java.util.HashSet; import java.util.Set; /** * 雪花ID生成 * * @author Auler_zrl */ public class SnowflakeUtils { private static SnowflakeUtils snowflakeUtils = new SnowflakeUtils(); // 起始的时间戳 private final static long START_STMP = 1579305600000L; // 毫秒时间戳 // 每一部分占用的位数 private final static long SEQUENCE_BIT = 12; // 序列号占用的位数 private final static long MACHINE_BIT = 5; // 机器标识占用的位数 private final static long DATACENTER_BIT = 5; // 数据中心占用的位数 // 每一部分最大值 private final static long MAX_DATACENTER_NUM = -1L ^ (-1L << DATACENTER_BIT); // 支持的最大数据中心ID,结果是31 private final static long MAX_MACHINE_NUM = -1L ^ (-1L << MACHINE_BIT); // 支持的最大机器ID,结果是31 private final static long MAX_SEQUENCE = -1L ^ (-1L << SEQUENCE_BIT); // 每一部分向左的位移 private final static long MACHINE_LEFT = SEQUENCE_BIT; private final static long DATACENTER_LEFT = SEQUENCE_BIT + MACHINE_BIT; private final static long TIMESTMP_LEFT = DATACENTER_LEFT + DATACENTER_BIT; private long datacenterId = 0; // 数据中心[0, 2^DATACENTER_BIT) private long machineId = 0; // 机器标识[0, 2^MACHINE_BIT) private long sequence = 0L; // 毫秒内序列号(0~4095) private long lastStmp = -1L; // 上一次时间戳 // 默认构造函数 public SnowflakeUtils() { } /** * 构造函数(分布式每台机子每条线程都不一样参数) * * @param datacenterId 数据中心 * @param machineId 机器id */ public SnowflakeUtils(long datacenterId, long machineId) { if (datacenterId > MAX_DATACENTER_NUM || datacenterId < 0 || machineId > MAX_MACHINE_NUM || machineId < 0) { throw new IllegalArgumentException("machineId can't be greater than MAX_MACHINE_NUM or less than 0"); } this.datacenterId = datacenterId; this.machineId = machineId; } // 产生下一个ID public String nextStrId() { return String.valueOf(nextId()); } // 产生下一个ID public synchronized long nextId() { long currStmp = getNewstmp(); if (currStmp < lastStmp) { throw new RuntimeException("Clock moved backwards. Refusing to generate id for " + (lastStmp - currStmp) + " milliseconds"); } if (currStmp == lastStmp) { // 同一毫秒内,序列号自增 sequence = (sequence + 1) & MAX_SEQUENCE; // 序列号溢出,阻塞到下一毫秒 if (sequence == 0) { currStmp = getNextMill(); } } else { // 不同毫秒内,序列号重置 sequence = 0L; } lastStmp = currStmp; // 组装ID return ((currStmp - START_STMP) << TIMESTMP_LEFT) // | (datacenterId << DATACENTER_LEFT) // | (machineId << MACHINE_LEFT) // | sequence; } private long getNextMill() { long mill = getNewstmp(); while (mill <= lastStmp) { mill = getNewstmp(); } return mill; } private long getNewstmp() { return System.currentTimeMillis(); } } 使用Redis生成全局ID的Java代码示例: 以下示例使用Jedis客户端与Redis交互,通过`INCR`命令递增一个键的值来生成全局ID。请确保已安装并配置好Jedis库。 import redis.clients.jedis.Jedis; public class RedisGlobalIdGenerator { private final Jedis jedis; public RedisGlobalIdGenerator(Jedis jedis) { this.jedis = jedis; } public long generateUniqueId(String key) { return jedis.incr(key); } } 使用示例: import redis.clients.jedis.Jedis; public class Main { public static void main(String[] args) { Jedis jedis = new Jedis("localhost"); // 替换为实际的Redis服务器地址和端口 RedisGlobalIdGenerator generator = new RedisGlobalIdGenerator(jedis); String key = "global_id_counter"; for (int i = 0; i < 10; i++) { long id = generator.generateUniqueId(key); System.out.println("Generated ID: " + id); } jedis.close(); } } 对于更复杂的场景,可能需要使用更高级的Redis客户端库(如Lettuce)或Redis的分布式ID生成插件(如RedLock、Redisson等)。

如何选择合适的分布式全局ID解决方案:

- 业务需求分析:

- 是否需要ID的趋势递增性?这会影响数据库索引和查询效率。

- 是否要求ID能反映生成时间?某些场景下,时间信息嵌入ID有助于数据分析。

- 是否有严格的ID生成速率要求?高并发场景下,需确保ID生成系统的吞吐量。

- 系统架构考量:

- 系统是否高度分布,是否允许引入中心化依赖?中心化方案(如数据库自增ID)可能不适合大规模分布式系统。

- 系统是否有严格的低延迟要求?某些方案(如号段模式、Redis)能够提供更低的延迟。

- 技术栈与运维能力:

- 团队对现有技术栈的熟悉程度,是否易于集成新组件。

- 运维团队对所选方案的监控、故障排查、扩展等能力。

- 成本与资源限制:

- 存储成本:考虑ID长度对数据库存储的影响,以及潜在的带宽消耗。

- 硬件资源:如Redis或专用ID服务可能需要额外的计算和内存资源。

综上所述,选择分布式全局ID解决方案应综合考虑业务需求、系统架构、技术栈、运维能力和成本因素,权衡各种方案的优缺点,选择最适合当前场景的策略。在实际应用中,有时会结合使用多种方案或者对已有方案进行适当改良,以满足特定业务场景的复杂需求。

本文参与?腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2024-04-17,如有侵权请联系?cloudcommunity@tencent.com 删除

本文分享自 IT技术订阅 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与?腾讯云自媒体分享计划? ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
数据库
云数据库为企业提供了完善的关系型数据库、非关系型数据库、分析型数据库和数据库生态工具。您可以通过产品选择和组合搭建,轻松实现高可靠、高可用性、高性能等数据库需求。云数据库服务也可大幅减少您的运维工作量,更专注于业务发展,让企业一站式享受数据上云及分布式架构的技术红利!
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
http://www.vxiaotou.com