详解分布式系统中的唯一id生成策略

系统唯一ID是我们在设计一个系统的时候常常会遇见的问题,也常常为这个问题而纠结。生成ID的方法有很多,适应不同的场景、需求以及性能要求。所以有些比较复杂的系统会有多个ID生成的策略。下面就介绍一些常见的ID生成策略。

平常应用中方式很多

1. 数据库自增长序列或字段

2. UUID

3.uuid的变种

4.redis生成

5. Twitter的snowflake算法

snowflake是Twitter开源的分布式ID生成算法,结果是一个long型的ID。其核心思想是:使用41bit作为毫秒数,10bit作为机器的ID(5个bit是数据中心,5个bit的机器ID),12bit作为毫秒内的流水号(意味着每个节点在每毫秒可以产生 4096 个 ID),最后还有一个符号位,永远是0。

本文主要是详解snowflake算法的实现代码解析

  1 /**
  2  * Twitter_Snowflake<br>
  3  * SnowFlake的结构如下(每部分用-分开):<br>
  4  * 0 - 0000000000 0000000000 0000000000 0000000000 0 - 00000 - 00000 - 000000000000 <br>
  5  * 1位标识,由于long基本类型在Java中是带符号的,最高位是符号位,正数是0,负数是1,所以id一般是正数,最高位是0<br>
  6  * 41位时间截(毫秒级),注意,41位时间截不是存储当前时间的时间截,而是存储时间截的差值(当前时间截 - 开始时间截)
  7  * 得到的值),这里的的开始时间截,一般是我们的id生成器开始使用的时间,由我们程序来指定的(如下下面程序IdWorker类的
  8  startTime属性)。41位的时间截,可以使用69年,年T = (1L << 41) / (1000L * 60 * 60 * 24 * 365) = 69<br>
  9  * 10位的数据机器位,可以部署在1024个节点,包括5位datacenterId和5位workerId<br>
 10  * 12位序列,毫秒内的计数,12位的计数顺序号支持每个节点每毫秒(同一机器,同一时间截)产生4096个ID序号<br>
 11  * 加起来刚好64位,为一个Long型。<br>
 12  * SnowFlake的优点是,整体上按照时间自增排序,并且整个分布式系统内不会产生ID碰撞(由数据中心ID和机器ID作区分),并且效率较高,经测试,SnowFlake每秒能够产生26万ID左右。
 13  */
 14 public class SnowflakeIdWorker {
 15
 16     // ==============================Fields===========================================
 17     /** 开始时间截 (2015-01-01) */
 18     private final long twepoch = 1420041600000L;
 19
 20     /** 机器id所占的位数 */
 21     private final long workerIdBits = 5L;
 22
 23     /** 数据标识id所占的位数 */
 24     private final long datacenterIdBits = 5L;
 25
 26     /** 支持的最大机器id,结果是31 (这个移位算法可以很快的计算出几位二进制数所能表示的最大十进制数) */
 27     private final long maxWorkerId = -1L ^ (-1L << workerIdBits);
 28
 29     /** 支持的最大数据标识id,结果是31 */
 30     private final long maxDatacenterId = -1L ^ (-1L << datacenterIdBits);
 31
 32     /** 序列在id中占的位数 */
 33     private final long sequenceBits = 12L;
 34
 35     /** 机器ID向左移12位 */
 36     private final long workerIdShift = sequenceBits;
 37
 38     /** 数据标识id向左移17位(12+5) */
 39     private final long datacenterIdShift = sequenceBits + workerIdBits;
 40
 41     /** 时间截向左移22位(5+5+12) */
 42     private final long timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits;
 43
 44     /** 生成序列的掩码,这里为4095 (0b111111111111=0xfff=4095) */
 45     private final long sequenceMask = -1L ^ (-1L << sequenceBits);
 46
 47     /** 工作机器ID(0~31) */
 48     private long workerId;
 49
 50     /** 数据中心ID(0~31) */
 51     private long datacenterId;
 52
 53     /** 毫秒内序列(0~4095) */
 54     private long sequence = 0L;
 55
 56     /** 上次生成ID的时间截 */
 57     private long lastTimestamp = -1L;
 58
 59     //==============================Constructors=====================================
 60     /**
 61      * 构造函数
 62      * @param workerId 工作ID (0~31)
 63      * @param datacenterId 数据中心ID (0~31)
 64      */
 65     public SnowflakeIdWorker(long workerId, long datacenterId) {
 66         if (workerId > maxWorkerId || workerId < 0) {
 67             throw new IllegalArgumentException(String.format("worker Id can‘t be greater than %d or less than 0", maxWorkerId));
 68         }
 69         if (datacenterId > maxDatacenterId || datacenterId < 0) {
 70             throw new IllegalArgumentException(String.format("datacenter Id can‘t be greater than %d or less than 0", maxDatacenterId));
 71         }
 72         this.workerId = workerId;
 73         this.datacenterId = datacenterId;
 74     }
 75
 76     // ==============================Methods==========================================
 77     /**
 78      * 获得下一个ID (该方法是线程安全的)
 79      * @return SnowflakeId
 80      */
 81     public synchronized long nextId() {
 82         long timestamp = timeGen();
 83
 84         //如果当前时间小于上一次ID生成的时间戳,说明系统时钟回退过这个时候应当抛出异常
 85         if (timestamp < lastTimestamp) {
 86             throw new RuntimeException(
 87                     String.format("Clock moved backwards.  Refusing to generate id for %d milliseconds", lastTimestamp - timestamp));
 88         }
 89
 90         //如果是同一时间生成的,则进行毫秒内序列
 91         if (lastTimestamp == timestamp) {
 92             sequence = (sequence + 1) & sequenceMask;
 93             //毫秒内序列溢出
 94             if (sequence == 0) {
 95                 //阻塞到下一个毫秒,获得新的时间戳
 96                 timestamp = tilNextMillis(lastTimestamp);
 97             }
 98         }
 99         //时间戳改变,毫秒内序列重置
100         else {
101             sequence = 0L;
102         }
103
104         //上次生成ID的时间截
105         lastTimestamp = timestamp;
106
107         //移位并通过或运算拼到一起组成64位的ID
108         return ((timestamp - twepoch) << timestampLeftShift) //
109                 | (datacenterId << datacenterIdShift) //
110                 | (workerId << workerIdShift) //
111                 | sequence;
112     }
113
114     /**
115      * 阻塞到下一个毫秒,直到获得新的时间戳
116      * @param lastTimestamp 上次生成ID的时间截
117      * @return 当前时间戳
118      */
119     protected long tilNextMillis(long lastTimestamp) {
120         long timestamp = timeGen();
121         while (timestamp <= lastTimestamp) {
122             timestamp = timeGen();
123         }
124         return timestamp;
125     }
126
127     /**
128      * 返回以毫秒为单位的当前时间
129      * @return 当前时间(毫秒)
130      */
131     protected long timeGen() {
132         return System.currentTimeMillis();
133     }
134
135     //==============================Test=============================================
136     /** 测试 */
137     public static void main(String[] args) {
138 //        System.out.println(Long.toBinaryString(5));
139         SnowflakeIdWorker idWorker = new SnowflakeIdWorker(1, 1);
140         for (int i = 0; i < 1000; i++) {
141             long id = idWorker.nextId();
142             System.out.println(Long.toBinaryString(id));
143             System.out.println(id);
144         }
145     }
146 }

