我们在开发中,有时非常需要一个全局唯一的ID值,不管是业务需求,还是为了以后可能的分表需求,全局唯一值都非常有用,本篇大象就来讲讲这个实现并对ID生成器性能进行一下测试。
大象所讲的这个全局唯一ID生成器,其实是Twitter公开的一个算法,源码是用Scala写的,被国内的开源爱好者改写成了Java版本。
大象将这个类的调用简化了一下,实际使用中还是应该根据机器节点和数据中心节点来配置相关的参数。我这里假设只有一个节点作为ID号的生成器,所以workerId和datacenterId都设为0,当前时间与计算标记时间twepoch(Thu, 04 Nov 2010 01:42:54 GMT)之间的毫秒数是一个38位长度的long值,再左移timestampLeftShift(22位),就得到一个60位长度的long数字,该数字与datacenterId << datacenterIdShift取或,datacenterId最小值为0,最大值为31,所以长度为1-5位,datacenterIdShift是17位,所以结果就是最小值为0,最大值为22位长度的long,同理,workerId << workerIdShift的最大值为17位的long。所以最终生成的会是一个60位长度的long型唯一ID
我直接贴代码,有部分注释,有一小部分我还没完全看懂,请明白的告诉我一下。
/** * 全局唯一ID生成器 */ public class IdGen { private long workerId; private long datacenterId; private long sequence = 0L; private long twepoch = 1288834974657L; //Thu, 04 Nov 2010 01:42:54 GMT private long workerIdBits = 5L; //节点ID长度 private long datacenterIdBits = 5L; //数据中心ID长度 private long maxWorkerId = -1L ^ (-1L << workerIdBits); //最大支持机器节点数0~31,一共32个 private long maxDatacenterId = -1L ^ (-1L << datacenterIdBits); //最大支持数据中心节点数0~31,一共32个 private long sequenceBits = 12L; //序列号12位 private long workerIdShift = sequenceBits; //机器节点左移12位 private long datacenterIdShift = sequenceBits + workerIdBits; //数据中心节点左移17位 private long timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits; //时间毫秒数左移22位 private long sequenceMask = -1L ^ (-1L << sequenceBits); //4095 private long lastTimestamp = -1L; private static class IdGenHolder { private static final IdGen instance = new IdGen(); } public static IdGen get(){ return IdGenHolder.instance; } public IdGen() { this(0L, 0L); } public IdGen(long workerId, long datacenterId) { if (workerId > maxWorkerId || workerId < 0) { throw new IllegalArgumentException(String.format("worker Id can't be greater than %d or less than 0", maxWorkerId)); } if (datacenterId > maxDatacenterId || datacenterId < 0) { throw new IllegalArgumentException(String.format("datacenter Id can't be greater than %d or less than 0", maxDatacenterId)); } this.workerId = workerId; this.datacenterId = datacenterId; } 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只有12bit,所以和sequenceMask相与一下,去掉高位 sequence = (sequence + 1) & sequenceMask; //判断是否溢出,也就是每毫秒内超过4095,当为4096时,与sequenceMask相与,sequence就等于0 if (sequence == 0) { timestamp = tilNextMillis(lastTimestamp); //自旋等待到下一毫秒 } } else { sequence = 0L; //如果和上次生成时间不同,重置sequence,就是下一毫秒开始,sequence计数重新从0开始累加 } lastTimestamp = timestamp; // 最后按照规则拼出ID。 // 000000000000000000000000000000000000000000 00000 00000 000000000000 // time datacenterId workerId sequence return ((timestamp - twepoch) << timestampLeftShift) | (datacenterId << datacenterIdShift) | (workerId << workerIdShift) | sequence; } protected long tilNextMillis(long lastTimestamp) { long timestamp = timeGen(); while (timestamp <= lastTimestamp) { timestamp = timeGen(); } return timestamp; } protected long timeGen() { return System.currentTimeMillis(); } }
接下来我再写个测试类,看下并发情况下,1秒钟可以生成多少个ID。我测试用的电脑CPU为I5-4210U,内存8G,JDK为1.7.0_79,系统是64位WIN 7,使用-server模式。
import java.util.ArrayList; import java.util.List; import java.util.concurrent.Callable; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; import org.junit.Test; public class GeneratorTest { @Test public void testIdGenerator() { long avg = 0; for (int k = 0; k < 10; k++) { List<Callable<Long>> partitions = new ArrayList<Callable<Long>>(); final IdGen idGen = IdGen.get(); for (int i = 0; i < 1400000; i++) { partitions.add(new Callable<Long>() { @Override public Long call() throws Exception { return idGen.nextId(); } }); } ExecutorService executorPool = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors()); try { long s = System.currentTimeMillis(); executorPool.invokeAll(partitions, 10000, TimeUnit.SECONDS); long s_avg = System.currentTimeMillis() - s; avg += s_avg; System.out.println("完成时间需要: " + s_avg / 1.0e3 + "秒"); executorPool.shutdown(); } catch (Exception e) { e.printStackTrace(); } } System.out.println("平均完成时间需要: " + avg / 10 / 1.0e3 + "秒"); } }
运行10次,平均下来,每次1.038秒生成140万个ID,除了第1次时间在3秒左右和第2次1.6秒左右,其余8次都在0.7秒左右。如果使用更好的硬件,测试数据肯定会更好。因此从大的方向上看,单节点的ID生成器基本上可以满足我们的需要了。
需要注意的是,该值只是一个唯一值,但并不能保证会是一个顺序值,就是说两个ID之间可能会跳一些数字,所以对于一些有特殊需求的业务来说请注意这个差异。
本文为菠萝大象原创,如要转载请注明出处。http://www.blogjava.net/bolo
相关推荐
在现代的分布式系统中,确保每个实体的唯一标识是非常重要的,这通常涉及到全局唯一ID(Global Unique Identifier,简称GUID)的生成。SpringBoot作为一个轻量级的Java开发框架,广泛应用于微服务架构,而Vesta ID ...
- **无状态**:理想的生成器应是无状态的,这样可以轻松地在集群中进行扩展和故障恢复。 结合上述信息,"idGenerate"这个文件很可能是包含了一个Java实现的分布式代码生成器项目,可能包含了Snowflake算法或者其他...
另外,集群化也是一种提高可用性的常见做法,即将多个Id生成器组成一个集群,共同承担生成Id的任务,这样可以分摊负载,也能提高系统的容错能力。 5. 高性能的Id生成器设计 对于分布式系统而言,性能是衡量其效率的...
为了实现这个功能,开发者可能需要创建一个自定义的ID生成器类,该类可以调用 Snowflake 算法或者其他分布式ID生成策略,并结合业务规则来构造最终的订单号。同时,为了适应多线程和并发场景,生成器类需要处理好...
- ID生成器模块:负责与MySQL交互,获取新的ID。 - 请求处理器模块:接收来自客户端的请求,使用goroutine进行异步处理。 - 锁管理模块:实现分布式锁,防止ID分配冲突。 - 客户端接口:提供给其他系统调用的API接口...
2. **序列号生成器**:Mycat内置或用户自定义的序列号生成器,用于在各个节点间生成全局唯一的序列号。例如,可以使用Twitter的Snowflake算法,它通过时间戳、工作节点ID和序列号三部分生成ID,保证全局唯一性。 3....
时间戳保证了ID的排序性,而工作节点ID和序列号则确保了在同一时间点不同节点生成的ID也能互不相同。 然而,Snowflake算法并非没有缺点。例如,它依赖于系统时钟同步,如果时钟出现偏差,可能导致ID重复。此外,...
5. **数据库主键生成器(如Twitter的Kafka的Sequence Number)**: - Kafka的Sequence Number是一种高效的主键生成策略,每个生产者维护一个独立的序列号,结合分区信息,可以确保在整个集群中的全局唯一性。 6. *...
在分布式环境中,不同的节点可能同时创建新的实体,如果没有全局的ID协调机制,可能会导致ID冲突。Go语言因其并发性能和简洁的语法,成为构建这类服务的理想选择。它允许我们构建高效的并发处理程序,以满足大规模...
7. **扩展性和容错性**:考虑到Redis集群和主从复制,模块需要处理多节点间的协同工作,确保ID的唯一性和顺序性。 通过这个Redis Snowflake模块,开发者可以轻松地在分布式环境中集成Twitter Snowflake算法,提升ID...
- 这种机制常用于生成全局唯一ID,或者在分布式环境中实现排序。 4. **临时顺序节点(Ephemeral Sequential Nodes)** - 结合了临时节点和顺序节点的特性,既有临时节点的会话依赖性,又有顺序节点的编号特性。 ...
总结起来,`IdWorker`是一个强大的分布式ID生成器,它通过结合时间戳、数据中心ID和工作节点ID,实现了全局唯一且有序的ID生成。对于大型分布式系统来说,`IdWorker`提供了一种简洁、高效的解决方案。然而,具体到`...
Guid生成器,方便快捷, 下面的字水了: 全局唯一标识符(GUID,Globally Unique Identifier)是一种由算法生成的二进制长度为128位的数字标识符。GUID主要用于在拥有多个节点、多台计算机的网络或系统中。在理想...
1. **安装依赖**:在所有集群节点上安装必要的依赖包。 2. **安装Ganglia**:通过包管理器(如yum或apt-get)安装Ganglia的软件包。 3. **配置gmond**:在每个节点上编辑`/etc/ganglia/gmond.conf`,设置集群的名称...
全局唯一标识符(GUID,Globally Unique Identifier)是一种由算法生成的二进制长度为128位的数字标识符。GUID主要用于在拥有多个节点、多台计算机的网络或系统中。在理想情况下,任何计算机和计算机集群都不会生成...
**Redis-ID-Generator** 是一个基于 Redis 的分布式自动增量 ID 生成器,它主要解决了在分布式系统中如何生成全局唯一且连续的序列号的问题。在大型互联网应用中,这种需求非常常见,例如订单号、用户ID等都需要保证...
在全局事务管理器(GTM)节点上,需要生成SSH密钥对,并将公钥添加到授权密钥列表中。之后,将公钥复制到其他节点的`postgres`用户上,以实现GTMP节点和数据节点间的免密SSH登录。 ### 2. 安装依赖包与Postgres-XL ...
- **节点规划**:集群包含两个节点,192.168.47.101作为master节点,192.168.47.102作为slave节点。master节点承担NameNode、ResourceManager等角色,而slave节点作为DataNode和NodeManager。 2. **配置Hadoop...
#### 集群节点配置 - **Master节点**: - Master01: 192.168.1.137 - Master02: 192.168.1.138 - Master03/Node03: 192.168.1.170 (作为Master和Node双重角色) - **Node节点**: - Node01: 192.168.1.161 - ...
主节点负责维护集群的全局配置,对等节点用于数据的存储和索引,而搜索头则用于搜索查询。 在部署集群时,需要考虑系统要求和其他部署因素,如启用主节点、对等节点和搜索头。一旦集群配置完成,需要进行索引配置,...