嘎里三分熟
  • 首页
  • JMusic
  • TSBay
  • 常用工具
  • About Me
  • 留言板
一行代码一世浮生
  1. 首页
  2. Java基础
  3. 正文

关于分布式ID

2018年10月21日 3575点热度 4人点赞 0条评论

一、背景

    唯一 ID,每个人都不陌生,因为在很多场景下我们都会接触到,比如:
        -- 用户唯一标识
        -- 订单唯一标识
        -- 商品唯一标示     唯一 ID 的生成方式是有很多的,而且在不同的业务场景下,会有不同的生成需求,即没有任何一种 ID 的生成方式是最适合所有业务场景的。     所以说,脱离业务来谈的技术,都是耍流氓。
    下文会分析一些分布式 ID 的生成方式以供参考。

二、分布式 ID —— UUID

    UUID(Universally Unique Identifier)是通用的唯一识别码,开放软件基金会(OSF)规范定义了包括网卡MAC地址、时间戳、名字空间(Namespace)、随机或伪随机数、时序等元素。利用这些元素来生成UUID。     UUID 是由128位二进制组成,一般转换成十六进制,然后用 String 表示。     在 java 中有个 UUID 类,在他的注释中我们看见这里有 4 种不同的UUID的生成策略:         UUID-01.png         ① 基于时间的; ② 基于 DCE 安全的; ③ 基于名称空间的; ④ 基于随机数的
        一般我们默认使用第四种,
    另外,我们如果想研究更多的 UUID 的算法,附上传送门:http://www.ietf.org/rfc/rfc4122.txt         UUID-02.png
  • 优点: 1、无序,即生成规则是无序的 2、本地生成,性能较高

  • 缺点: 1、无序,因为无序带来的弊端是不能生成连续的数字 2、32 位的十六进制,只能使用 String 来存储,空间占用相对会多一点


三、分布式 ID —— 数据库主键自增

    数据库主键自增比较简单,使用其实很方便,场景也相对比较广。     使用时只需要将字段设置为主键即可。
  • 优点: 1、简单,方便 2、排序和分页都很方便

  • 缺点: 1、并发性不好,受限于数据库性能 2、稳定性不高,受限于数据库的服务,需担心数据库宕机 3、分库分表时会有问题,需要定制化开发区解决 4、业务量太明显,因为是自增的数字,所以业务量很容易被研究

    所以,对于一些简单的业务场景,比如内部系统、to-B 的系统等,业务量并不高,使用数据库主键自增的方案为最佳。



四、分布式 ID —— redis

    redis 是单线程的,可以充分保证原子性
    redis 中的俩命令一用便会:Incr 和 IncrBy
  • 优点: 1、性能比数据库好 2、能满足自增的序列号

  • 缺点: 1、由于是基于内存的数据库,所以有可能会存在数据丢失的情况,会导致 ID 重复 2、稳定性需担心,因为完全依赖于 redis



五、分布式 ID —— 雪花算法(Snowflake)

    Snowflake 是 Twitter 提出来的一个算法,其目的是生成一个 64bit 的整数,如下图:         UUID-03.png     说明:
        1、1bit:一般是符号位,不做处理         2、41bit:用来记录时间戳,但是最多只可以记录69年(如果这个系统能用69年,估计已经被重构好多次了)         3、10bit:10bit用来记录机器ID,总共可以记录1024台机器,一般用前5位代表数据中心,后面5位是某个数据中心的机器ID         4、12bit:循环位,用来对同一个毫秒之内产生不同的ID,12位可以最多记录4095个,也就是在同一个机器同一毫秒最多记录4095个,多余的需要进行等待下毫秒。     当然,上面的说明只是一个最常规的使用说明,在我么实际使用的时候,需要根据业务来进行酌情调整,但是总长度不变(64bit),比如:
        案例一:当我们的业务服务器位于北、上、广、深四地,则可以将 10bit 的工作机器 id 调整为 3+7 模式,即 3 位(最多看标识8)用于标识机房位置,7位(最多标识128)用于标识机器 id。
        案例二:当我们的业务量没那么大的时候,则可以将最后的 12bit 的序列号再进行划分,调整为 10 + 2 模式,即 10 位(最多标识1024)用于标识序列号,2 位用于扩展。     雪花算法生成的 ID 有点多多,满足了 long 类型的 ID、性能并不低、无序等。

    自己实现的简单的雪花算法:

