ULID规范解读与实现原理

前提最近发现各个频道推荐了很多ULID相关文章,这里对ULID的规范文件进行解读,并且基于Java语言自行实现ULID , 通过此实现过程展示ULID的底层原理 。
ULID出现的背景

ULID规范解读与实现原理

文章插图
ULID全称是Universally Unique Lexicographically Sortable Identifier,直译过来就是通用唯一按字典排序的标识符,它的原始仓库是https://github.com/ulid/javascript,该项目由前端开发者alizain发起,基于JavaScript语言编写 。从项目中的commit历史来看已经超过了5年,理论上得到充分的实践验证 。ULID出现的原因是一些开发者认为主流的UUID方案在许多场景下可能不是最优的,存在下面的原因:
  • UUID不是128 bit随机编码(由128 bit随机数通过编码生成字符串)的最高效实现方式
  • UUIDv1/v2实现在许多环境中是不切实际的,因为这两个版本的的实现需要访问唯一的、稳定的MAC地址
  • UUIDv3/v5实现需要唯一的种子,并且产生随机分布的ID , 这可能会导致在许多数据结构中出现碎片
  • UUIDv4除了随机性之外不需要提供其他信息 , 随机性可能会在许多数据结构中导致碎片
这里概括一下就是:UUIDv1/v2实现依赖唯一稳定MAC地址不现实,v3/v4/v5实现因为随机性产生的ID会"碎片化" 。
基于此提出了ULID,它用起来像这样:
ulid() // 01ARZ3NDEKTSV4RRFFQ69G5FAVULID的特点如下:
  • 设计为128 bit大小,与UUID兼容
  • 每毫秒生成1.21e+24个唯一的ULID(高性能)
  • 按字典顺序(字母顺序)排序
  • 标准编码为26个字符的字符串,而不是像UUID那样需要36个字符
  • 使用Crockfordbase32算法来提高效率和可读性(每个字符5 bit
  • 不区分大小写
  • 没有特殊字符串(URL安全,不需要进行二次URL编码)
  • 单调排序(正确地检测并处理相同的毫秒,所谓单调性,就是毫秒数相同的情况下,能够确保新的ULID随机部分的在最低有效位上加1位)
ULID规范下面的ULID规范在ULID/javascript类库中实现,此二进制格式目前没有在JavaScript中实现:
01AN4Z07BY79KA1307SR9X4MV3|----------||----------------| TimestampRandomness48bits80bits组成时间戳(Timestamp)
  • 占据48 bit(high)
  • 本质是UNIX-time,单位为毫秒
  • 直到公元10889年才会用完
随机数(Randomness)
  • 占据80 bit(low)
  • 如果可能的话,使用加密安全的随机源
排序"最左边"的字符必须排在最前面,"最右边"的字符排在最后(词法顺序,或者俗称的字典排序),并且所有字符必须使用默认的ASCII字符集 。在相同的毫秒(时间戳)内,无法保证排序顺序 。
规范的表示形式ULID规范的字符串表示形式如下:
ttttttttttrrrrrrrrrrrrrrrrwheret is Timestamp (10 characters)r is Randomness (16 characters)也就是:
  • 时间戳占据高(左边)10个(编码后的)字符
  • 随机数占据低(右边)16个(编码后的)字符
ULID规范的字符串表示形式的长度是确定的,共占据26个字符 。
编码使用Crockford Base32编码算法,这个编码算法的字母表如下:
0123456789ABCDEFGHJKMNPQRSTVWXYZ该字母表排除了ILOU字母,目的是避免混淆和滥用 。此算法实现不难 , 它的官网有详细的算法说明(见https://www.crockford.com/base32.html):
ULID规范解读与实现原理

文章插图
单调性(如果启用了单调性这个特性为前提下)当在相同的毫秒内生成多个ULID时,可以保证排序的顺序 。也就是说,如果检测到相同的毫秒,则随机分量在最低有效位上加1位(带进位) 。例如:

推荐阅读