ID设计

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代码:

import java.security.SecureRandom;

/**
 * 自定义 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;

    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;
    }

    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;
    }

    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();
    }
}

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

  • 为了保持增长的趋势,要避免有些服务器的时间早,有些服务器的时间晚,需要控制好所有服务器的时间,而且要避免NTP时间服务器回拨服务器的时间。
  • 在跨毫秒时,序列号总是归0,会使得序列号为0的ID比较多,导致生成的ID取模后不均匀,所以序列号不是每次都归0,而是归一个0到9的随机数。
  • 使用这个CustomUUID类,最好在一个系统中能保持单例模式运行。
时间: 2024-10-10 09:21:12

ID设计的相关文章

从数据库、页面加载速度角度思考 id设计 sku asin

w 超值套装 [小米红米4A][超值套装]小米 红米 4A 全网通 2GB内存 16GB ROM 香槟金色 移动联通电信4G手机 双卡双待[行情 报价 价格 评测]-京东  https://item.jd.com/4891234.html#crumb-wrap [小米红米4A][超值套装]小米 红米 4A 全网通 2GB内存 16GB ROM 玫瑰金色 移动联通电信4G手机 双卡双待[行情 报价 价格 评测]-京东  https://item.jd.com/4096723.html#crumb-

Instagram架构的分片和ID设计

前言 每秒上传超过25张图和90个"喜欢",在Instagram我们存了很多数据,为了确保把重要的数据都扔到内存里,达到快速响应用户的请求,我们已经开始把数据进行分片-换句话说,把数据放到更多的小桶子里,每个桶了装一部分数据. 我们的应用服务器跑的是Django和后端是PostgreSQL,在决定要分片后的第一个问题是,是否还继续用PostgreSQL作为主要数据仓库,或者换成别的?我们评估了一些NoSQL的解决方案,但最终决定最好的解决方案是:把数据分片到不同的PostgreSQL数

数据库的唯一标示符(ID)的选择

背景:数年的工作中,已经设计了很多系统或产品的数据库,有单机的.有局域网环境下的.也有互联网环境下的,对于不同的环境,设计考虑都有所不同.即使对于相同的环境,也会因为业务或者数据量的不同而有不同的设计.近期,又要设计一款互联网产品的数据库(MySQL服务).经过之前的积累,在表的ID设计这个环节就进行了大量的分析.比较.学习,对ID的设计也有了更系统和深刻的认知,把自己学习实践到的知识总结下来,分享给大家. 主键id的选择 对于关系数据库来说,设计每个表的第一步都会确定其主键,主键就是ID.在"

为什么数据库ID用int不用string

ID设计:唯一性自增ID(int),或者Guid(string)(36位,32位英文字符+4位横杠) 1.空间考虑:int型比Guid暂用空间少.//1,000,000,000(10位数就是10亿,顶多再扩张几位,足够用了) 2.效率考虑:整型比对 比字符型快.

中国工业设计现状

工业设计 对中国工业设计现状如果可以用一个比喻来形容的话:我们一直在为客户护养宠物,却没有属于自己的宠物,也没有为客户缔造新的宠物.对于护养的工作,我们又缺乏一定的专业性.很多时候,我们也没有能力去拥有或者缔造宠物. 中国工业设计现状像护养宠物:中国大部分的产品设计师一直在按照客户提出的想法和要求进行着产品的设计,参与企业产品的开发,为企业,为社会创造着价值.这是事实也是这个时代中国工业设计所必须经历的一个阶段.但这种设计的价值和影响力远远不是设计的全部. 不得不做的护养工作:即使委托设计的价值

业务系统需要什么样的ID生成器

业务系统需要什么样的ID生成器 ID 生成器在微博我们一直叫发号器,微博就是用这样的号来存储,而我微博里讨论的时候也都是以发号器为标签.它的主要目的确如平常大家理解的“为一个分布式系统的数据object产生一个唯一的标识”,但其实在一个真实的系统里可能也可以承担更多的作用.概括起来主要有以下几点: 唯一性 时间相关 粗略有序 可反解 可制造 下面我会分别讲每个作用后面的考虑和权衡,也会对比介绍一下业界已知的几种 ID 设计. 要唯一性,是否需要全局唯一? 说起全局唯一,通常大家都会在想到发号器服

AVEVA.PDMS.V12.1SP1 (DARS已解开)三维工厂设计

AVEVA.PDMS.V12.1SP1 (DARS已解开)三维工厂设计PDMS 所包含的一整套功能涉及到三维工厂设计的方方面面.要了解详情,请从列表中选择.完全交互式的实体着色三维工厂设计环境 电话TEL:18980583122  客服 QQ:1140988741数百位设计人员可同时执行同一个项目,整个过程完全可控,整个设计方案随时可见.设计人员可从扩展元件库中选择和定位参数化零件,以此来循序渐进地创建高度智能化的三维设计方案.冲突检查规则和可配置的完整性检查规则可以帮助设计人员创建“一次性正确

商品分类表设计

所要做的业务如下图所示: 表设计有两种方法: 方法1: 商品分类表(category) 包括字段:id, name, parentId 注:id为自增,第一级的父类ID设计为0. 商品与分类关联表(product_category) 包括字段:id, product, category 注:商品在添加时,与最后一级分类关联,但在存储时,要把所有的一级二级分类等都关联进来. 表数据如下: id name parentId 1 服装 0 2 女装 1 3 男装 1 4 数码 0 方法2: 商品分类表

业务系统需要怎样的全局唯一ID? #Ticktick#(环信首席架构师:一乐)

ID 生成器在微博我们一直叫发号器,微博就是用这样的号来存储,而我微博里讨论的时候也都是以发号器为标签.它的主要目的确如平常大家理解的"为一个分布式系统的数据object产生一个唯一的标识",但其实在一个真实的系统里可能也可以承担更多的作用.概括起来主要有以下几点: 1. 唯一性 2. 时间相关 3. 粗略有序 4. 可反解 5. 可制造 下面我会分别讲每个作用后面的考虑和权衡,也会对比介绍一下业界已知的几种 ID 设计. 1. 要唯一性,是否需要全局唯一? 说起全局唯一,通常大家都会