附带有直接的测试方法,建议动手可以敲一下看一下其中的原理。

如有问题或者错误烦请指出!

原文地址:https://www.cnblogs.com/javasingle/p/10714214.html

时间: 2024-08-19 09:08:31

详解分布式系统中的唯一id生成策略的相关文章

hibernate 中mysql的id生成策略

数据库的规划和操作号码大全中,咱们一般会给表建立长尾关键词挖掘工具的主键. 主键,可以分为天然主键和署理主键. 天然主键表明:选用具有事务逻辑意义的字段作为表的主键.比方在用户信息表中,选用用户的身份证号码作为主键.可是这样一来,跟着事务逻辑的变化,主键就有可能要更改.比方,假定哪天身份证号码升级成19,2位,那....... 署理主键:在表中人为的添加一个字段,该字段并没有表明任何的事务逻辑,仅仅用来标识一行数据.比方说在用户信息表中,添加一个用户ID的字段.用来表明该条用户信息的记录. 一般

常见分布式全局唯一ID生成策略

全局唯一的 ID 几乎是所有系统都会遇到的刚需.这个 id 在搜索, 存储数据, 加快检索速度 等等很多方面都有着重要的意义.工业上有多种策略来获取这个全局唯一的id,针对常见的几种场景,我在这里进行简单的总结和对比. 简单分析一下需求 [1] 所谓全局唯一的 id 其实往往对应是生成唯一记录标识的业务需求. 这个 id 常常是数据库的主键,数据库上会建立聚集索引(cluster index),即在物理存储上以这个字段排序.这个记录标识上的查询,往往又有分页或者排序的业务需求.所以往往要有一个t

