`

全局唯一ID设计方案

阅读更多
在分布式系统中,经常需要使用全局唯一ID查找对应的数据。产生这种ID需要保证系统全局唯一,而且要高性能以及占用相对较少的空间。

全局唯一ID在数据库中一般会被设成主键,这样为了保证数据插入时索引的快速建立,还需要保持一个有序的趋势。

这样全局唯一ID就需要保证这两个需求:
全局唯一
趋势有序
全局ID产生的几种方式


数据库自增
当服务使用的数据库只有单库单表时,可以利用数据库的auto_increment来生成全局唯一递增ID.

优势:
简单,无需程序任何附加操作
保持定长的增量
在单表中能保持唯一性

劣势:
高并发下性能不佳,主键产生的性能上限是数据库服务器单机的上限。
水平扩展困难,在分布式数据库环境下,无法保证唯一性。

UUID
一般的语言中会自带UUID的实现,比如Java中UUID方式UUID.randomUUID().toString(),可以通过服务程序本地产生,ID的生成不依赖数据库的实现。

优势:
本地生成ID,不需要进行远程调用。
全局唯一不重复。
水平扩展能力非常好。

劣势:
ID有128 bits,占用的空间较大,需要存成字符串类型,索引效率极低。
生成的ID中没有带Timestamp,无法保证趋势递增


Twitter Snowflake

snowflake是twitter开源的分布式ID生成算法,其核心思想是:产生一个long型的ID,使用其中41bit作为毫秒数,10bit作为机器编号,12bit作为毫秒内序列号。这个算法单机每秒内理论上最多可以生成1000*(2^12)个,也就是大约400W的ID,完全能满足业务的需求。

根据snowflake算法的思想,我们可以根据自己的业务场景,产生自己的全局唯一ID。因为Java中long类型的长度是64bits,所以我们设计的ID需要控制在64bits。

比如我们设计的ID包含以下信息:
| 41 bits: Timestamp | 3 bits: 区域 | 10 bits: 机器编号 | 10 bits: 序列号 |

产生唯一ID的Java代码:有修改

package com.pandy.framework.core.id;

import java.security.SecureRandom;

/**
 * Created by pandy on 16-6-27.
 * <p/>
 * 项目名称: workspace  * 功能说明:
 * snowflake是twitter开源的分布式ID生成算法,其核心思想是:
 * 产生一个long型的ID,使用其中41bit作为毫秒数,10bit作为机器编号,12bit作为毫秒内序列号。
 * 这个算法单机每秒内理论上最多可以生成1000*(2^12)个,也就是大约400W的ID,完全能满足业务的需求。
 * 根据snowflake算法的思想,我们可以根据自己的业务场景,产生自己的全局唯一ID。
 * 因为Java中long类型的长度是64bits,所以我们设计的ID需要控制在64bits。
 * 比如我们设计的ID包含以下信息:
 * | 41 bits: Timestamp | 3 bits: 区域 | 10 bits: 机器编号 | 10 bits: 序列号 |
 * 创建者: Pandy,
 * 邮箱: panyongzheng@163.com, 1453261799@qq.com
 * 版权:
 * 官网:
 * 创建日期: 16-6-27.
 * 创建时间: 下午10:59.
 * 修改历史:
 * -----------------------------------------------
 */

/**
 * 创建的时候:  workerId 可以用于标记表的序号(表少于1024个表的情况下),
 * 或者模块的序号(表大于1024个表的情况下,按照表属于的模块来定义这个序号),
 * 避免重复ID出现
 * 自定义 ID 生成器 ID 生成规则: ID长达 64 bits
 *
 * | 41 bits: Timestamp (毫秒) | 3 bits: 区域(机房) | 10 bits: 机器编号 | 10 bits: 序列号 |
 */
public class CustomUUID {

    // 基准时间
    private long twepoch = 1288834974657L; // Thu, 04 Nov 2010 01:42:54 GMT
    // 区域标志位数
    private final static long regionIdBits = 3L;
    // 机器标识位数
    private final static long workerIdBits = 10L;
    // 序列号识位数
    private final static long sequenceBits = 10L;

    // 区域标志ID最大值
    private final static long maxRegionId = -1L ^ (-1L << regionIdBits);
    // 机器ID最大值
    private final static long maxWorkerId = -1L ^ (-1L << workerIdBits);
    // 序列号ID最大值
    private final static long sequenceMask = -1L ^ (-1L << sequenceBits);

    // 机器ID偏左移10位
    private final static long workerIdShift = sequenceBits;
    // 业务ID偏左移20位
    private final static long regionIdShift = sequenceBits + workerIdBits;
    // 时间毫秒左移23位
    private final static long timestampLeftShift = sequenceBits + workerIdBits + regionIdBits;

    private static long lastTimestamp = -1L;

    private long sequence = 0L;
    private final long workerId;
    private final long regionId;

    //workerId>=0 && workerId<1024
    //regionId>=0 && regionId<8
    public CustomUUID(long workerId, long regionId) {

        // 如果超出范围就抛出异常
        if (workerId > maxWorkerId || workerId < 0) {
            throw new IllegalArgumentException("worker Id can't be greater than %d or less than 0");
        }
        if (regionId > maxRegionId || regionId < 0) {
            throw new IllegalArgumentException(
                    "datacenter Id can't be greater than %d or less than 0");
        }

        this.workerId = workerId;
        this.regionId = regionId;
        System.out.println("区域标志ID最大值=" + maxRegionId + ", 机器ID最大值=" + maxWorkerId + ", 序列号ID最大值=" + sequenceMask);
    }

    public CustomUUID(long workerId) {
        // 如果超出范围就抛出异常
        if (workerId > maxWorkerId || workerId < 0) {
            throw new IllegalArgumentException("worker Id can't be greater than %d or less than 0");
        }
        this.workerId = workerId;
        this.regionId = 0;
        System.out.println("区域标志ID最大值=" + maxRegionId + ", 机器ID最大值=" + maxWorkerId + ", 序列号ID最大值=" + sequenceMask);
    }

    public long generate() {
        return this.nextId(false, 0);
    }

    /**
     * 实际产生代码的
     *
     * @param isPadding
     * @param busId
     * @return
     */
    private synchronized long nextId(boolean isPadding, long busId) {

        long timestamp = timeGen();
        long paddingnum = regionId;

        if (isPadding) {
            paddingnum = busId;
        }

        if (timestamp < lastTimestamp) {
            try {
                throw new Exception("Clock moved backwards.  Refusing to generate id for "
                        + (lastTimestamp - timestamp) + " milliseconds");
            } catch (Exception e) {
                e.printStackTrace();
            }
        }

        // 如果上次生成时间和当前时间相同,在同一毫秒内
        if (lastTimestamp == timestamp) {
            // sequence自增,因为sequence只有10bit,所以和sequenceMask相与一下,去掉高位
            sequence = (sequence + 1) & sequenceMask;
            // 判断是否溢出,也就是每毫秒内超过1024,当为1024时,与sequenceMask相与,sequence就等于0
            if (sequence == 0) {
                // 自旋等待到下一毫秒
                timestamp = tailNextMillis(lastTimestamp);
            }
        } else {
            // 如果和上次生成时间不同,重置sequence,就是下一毫秒开始,sequence计数重新从0开始累加,
            // 为了保证尾数随机性更大一些,最后一位设置一个随机数
            sequence = new SecureRandom().nextInt(10);
        }

        lastTimestamp = timestamp;

        return ((timestamp - twepoch) << timestampLeftShift) | (paddingnum << regionIdShift)
                | (workerId << workerIdShift) | sequence;
    }

    // 防止产生的时间比之前的时间还要小(由于NTP回拨等问题),保持增量的趋势.
    private long tailNextMillis(final long lastTimestamp) {
        long timestamp = this.timeGen();
        while (timestamp <= lastTimestamp) {
            timestamp = this.timeGen();
        }
        return timestamp;
    }

    // 获取当前的时间戳
    protected long timeGen() {
        return System.currentTimeMillis();
    }


    public static void main(String args[]) {
        CustomUUID uuid1 = new CustomUUID(1l, 0);
        CustomUUID uuid2 = new CustomUUID(2l, 0);
        for (int i = 0; i < 100; i++) {
            System.out.println(uuid1.nextId(false, 0));
            System.out.println(uuid2.nextId(false, 0) + "-");
        }

    }

}

使用自定义的这种方法需要注意的几点:

为了保持增长的趋势,要避免有些服务器的时间早,有些服务器的时间晚,需要控制好所有服务器的时间,而且要避免NTP时间服务器回拨服务器的时间。
在跨毫秒时,序列号总是归0,会使得序列号为0的ID比较多,导致生成的ID取模后不均匀,所以序列号不是每次都归0,而是归一个0到9的随机数。
使用这个CustomUUID类,最好在一个系统中能保持单例模式运行。
原文地址:http://www.androidchina.net/4744.html
分享到:
评论

相关推荐

    全局自增ID设计方案

    在大型互联网应用中,随着用户数的增加,为了提高应用的性能,我们经常需要对数据库进行分库分表操作。在单表时代,我们可以完全依赖于数据库的自增ID来唯一标识一个用户或数据对象

    分布式架构系统生成全局唯一序列号的一个思路

    分布式架构下,唯一序列号生成是我们在设计一个系统,尤其是数据库使用分库分表的时候常常会遇见的问题。当分成若干个sharding表后,如何能够...1.全局唯一 2.支持高并发 3.能够体现一定属性 4.高可靠,容错单点故障

    sequence:具有全局唯一,粗略顺序,高性能,高可用性,可伸缩性和其他特征的分布式ID生成器

    全局唯一 可以利用时间的有序性, 并且在某个时间单元下采用自增序列 粗略有序 在分布式系统中, 难以做到绝对有序, 因此可以采用相对有序的方式 可反解 一个 ID 在生成后, 本身就带有很多信息量, 在存储层面可以省下...

    bamboo-sequence.jar

    这时候全局唯一的id生成系统就派上了用场。当然这只是id生成其中的一种应用场景。那么id生成系统有哪些要求呢? 全局唯一的id:无论怎样都不能重复,这是最基本的要求了 高性能:基础服务尽可能耗时少,如果能够...

    java面试难点讲解:hashmap,spring aop,classload,dubbo,zookeeper,session等。

    分库分表之后分布式下如何保证ID全局唯一性 互联网企业必备高质量API网关接口设计 大型公司面试必答之数据结构与算法精讲 高性能网络编程必备技能之IO与NIO阻塞分析 无处不在的Spring AOP事务及踩过的坑

    库存管理系统课程设计报告.doc

    3 二、系统详细设计3 系统总体设计3 2.1.1 运行环境3 2.1.2 系统流程3 2.1.3 系统构造3 系统接口的概要设计3 2.2.1 用户接口3 2.3 数据库概要设计3 2.3.1 物理构造设计3 三、系统实现3 3.1 系统开发环境3 3.2 系统...

    最新Java面试题视频网盘,Java面试题84集、java面试专属及面试必问课程

    分库分表之后分布式下如何保证ID全局唯一性.mp4 │ │ │ └─15.大型公司面试必答之数据结构与算法精讲 │ 大型公司面试必答之数据结构与算法(一)-达摩老师.mp4 │ 大型公司面试必答之数据结构与算法(二).mp4 ...

    C#微软培训资料

    第二部分 C#程序设计基础.28 第四章 数 据 类 型 .28 4.1 值 类 型 .28 4.2 引 用 类 型 .33 4.3 装箱和拆箱 .39 4.4 小 结 .42 第五章 变量和常量 .44 5.1 变 量 .44 5.2 常 量 .46 5.3 小 结 .47 ...

    java微信公众号MVC开发框架

    WeixinConfigurer是微信上下文全局配置类,里面包含了处理微信类扫描、微信消息重排处理、微信方法执行线程池大小、微信方法调用超时阀值等方面的配置,packages包扫描配置是唯一必须的配置部分,这个配置在快速入门...

    什么是VLAN

     VLAN是为解决以太网的广播问题和安全性而提出的一种协议,它在以太网帧的基础上增加了VLAN头,用VLAN ID把用户划分为更小的工作组,限制不同工作组间的用户互访,每个工作组就是一个虚拟局域网。虚拟局域网的好处...

    ExtAspNet v2.2.1 (2009-4-1) 值得一看

    -Grid中TemplateField生成到页面中控件具有唯一ID,例如Grid1_ct5_Label2,Grid1_ct6_Label2(feedback:geruger)。 +2009-09-27 v2.1.2 -为Tree控件增加GetExpandAllNodesReference和...

    Oracle SQL高级编程(资深Oracle专家力作,OakTable团队推荐)--随书源代码

    7.8 使用GROUPING_ID()来扩展报告 187 7.9 GROUPING SETS与ROLLUP() 191 7.10 GROUP BY局限性 193 7.11 小结 196 第8章 分析函数 197 8.1 示例数据 197 8.2 分析函数剖析 198 8.3 函数列表 199 8.4 聚合函数...

    C++ MFC实现飞机大战游戏

     在MFC中,afxmessagebox是全局的对话框最安全,也最方便。 2.6内存释放  在VC/MFC用CDC绘图时,频繁的刷新,屏幕会出现闪烁的现象,CPU时间占用率相当高,绘图效率极低,很容易出现程序崩溃。及时的释放程序所...

    ExtAspNet_v2.3.2_dll

    -Grid中TemplateField生成到页面中控件具有唯一ID,例如Grid1_ct5_Label2,Grid1_ct6_Label2(feedback:geruger)。 +2009-09-27 v2.1.2 -为Tree控件增加GetExpandAllNodesReference和...

    华为编程开发规范与案例

    接口类问题(B类)-指设计、编码中出现的函数和环境、其他函数、全局/局部变量或数据变量之间的数据/控制传输不匹配的问题,在系统中起重要作用,将导致模块间配合失效等严重问题; 维护类问题(C类)-指设计、...

    oracle学习文档 笔记 全面 深刻 详细 通俗易懂 doc word格式 清晰 连接字符串

    在创建表时,经常会创建该表的主键、外键、唯一约束、Check约束等  语法结构 create table 表名( [字段名] [类型] [约束] ……….. CONSTRAINT fk_column FOREIGN KEY(column1,column2,…..column_n) ...

Global site tag (gtag.js) - Google Analytics