ULID和UUID|ULID的学习及使用

目录

1 什么是UUID

1.1 UUID的定义

1.2 UUID的组成

1.3 UUID的版本

1.4 UUID存在的问题

2 什么是ULID

2.1 ULID的组成

2.2 ULID的特点

2.3 ULID的应用场景

2.4 ULID的溢出错误处理

2.5 ULID的二进制布局

3 Java使用ULID

3.1 pom文件引入依赖:

3.2 使用Demo

3.2.1 常规生成ULID

3.2.2 生成单调排序ULID

3.2.3 测试单调性ULID大小比较

3.2.4 获取ULID的时间部分,随机数部分,创建时刻


1 什么是UUID

1.1 UUID的定义

UUID(Universally Unique Identifier),翻译为中文是通用唯一识别码,UUID的目的是让分布式系统中的所有元素都能有唯一的识别信息。每个人都可以创建不与其他人冲突的UUID,就不需要考虑数据库创建的时候名称重复的问题;

UUID 是由一组32位数的16进制数字所构成,是故 UUID 理论上的总数为16^32=2^128,约等于3.4 x 10^123。即若每秒产生一百万个UUID,要花100亿年才会将所有的UUID用完。

UUID生成ID的时候,只考虑随机性或者是时间戳,生成一个36个字符的长字符串

1.2 UUID的组成

UUID的十六个八位字节被表示为32个十六进制的数字,以连字号分页的五组来显示,形式为8-4-4-4-12,总共有36个字符【32个英文、数字字母和四个连字号】

例如:

123e4567-e89b-12d3-a456-426655440000

xxxxxxxx-xxxx-Mxxx-Nxxx-xxxxxxxxxxxx

数字 M 的四位表示 UUID 版本,当前规范有5个版本,M可选值为1,2,3,4,5;

数字 N 的一至四个最高有效位表示 UUID 变体( variant ),有固定的两位 10xx 因此只可能取值8, 9, a, b;

1.3 UUID的版本

UUID版本通过M表示,当前规范有5个版本,M可选值为1, 2, 3, 4, 5。这5个版本使用不同算法,利用不同的信息来产生UUID,各版本有各自优势,适用于不同情景。具体使用的信息

  • version 1, date-time & MAC address【日期时间和mac地址】

  • version 2, date-time & group/user id【日期时间和小组/用户id】

  • version 3, MD5 hash & namespace【MD5哈希值和命名空间】

  • version 4, pseudo-random number【伪随机数】

  • version 5, SHA-1 hash & namespace【安全散列算法1和命名空间】

SHA-1(英语:Secure Hash Algorithm 1,中文名:安全散列算法1)是一种密码散列函数。SHA-1可以生成一个被称为消息摘要的160位(20字节)散列值,散列值通常的呈现形式为40个十六进制数。

使用较多的是版本1和版本4,其中版本1使用当前时间戳和MAC地址信息。版本4使用(伪)随机数信息,128bit中,除去版本确定的4bit和variant(变体)确定的2bit,其它122bit全部由(伪)随机数信息确定。

因为时间戳和随机数的唯一性,版本1和版本4总是生成唯一的标识符。

若希望对给定的一个字符串总是能生成相同的 UUID,使用版本3或版本5。

Java中 UUID 使用版本4进行实现,所以由java.util.UUID类产生的 UUID,128个比特中,有122个比特是随机产生,4个比特标识版本被使用,还有2个标识变体被使用。

1.4 UUID存在的问题

  • UUID不是128 bit随机编码(由128 bit随机数通过编码生成字符串)的最高效实现方式

  • UUID的v1/v2实现依赖唯一稳定MAC地址不现实,v3/v4/v5实现因为随机性产生的ID会"碎片化"。

2 什么是ULID

ULID(Universally Unique Lexicographically Sortable Identifier)通用唯一词典分类标识符;ULID生成ID的时候,会同时考虑随机性和时间戳来生成id,并将他们编码为26个字符串(128位)

2.1 ULID的组成

ULID 的前10个字符表示时间戳,后16个字符表示随机性。这两个部分都是base 32编码字符串,分别使用48位和80位表示。

ULID规范的字符串表示形式的长度是确定的,共占据26个字符

ULID规范的字符串表示形式如下:

ttttttttttrrrrrrrrrrrrrrrr

where
t is Timestamp (10 characters)【
时间戳】
r is Randomness (16 characters)【随机数】

例如:
ULID
01FHZXHK8PTP9FVK99Z66GXQTX
ULID分解如下
时间戳 (48 bits) - 01FHZXHK8P
随机数 (80 bits) - TP9FVK99Z66GXQTX
 

注意

ULID的时间戳部分是以UNIX时间(以毫秒为单位)表示,知道公元10889年才会耗尽空间。

