Java实战:分布式ID生成方案

本文涉及的产品
云原生网关 MSE Higress,422元/月
服务治理 MSE Sentinel/OpenSergo,Agent数量 不受限
注册配置 MSE Nacos/ZooKeeper,118元/月
简介: 在分布式系统的设计与开发过程中,如何生成全局唯一、有序且高可用的ID是一个绕不开的核心问题。尤其是在电商、社交网络、金融交易等领域,ID不仅是业务数据的重要标识,还可能直接影响系统的稳定性和扩展性。本文将深入剖析分布式ID生成方案的设计原则、常见算法,并通过Java示例展示一种可行的实现方式。

在分布式系统的设计与开发过程中,如何生成全局唯一、有序且高可用的ID是一个绕不开的核心问题。尤其是在电商、社交网络、金融交易等领域,ID不仅是业务数据的重要标识,还可能直接影响系统的稳定性和扩展性。本文将深入剖析分布式ID生成方案的设计原则、常见算法,并通过Java示例展示一种可行的实现方式。

一、分布式ID生成的需求分析

全局唯一性:在分布式环境下,必须保证生成的ID在全球范围内不重复,避免数据冲突。

趋势递增:在许多业务场景下,ID有序有助于数据的排序、分页查询以及时间序列分析。

高可用性:ID生成服务需要具备高可用性,即使在部分节点故障的情况下也能继续生成ID。

性能高效:ID生成操作应足够快,尽量降低对业务的影响,尤其在高并发场景下。

易于扩展:随着业务发展,ID生成服务需要能够平滑地进行水平扩展。

二、分布式ID生成方案概述

雪花算法(Snowflake)

雪花算法是一种经典的分布式ID生成方案,由Twitter开源。其ID结构分为64位,由时间戳(41位)、机器标识符(10位)、序列号(12位)组成。通过这种方式,既保证了ID的全局唯一性,又实现了趋势递增。

public class SnowflakeIdWorker {

   private static final long EPOCH = 1577808000000L; // 自定义起始时间戳

   private static final long SEQUENCE_BITS = 12L; // 序列号位数

   private static final long WORKER_ID_BITS = 10L; // 工作节点位数

   private static final long TIMESTAMP_LEFT_SHIFT = SEQUENCE_BITS + WORKER_ID_BITS;

   private static final long WORKER_ID_LEFT_SHIFT = SEQUENCE_BITS;

   private final long workerId; // 工作节点ID

   private long sequence = 0L; // 序列号

   private long lastTimestamp = -1L; // 上次生成ID的时间戳

   public synchronized long nextId() {

       long timestamp = timeGen();

       if (timestamp < lastTimestamp) {

           throw new RuntimeException(String.format("Clock moved backwards. Refusing to generate id for %d milliseconds", lastTimestamp - timestamp));

       }

       if (lastTimestamp == timestamp) {

           // 同一毫秒内,序列号自增

           sequence = (sequence + 1) & ((1 << SEQUENCE_BITS) - 1);

           if (sequence == 0) {

               // 序列号溢出,阻塞等待下一毫秒

               timestamp = tilNextMillis(lastTimestamp);

           }

       } else {

           // 不同毫秒内,序列号重置

           sequence = 0L;

       }

       lastTimestamp = timestamp;

       return ((timestamp - EPOCH) << TIMESTAMP_LEFT_SHIFT) | (workerId << WORKER_ID_LEFT_SHIFT) | sequence;

   }

   // 获取当前时间戳,如果当前时间小于上一次ID生成的时间戳,那么一直循环等待直到超过那个时间戳为止

   protected long tilNextMillis(long lastTimestamp) {

       long timestamp = timeGen();

       while (timestamp <= lastTimestamp) {

           timestamp = timeGen();

       }

       return timestamp;

   }

   // 获取以毫秒为单位的当前时间

   protected long timeGen() {

       return System.currentTimeMillis();

   }

}


UUID

虽然UUID天生具有全球唯一性,但由于其无序性和较长的长度,一般不推荐用于需要趋势递增的场景。但在某些特殊场景下,如临时唯一标识符,可以考虑使用UUID。

数据库自增ID

通过数据库的自增主键来生成ID,简单易行,但存在单点风险、扩展困难等问题,不适合大规模分布式系统。

Zookeeper、Redis等中间件辅助生成

利用中间件的原子操作特性,如Zookeeper的顺序节点、Redis的INCR命令等,可以实现分布式的有序ID生成。不过同样要考虑中间件本身的高可用性和性能瓶颈。

三、分布式ID生成服务的高可用与扩展性设计

高可用性设计:可通过冗余部署多个ID生成服务,每个服务拥有唯一的workId,通过负载均衡器将请求均匀分发到各个服务节点。当某个节点故障时,剩余节点仍然可以继续提供服务。