分布式全局唯一ID生成策略?

一.背景 分布式系统中我们会对一些数据量大的业务进行分拆,如:用户表,订单表.因为数据量巨大一张表无法承接,就会对其进行分库分表. 但一旦涉及到分库分表,就会引申出分布式系统中唯一主键ID的生成问题. 1.1 唯一ID的特性 整个系统ID唯一; ID是数字类型,而且是趋势递增; ID简短,查询效率快. 1.2 递增与趋势递增 递增 趋势递增 第一次生成的ID为12,下一次生成的ID是13,再下一次生成的ID是14. 什么是?如:在一段时间内,生成的ID是递增的趋势.如:再一段时间内生成的ID在[

分布式系统唯一ID生成方案汇总

系统唯一ID是我们在设计一个系统的时候常常会遇见的问题,也常常为这个问题而纠结.生成ID的方法有很多,适应不同的场景.需求以及性能要求.所以有些比较复杂的系统会有多个ID生成的策略.下面就介绍一些常见的ID生成策略. 1. 数据库自增长序列或字段 最常见的方式.利用数据库,全数据库唯一. 优点: 1)简单,代码方便,性能可以接受. 2)数字ID天然排序,对分页或者需要排序的结果很有帮助. 缺点: 1)不同数据库语法和实现不同,数据库迁移的时候或多数据库版本支持的时候需要处理. 2)在单个数据库或

用IDEA详解Spring中的IoC和DI(挺透彻的,点进来看看吧)

用IDEA详解Spring中的IoC和DI 一.Spring IoC的基本概念 控制反转(IoC)是一个比较抽象的概念,它主要用来消减计算机程序的耦合问题,是Spring框架的核心.依赖注入(DI)是IoC的另外一种说法,只是从不同的角度描述相同的概念.看完这两句,是不是不但没懂,反而更迷惑了,别急,往下看: IoC的背景 我们都知道,在采用面向对象方法设计的软件系统中,它的底层实现都是由N个对象组成的,所有的对象通过彼此的合作,最终实现系统的业务逻辑. 如果我们打开机械式手表的后盖,就会看到与

实例详解 EJB 中的六大事务传播属性--转

前言 事务 (Transaction) 是访问并可能更新数据库中各种数据项的一个程序执行单元 (unit).在关系数据库中,一个事务可以是一条或一组 SQL 语句,甚至整个程序.它有通常被称为 ACID 的原子性(Atomicity).一致性(Consistency).隔离性(Isolation).持续性(Durability)四大特性: 原子性(Atomicity):一个事务是一个不可分割的工作单位,事务中包括的诸操作要么都做,要么都不做. 一致性(Consistency):事务必须是使数据库

【转】详解C#中的反射

原帖链接点这里:详解C#中的反射 反射(Reflection) 2008年01月02日 星期三 11:21 两个现实中的例子: 1.B超:大家体检的时候大概都做过B超吧,B超可以透过肚皮探测到你内脏的生理情况.这是如何做到的呢?B超是B型超声波,它可以透过肚皮通过向你体内发射B型超声波,当超声波遇到内脏壁的时候就会产生一定的“回音”反射,然后把“回音”进行处理就可以显示出内脏的情况了(我不是医生也不是声学专家,不知说得是否准确^_^). 2.地球内部结构:地球的内部结构大体可以分为三层:地壳.地

详解javascript中的this对象

详解javascript中的this对象 前言 Javascript是一门基于对象的动态语言,也就是说,所有东西都是对象,一个很典型的例子就是函数也被视为普通的对象.Javascript可以通过一定的设计模式来实现面向对象的编程,其中this "指针"就是实现面向对象的一个很重要的特性.但是this也是Javascript中一个非常容易理解错,进而用错的特性.特别是对于接触静态语言比较久了的同志来说更是如此. 示例说明 我们先来看一个最简单的示例: <script type=&q

【系统设计】分布式唯一ID生成方案总结

目录 分布式系统中唯一ID生成方案 1. 唯一ID简介 2. 全局ID常见生成方案 2.1 UUID生成 2.2 数据库生成 2.3 Redis生成 2.4 利用zookeeper生成 2.5 雪花算法生成 2.6 其他生成方式 分布式系统中唯一ID生成方案 在系统设计中,我们经常需要一个全局唯一的ID来标识一条数据,比如订单表,商品表的主键ID.这个ID往往能影响到数据存储.索引和查询等操作的效率.因此这个全局唯一的ID对系统的可用性和性能至关重要. 1. 唯一ID简介 在系统设计中,我们经常