ULID 使用 Crockford 的 Base32 字母表 (0123456789ABCDEFGHJKMNPQRSTVWXYZ) 进行编码。它不包括 I、L、O 和 U 字母以避免任何意外的混淆

2.2 ULID的特点

  • 设计为128 bit大小,与UUID兼容

  • ULID是既基于时间戳又基于随机数,时间戳精确到毫秒,不存在冲突的风险,每毫秒生成1.21e+24个唯一的ULID(高性能)

  • 按字典顺序(字母顺序)排序

  • 标准编码为26个字符的字符串,而不是像UUID那样需要36个字符

  • 使用Crockford base32算法来提高效率和可读性(每个字符5 bit)

  • 不区分大小写

  • 没有特殊字符串(URL安全,不需要进行二次URL编码)

  • 单调排序(正确地检测并处理相同的毫秒,所谓单调性,就是毫秒数相同的情况下,能够确保新的ULID随机部分的在最低有效位上加1位)

词典可排序熊是ULID最突出的特点之一。最左边的字符必须排在最前面,最右边的字符必须排在最后(词汇顺序)。必须使用默认的ASCII字符集,在统一毫秒之内,不能保证排序顺序;

//单调排序举例:
monotonicUlid()  // 01GGS5FGGZA4DPDE82PHAEB7SZ
monotonicUlid()  // 01GGS5FGGZA4DPDE82PHAEB7T0
monotonicUlid()  // 01GGS5FGGZA4DPDE82PHAEB7T1
monotonicUlid()  // 01GGS5FGGZA4DPDE82PHAEB7T2
...
monotonicUlid()  // 01GGS5FGGZA4DPDE82PHAEB7TZ
monotonicUlid()  // 01GGS5FGGZA4DPDE82PHAEB7T0

可以看到上边的时间戳部分都为:01GGS5FGGZ,随机数部分只有最后以为一次增大,这就是
ULId的单调性

2.3 ULID的应用场景

  • 替换数据库自增id,无需DB参与主键生成;

  • 分布式环境下,替换UUID,全局唯一且毫秒精度有序;

  • 如果要按照日期对数据库进行分区分表,可以使用ULID中嵌入的时间戳来选择正确的分区分表

  • 如果毫秒精度内是可以接受的(毫秒内无序),可以按照ULID进行排序,二不是单独的created_at字段;

2.4 ULID的溢出错误处理

从技术实现上来看,26个字符的Base32编码字符串可以包含130 bit信息,而ULID只包含128bit的信息,所以可以使用Base32算法对ULID进行编码。

基于Base32编码算法能有生成的最大的合法的ULID数是:7ZZZZZZZZZZZZZZZZZZZZZZZZZ,并且使用的时间戳最大为纪元时间:281474976710655,即2^48-1。对于任何大于这个值的ULID进行解码或者编码的尝试都应该改被所有实现拒绝,以防止溢出错误。

ULID溢出错误测试:

如果时间戳时间大于最大值,则会出现java.lang.IllegalArgumentException: Invalid time value的错误;

2.5 ULID的二进制布局

0                   1                   2                   3
 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                      32_bit_uint_time_high                    |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|     16_bit_uint_time_low      |       16_bit_uint_random      |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                       32_bit_uint_random                      |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                       32_bit_uint_random                      |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 

二进制布局的多个部分被编码为16 byte,每个部分都以最高字节优先(网络字节序,也就是big-endian)进行编码;

3 Java使用ULID

3.1 pom文件引入依赖:

<!-- https://search.maven.org/artifact/com.github.f4b6a3/ulid-creator -->
<dependency>
  <groupId>com.github.f4b6a3</groupId>
  <artifactId>ulid-creator</artifactId>
  <version>5.1.0</version>
</dependency>
 

3.2 使用Demo

3.2.1 常规生成ULID

生成普通的ULID的方法:

  • UlidCreator.getUlid()

  • UlidCreator.getUlid(final Long time)【参数time – 自 1970-01-01(Unix 纪元)以来的毫秒数】

  • @Test
        void ulidTest(){
            Ulid ulid = UlidCreator.getUlid(1670837655000l);
            Ulid ulid1 = UlidCreator.getUlid(1670837655000l);
            Ulid ulid2 = UlidCreator.getUlid(1670837655000l);
            
            log.info("返回结果:" + ulid + "***********"+ ulid1 + "*********" + ulid2);
    
    		log.info("ULID时间:"+ String.valueOf(Ulid.getTime(ulid.toString())) +
                    "***********"+ String.valueOf(Ulid.getTime(ulid1.toString())) +
                    "***********"+ String.valueOf(Ulid.getTime(ulid2.toString())));
        }
    
    	返回结果:01GM2TYNER7TMDSJCJRARCHP6A***********01GM2TYNER1CKWEFH0HDSR0YQB*********01GM2TYNERC7BH0VZ8G3NPJNCZ
    	ULID时间:1670837655000***********1670837655000***********1670837655000
     

    结论:

    根据返回结果可以得到结果的时间戳部分是相同的,但是他的随机数部分是随机的没有排序顺序没有单调性;

    根据已ULID时间可以得到这些ULID的时间是相同的;

    3.2.2 生成单调排序ULID

    生成单调排序的ULID的方法:

  • UlidCreator.getMonotonicUlid()

  • UlidCreator.getMonotonicUlid(final Long time)【参数time – 自 1970-01-01(Unix 纪元)以来的毫秒数】