public class IdWorker{
    private long workerId;
    private long datacenterId;
    private long sequence = 0;
    /**
     * 2018/10/21日,从此时开始计算,可以用到2087年
     */
    private long twepoch = 1540126254592L;
    private long workerIdBits = 5L;
    private long datacenterIdBits = 5L;
    private long sequenceBits = 12L;
    private long workerIdShift = sequenceBits;
    private long datacenterIdShift = sequenceBits + workerIdBits;
    private long timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits;
    // 得到0000000000000000000000000000000000000000000000000000111111111111
    private long sequenceMask = ~(-1L << sequenceBits);
    private long lastTimestamp = -1L;
    public IdWorker(long workerId, long datacenterId){
        this.workerId = workerId;
        this.datacenterId = datacenterId;
    }
    public synchronized long nextId() {
        long timestamp = timeGen();
        // 时间回拨,抛出异常
        if (timestamp < lastTimestamp) {
            System.err.printf("clock is moving backwards.  Rejecting requests until %d.", lastTimestamp);
            throw new RuntimeException(String.format("Clock moved backwards.  Refusing to generate id for %d milliseconds",
                    lastTimestamp - timestamp));
        }
        // 时间回拨
        // 伪代码
        /*if ( timestamp < lastTimestamp) {
            if (lastTimestamp - timestamp <= 5) {
                // 时间在 5ms 以内,则直接等待 5ms,让其追上
                LockSupport.parkNanos(TimeUnit.MICROSECONDS.toNanos(5));
                timestamp = timeGen();
                // 如果还小,则利用扩展字段(2位扩展字段最多支持3次回拨)
                if (timestamp < lastTimestamp) {
                    // 扩展字段 + 1
                    extension += 1;
                    if (extension > maxExtension) {
                        throw new RuntimeException(String.format("Clock moved backwards.  Refusing to generate id for %d milliseconds",
                                lastTimestamp - timestamp));
                    }
                }
            } else {
                // 扩展字段 + 1
                extension += 1;
                if (extension > maxExtension) {
                    throw new RuntimeException(String.format("Clock moved backwards.  Refusing to generate id for %d milliseconds",
                            lastTimestamp - timestamp));
                }
            }
        }*/
        if (lastTimestamp == timestamp) {
            sequence = (sequence + 1) & sequenceMask;
            if (sequence == 0) {
                timestamp = tilNextMillis(lastTimestamp);
            }
        } else {
            sequence = 0;
        }
        lastTimestamp = timestamp;
        return ((timestamp - twepoch) << timestampLeftShift) |
                (datacenterId << datacenterIdShift) |
                (workerId << workerIdShift) |
                sequence;
    }
    /**
     * 当前ms已经满了
     * @param lastTimestamp
     * @return
     */
    private long tilNextMillis(long lastTimestamp) {
        long timestamp = timeGen();
        while (timestamp <= lastTimestamp) {
            timestamp = timeGen();
        }
        return timestamp;
    }
    private long timeGen(){
        return System.currentTimeMillis();
    }
    public static void main(String[] args) {
        IdWorker worker = new IdWorker(1,1);
        for (int i = 0; i < 30; i++) {
            System.out.println(worker.nextId());
        }
    }
}











本作品采用 知识共享署名 4.0 国际许可协议 进行许可
标签: java UUID 分布式ID 算法 雪花算法
最后更新:2018年10月21日

GoldenJet

爱折腾技术的90后漫威小死忠程序员一枚

点赞
< 上一篇
下一篇 >

文章评论

取消回复

通过电子邮件订阅博客

分类目录
  • BootStrap (2)
  • Bug集中营 (6)
  • Java web (3)
  • JavaScript (7)
  • Java基础 (17)
  • Java工具 (5)
  • Linux (3)
  • Python (3)
  • SpringBoot (14)
  • Spring基础 (8)
  • thymeleaf (1)
  • 娱乐 (3)
  • 小谈 (2)
  • 常用工具 (7)
  • 技术分析集 (5)
  • 技能 (10)
  • 源码 (4)
  • 科普类 (1)
  • 算法 (9)
  • 踩坑记 (5)
文章归档
  • 2020年11月 (1)
  • 2020年7月 (1)
  • 2020年4月 (2)
  • 2020年3月 (1)
  • 2020年1月 (1)
  • 2019年11月 (1)
  • 2019年10月 (1)
  • 2019年9月 (1)
  • 2019年8月 (1)
  • 2019年7月 (2)
  • 2019年5月 (2)
  • 2019年4月 (2)
  • 2019年3月 (3)
  • 2019年2月 (2)
  • 2019年1月 (2)
  • 2018年12月 (2)
  • 2018年11月 (3)
  • 2018年10月 (3)
  • 2018年9月 (2)
  • 2018年8月 (3)
  • 2018年7月 (2)
  • 2018年5月 (1)
  • 2018年4月 (3)
  • 2018年3月 (2)
  • 2018年2月 (3)
  • 2018年1月 (5)
  • 2017年12月 (2)
  • 2017年11月 (3)
  • 2017年10月 (1)
  • 2017年9月 (1)
  • 2017年8月 (1)
  • 2017年7月 (7)
  • 2017年6月 (5)
  • 2017年5月 (1)
  • 2017年4月 (2)
  • 2017年3月 (4)
  • 2017年2月 (2)
小伙伴友链
  • 前端驿站

COPYRIGHT © 2017-2023 嘎里三分熟. ALL RIGHTS RESERVED.

浙ICP备17005575号-1

浙公网安备 33010802009043号