扩展性设计:增加新的ID生成服务节点时,只需为其分配一个新的workId即可。在雪花算法中,预留足够的workerId位数,就可以支持大量节点的扩展。

四、实战优化与思考

防止单点故障:除了服务冗余外,还可以结合中间件的特性,如Redis哨兵或集群模式,增强服务的容错性。

性能优化:对于雪花算法,可以预先批量生成一批ID并缓存起来,避免每次请求都进行CPU密集型的ID生成操作。

业务连续性保障:设计合理的workId分配策略,确保在扩容或缩容时不影响ID的生成,例如采用时间窗口内轮转分配workerId的方式。

ID的安全性与合规性:遵循ID生成策略的透明性和可追溯性,考虑ID中是否包含敏感信息,以及是否符合法律法规的要求。

五、分布式ID生成的挑战及应对

时钟回拨问题:雪花算法依赖于精确的时间戳,时钟同步问题可能导致ID冲突或生成暂停。为解决此问题,可以引入逻辑时钟或者设置一定的容忍度,在时钟轻微回拨时依然允许ID生成,同时报警提醒运维人员及时处理时钟同步问题。

序列号耗尽:在雪花算法中,每台服务器每毫秒最多能生成 (2^{12}) 个ID,若某节点在一个毫秒内的请求量远超此值,会导致序列号耗尽。针对这一情况,可以通过提前预警机制监控每台服务器的序列号消耗速率,必要时可动态调整工作节点的数量或增大序列号位数。

六、基于Zookeeper实现分布式ID生成器

借助Zookeeper的有序节点特性,我们可以构建一个简单的分布式ID生成服务:

import org.apache.zookeeper.*;

import org.apache.zookeeper.data.Stat;

import java.util.concurrent.CountDownLatch;

public class ZookeeperIdGenerator implements Watcher {

   private static final String ROOT_PATH = "/distributed_ids";

   private ZooKeeper zooKeeper;

   private CountDownLatch latch = new CountDownLatch(1);

   private String currentNodePath;

   public void init(String zkServers) throws Exception {

       zooKeeper = new ZooKeeper(zkServers, 3000, this);

       latch.await();

   }

   @Override

   public void process(WatchedEvent event) {

       if (event.getState() == Event.KeeperState.SyncConnected) {

           latch.countDown();

       }

   }

   public long generateId() throws KeeperException, InterruptedException {

       // 创建一个有序子节点

       currentNodePath = zooKeeper.create(ROOT_PATH + "/id-", null, ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT_SEQUENTIAL);

     

       // 获取当前节点名,去除父路径得到ID

       int idIndex = currentNodePath.lastIndexOf("/") + 1;

       String nodeId = currentNodePath.substring(idIndex);

       // 转换字符串ID为整型ID(假设节点名称直接对应整数值)

       long id = Long.parseLong(nodeId);

       // 返回生成的ID

       return id;

   }

   public void close() throws InterruptedException {

       zooKeeper.close();

   }

   // 省略异常处理及实际项目中的封装代码...

}



在这个示例中,每次生成ID时都会在Zookeeper的指定路径下创建一个有序的持久化节点,节点名称即为ID。由于Zookeeper会自动为这些节点编号,因此保证了ID的全局唯一性和有序性。

然而,这种方案在高并发场景下可能会面临Zookeeper连接压力大、写入速度受限的问题,此时就需要根据实际业务需求进行相应的优化,比如批量获取ID、引入队列等方式。

七、总结

选择和设计分布式ID生成方案是一项细致而重要的任务,不仅要考虑到系统的当前规模,更要预见未来可能的增长趋势。从长远看,一个优秀的分布式ID生成服务应兼具可靠、高效、可扩展的特点,同时还能灵活适应不断变化的业务需求。通过深入理解和实践上述多种方法,我们能够在真实环境中找到最契合自身业务场景的最佳解决方案。

