`

全局唯一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(Global Unique Identifier,简称GUID)在IT...总的来说,全局唯一ID生成是分布式系统设计中的核心问题之一,涉及多种技术和策略。理解并合理选用这些技术,对于构建高性能、高可用的分布式系统至关重要。

    全局自增ID设计方案

    ### 全局自增ID设计方案 #### 背景与挑战 在大型互联网应用中,随着用户数量的急剧增加,为了提升应用性能、确保系统的稳定运行,常常需要对数据库进行分库分表处理。在传统的单表环境中,数据库自带的自增ID功能...

    生成数字的全局唯一Id.zip

    "生成数字的全局唯一Id.zip" 提供了一个Java实现,利用雪花算法来生成Long类型的唯一ID。下面将详细解释雪花算法以及如何在Java中实现它。 雪花算法(Snowflake Algorithm)是由Twitter开源的一种分布式ID生成方案...

    Springboot唯一编号整合,vesta全局唯一id生成器

    通过以上步骤,我们可以在SpringBoot项目中成功地集成Vesta ID Generator,为分布式系统提供稳定、高效的全局唯一ID生成方案。在高并发环境下,Vesta的优秀性能和易用性将为系统的可靠性和扩展性带来显著提升。

    全局唯一id生成器vesta.rar

    在分布式系统设计中,全局唯一ID的生成是数据库设计、消息队列、微服务等组件的关键部分。例如,在数据库中,主键通常需要是全局唯一的,Vesta可以提供这样的保证。在消息队列中,每条消息都需要一个唯一的ID,以便...

    分布式唯一ID解决方案-雪花算法.docx

    全局唯一ID几乎是所有设计系统时都会遇到的,全局唯一ID在存储和检索中有至关重要的作用。ID生成器在应用程序中,经常需要全局唯一的ID作为数据库主键。如何生成全局唯一ID?首先,需要确定全局唯一ID是整型还是字符...

    dedid全局唯一 ID(主键)生成器

    Snowflake算法是一种基于时间戳、工作节点ID和序列号的分布式ID生成方案,确保在分布式环境中产生的ID具有全局唯一性、时间有序性和无冲突性。 然而,与Snowflake不同的是,dedid生成器的最后12位允许使用不仅仅是...

    java 获取分布式唯一ID.雪花ID

    雪花ID(Snowflake ID)是一种被广泛采用的解决方案,由Twitter开源,其设计目标就是生成全局唯一的、时间序列的64位整数ID。以下我们将详细探讨这个话题。 首先,雪花ID的结构分为6部分,共64位: 1. **符号位**...

    cpp-idgen是一个可以生成全局唯一自增id的分布式的高可用服务

    cpp-idgen是一个专门为生成全局唯一且自增ID设计的分布式高可用服务,它在C++编程语言环境下实现,适用于各种需要大量唯一标识的系统。在现代互联网应用中,尤其是在大型分布式系统中,对唯一ID的需求尤为突出,cpp-...

    10-发号器:如何保证分库分表后ID的全局唯一性?_For_group_share1

    在分布式数据库环境中,...总的来说,解决分库分表后ID的全局唯一性问题,需要综合考虑业务需求、数据结构、算法选择以及服务架构设计等多个方面。合理选择和设计ID生成策略,对于构建高效、稳定的分布式系统至关重要。

    分布式数据库唯一主键设计

    2. UUID(通用唯一标识符):UUID是一种广泛使用的全局唯一ID生成方案,但其128位长度可能导致存储和索引效率较低。 3. 时间戳+业务流水号:结合当前时间戳和业务流水号,可以生成具有时间顺序的唯一ID,适用于对...

    Mysql全局ID生成方法

    MySQL全局ID生成方法是数据库设计中的一个重要议题,特别是在大规模分布式系统中,确保ID的全局唯一性和高并发下的高效生成是数据库架构的关键要素。以下是一些常见的全局ID生成策略: 1. **基于CAS(Check and Set...

    50_一个关键的问题!分库分表之后全局id咋生成?.zip

    然而,随着数据的分散,一个新的挑战也随之而来:如何在分库分表后生成全局唯一ID(Global Unique ID,GID)。这个问题在标题“50_一个关键的问题!分库分表之后全局id咋生成?”中被明确提出。下面将详细探讨这个...

    java 分布式 代码生成器 唯一ID

    通过这种方式,可以在分布式环境下高效地生成全局唯一ID。 2. **UUID**:UUID(通用唯一标识符)是一种广泛使用的标准,它能生成128位的唯一ID。但由于UUID的生成包含随机性,可能在网络延迟或者并发较高的情况下...

    官方Java端口的Sqids生成短唯一的id从数字.zip

    Sqids设计的目标是在保持ID短小的同时,确保其全局唯一性。这通常通过结合时间戳、序列号和工作节点ID来实现。时间戳确保了ID的顺序性,序列号处理同一时间戳内的并发请求,工作节点ID则区分不同的生成器,防止ID...

    分布式架构系统生成全局唯一序列号的一些思路对比分析.docx

    分布式架构中的全局唯一序列号生成是一个关键问题,特别是在大规模并发的场景下,保证序列号的唯一性和高效生成是系统设计的重要考量。本文档主要对比分析了几种常见的解决方案,并结合携程的实践经验进行了深入探讨...

    开箱即用微服务leaf唯一id生成

    总的来说,Leaf作为一款成熟的微服务唯一ID生成方案,通过雪花算法实现了高效、可靠的全局唯一ID生成。对于开发者来说,它的易用性和高性能使得在微服务架构中集成和使用变得更加简单。结合具体的业务模块如mall-...

    Twitter的分布式自增ID雪花算法snowflake

    雪花算法广泛应用于分布式数据库、分布式消息队列、分布式锁等领域,需要全局唯一ID的地方都可以考虑使用。它的优点是简单高效,不需要中心化的协调服务,而且生成的ID具有良好的排序性,便于数据处理。 ### 7. ...

    分布式系统中唯一ID的生成方法共3页.pdf.zip

    标题中的“分布式系统中唯一ID的生成方法”指的就是如何在这样一个复杂的环境中设计和实现一个有效的ID生成策略。通常,这样的系统需要处理海量的数据和高并发请求,因此ID生成器必须具备高效、可扩展以及高度可用的...

Global site tag (gtag.js) - Google Analytics