@Test
void ulidMonotonicTest(){
    Ulid monotonicUlid = UlidCreator.getMonotonicUlid(1670837655000l);
    Ulid monotonicUlid1 = UlidCreator.getMonotonicUlid(1670837655000l);
    Ulid monotonicUlid2 = UlidCreator.getMonotonicUlid(1670837655000l);
    log.info("结果是:" + monotonicUlid + "***********"+ monotonicUlid1 + "*********" + monotonicUlid2); 
	log.info("ULID时间:"+ String.valueOf(Ulid.getTime(monotonicUlid .toString())) +
                "***********"+ String.valueOf(Ulid.getTime(monotonicUlid1.toString())) +
                "***********"+ String.valueOf(Ulid.getTime(monotonicUlid2.toString())));
}

	返回结果:01GM2TYNERZ5GQDAG9Q7Y56YC3***********01GM2TYNERZ5GQDAG9Q7Y56YC4*********01GM2TYNERZ5GQDAG9Q7Y56YC5
	ULID时间:1670837655000***********1670837655000***********1670837655000
 

结论:

根据返回结果可以得到结果的时间戳部分是相同的,它的随机数部分也是相同的,但是随机数部分的最后一位逐次+1,提现了ULID的单调性;

根据已ULID时间可以得到这些ULID的时间是相同的;

3.2.3 测试单调性ULID大小比较

方法:

compareTo();

@Test
    void ulidSortTest(){
        Ulid monotonicUlid = UlidCreator.getMonotonicUlid(1670837655000l);
        Ulid monotonicUlid1 = UlidCreator.getMonotonicUlid(1670837655000l);
        Ulid monotonicUlid2 = UlidCreator.getMonotonicUlid(1670837655000l);
        log.info("结果是:" + monotonicUlid + "***********"+ monotonicUlid1 + "*********" + monotonicUlid2);
        log.info(String.valueOf(monotonicUlid.compareTo(monotonicUlid1)));
        log.info(String.valueOf(monotonicUlid.compareTo(monotonicUlid)));
        log.info(String.valueOf(monotonicUlid2.compareTo(monotonicUlid)));
    }

	结果是:01GM2TYNERJGWQQ9SA4BYQ4Y7C***********01GM2TYNERJGWQQ9SA4BYQ4Y7D*********01GM2TYNERJGWQQ9SA4BYQ4Y7E
	-1 (小于)
	0  (等于)
	1  (大于)
 

结论:

根据获取到的结果,可以可出他的单调ULID,也可以看出他的随机数部分是在最低位依次增大,之后使用compareTo方法也可以对其进行大小比较;

3.2.4 获取ULID的时间部分,随机数部分,创建时刻

方法:

获取时间:Ulid.getTime(String);

获取随机数:Ulid.getRandom(String);

获取创建时刻:Ulid.getInstant(String);

@Test
    void getTime(){
        Ulid monotonicUlid = UlidCreator.getMonotonicUlid();
        log.info(monotonicUlid.toString());
        String s = monotonicUlid.toString();
        log.info("时间是:"+String.valueOf(Ulid.getTime(s)));
        log.info("随机数部分是:"+String.valueOf(Ulid.getRandom(s)));
        log.info("创建时刻是:"+String.valueOf(Ulid.getInstant(s)));
		Date date = new Date(Ulid.getInstant(s).toEpochMilli());
		long time = date.getTime();
		log.info("日期时间是:"+date.toString());
		log.info("时间戳是:"+String.valueOf(time));
    }
	时间是:1670837655000
	随机数部分是:[B@7ecec90d
	创建时刻是:2022-12-12T09:34:15Z
	日期时间是:Mon Dec 12 17:34:15 CST 2022
 

结论:

根据代码运行结果可以得出,ULID可以通过方法获取到他的时间戳部分、随机数部分、以及创建ULID的时刻;

如果毫秒精度内是可以接受的(毫秒内无序),可以通过获取ULID的时间戳部分进行排序;

ULID Creatoricon-default.png?t=N7T8https://github.com/f4b6a3/ulid-creator

关于Base32加密算法icon-default.png?t=N7T8https://www.jianshu.com/p/1fce3f5d9201

  • 6
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值