相关实践学习
基于MSE实现微服务的全链路灰度
通过本场景的实验操作,您将了解并实现在线业务的微服务全链路灰度能力。
相关文章
|
25天前
|
Java 数据库
在Java中使用Seata框架实现分布式事务的详细步骤
通过以上步骤,利用 Seata 框架可以实现较为简单的分布式事务处理。在实际应用中,还需要根据具体业务需求进行更详细的配置和处理。同时,要注意处理各种异常情况,以确保分布式事务的正确执行。
|
25天前
|
消息中间件 Java Kafka
在Java中实现分布式事务的常用框架和方法
总之,选择合适的分布式事务框架和方法需要综合考虑业务需求、性能、复杂度等因素。不同的框架和方法都有其特点和适用场景,需要根据具体情况进行评估和选择。同时,随着技术的不断发展,分布式事务的解决方案也在不断更新和完善,以更好地满足业务的需求。你还可以进一步深入研究和了解这些框架和方法,以便在实际应用中更好地实现分布式事务管理。
|
6天前
|
NoSQL Java Redis
秒杀抢购场景下实战JVM级别锁与分布式锁
在电商系统中,秒杀抢购活动是一种常见的营销手段。它通过设定极低的价格和有限的商品数量,吸引大量用户在特定时间点抢购,从而迅速增加销量、提升品牌曝光度和用户活跃度。然而,这种活动也对系统的性能和稳定性提出了极高的要求。特别是在秒杀开始的瞬间,系统需要处理海量的并发请求,同时确保数据的准确性和一致性。 为了解决这些问题,系统开发者们引入了锁机制。锁机制是一种用于控制对共享资源的并发访问的技术,它能够确保在同一时间只有一个进程或线程能够操作某个资源,从而避免数据不一致或冲突。在秒杀抢购场景下,锁机制显得尤为重要,它能够保证商品库存的扣减操作是原子性的,避免出现超卖或数据不一致的情况。
39 10
|
1天前
|
Java
Java基础却常被忽略:全面讲解this的实战技巧!
本次分享来自于一道Java基础的面试试题,对this的各种妙用进行了深度讲解,并分析了一些关于this的常见面试陷阱,主要包括以下几方面内容: 1.什么是this 2.this的场景化使用案例 3.关于this的误区 4.总结与练习
|
12天前
|
消息中间件 SQL 中间件
大厂都在用的分布式事务方案,Seata+RocketMQ带你打破10万QPS瓶颈
分布式事务涉及跨多个数据库或服务的操作,确保数据一致性。本地事务通过数据库直接支持ACID特性,而分布式事务则需解决跨服务协调难、高并发压力及性能与一致性权衡等问题。常见的解决方案包括两阶段提交(2PC)、Seata提供的AT和TCC模式、以及基于消息队列的最终一致性方案。这些方法各有优劣,适用于不同业务场景,选择合适的方案需综合考虑业务需求、系统规模和技术团队能力。
98 7
|
20天前
|
NoSQL Java 数据处理
基于Redis海量数据场景分布式ID架构实践
【11月更文挑战第30天】在现代分布式系统中,生成全局唯一的ID是一个常见且重要的需求。在微服务架构中,各个服务可能需要生成唯一标识符,如用户ID、订单ID等。传统的自增ID已经无法满足在集群环境下保持唯一性的要求,而分布式ID解决方案能够确保即使在多个实例间也能生成全局唯一的标识符。本文将深入探讨如何利用Redis实现分布式ID生成,并通过Java语言展示多个示例,同时分析每个实践方案的优缺点。
38 8
|
17天前
|
缓存 NoSQL Java
Spring Boot中的分布式缓存方案
Spring Boot提供了简便的方式来集成和使用分布式缓存。通过Redis和Memcached等缓存方案,可以显著提升应用的性能和扩展性。合理配置和优化缓存策略,可以有效避免常见的缓存问题,保证系统的稳定性和高效运行。
35 3
|
16天前
|
消息中间件 架构师 数据库
本地消息表事务:10Wqps 高并发分布式事务的 终极方案,大厂架构师的 必备方案
45岁资深架构师尼恩分享了一篇关于分布式事务的文章,详细解析了如何在10Wqps高并发场景下实现分布式事务。文章从传统单体架构到微服务架构下分布式事务的需求背景出发,介绍了Seata这一开源分布式事务解决方案及其AT和TCC两种模式。随后,文章深入探讨了经典ebay本地消息表方案,以及如何使用RocketMQ消息队列替代数据库表来提高性能和可靠性。尼恩还分享了如何结合延迟消息进行事务数据的定时对账,确保最终一致性。最后,尼恩强调了高端面试中需要准备“高大上”的答案,并提供了多个技术领域的深度学习资料,帮助读者提升技术水平,顺利通过面试。
本地消息表事务:10Wqps 高并发分布式事务的 终极方案,大厂架构师的 必备方案
|
17天前
|
Java 程序员
Java基础却常被忽略:全面讲解this的实战技巧!
小米,29岁程序员,分享Java中`this`关键字的用法。`this`代表当前对象引用,用于区分成员变量与局部变量、构造方法间调用、支持链式调用及作为参数传递。文章还探讨了`this`在静态方法和匿名内部类中的使用误区,并提供了练习题。
19 1
|
22天前
|
NoSQL 安全 PHP
hyperf-wise-locksmith,一个高效的PHP分布式锁方案
`hyperf-wise-locksmith` 是 Hyperf 框架下的互斥锁库,支持文件锁、分布式锁、红锁及协程锁,有效防止分布式环境下的竞争条件。本文介绍了其安装、特性和应用场景,如在线支付系统的余额扣减,确保操作的原子性。
24 4