Go 语言 UUID 库 google/uuid 源码解析:时钟信息

google/uuid 库地址

google/uuid 时间相关的部分汇聚在 uuid 包下的 time.go 文件中。

UUID 的 RFC 4122 变体中的版本1和版本2依赖于时钟信息,所以 uuid 库将时钟信息的实现定义在本文件中,供对应版本 UUID 的生成使用。

UUID 依赖于时钟信息的实现版本包含两方面的时钟信息:时间和时钟序列。下面将针对时间和时钟序列进行详细介绍。

时间

UUID版本1和版本2的规范,根据RFC 4122,使用的是自1582年10月15日以来的100纳秒数作为时间戳。

这部分信息被存储在 int64 类型中,其声明如下:

type Time int64

而时间戳的生成可以拆分为两部分,go 原生支持的 time.Now().UnixNano 和 Unix 纪元与格里历改日的差。

go 原生支持的 time.Now().UnixNano() 可以获取到距离 Unix 纪年(1970年1月1日00:00:00 UTC)的纳秒数,而我们需要的是距离格里历改日(1582年10月15日)的100纳秒数,所以除了对 time.Now().UnixNano() 除以 100 外还需要补充 Unix 纪元到格里历改日的差。

其实现如下:

const (
	lillian    = 2299160          // 1582年10月15日的儒略日
	unix       = 2440587          // 1970年1月1日的儒略日
	epoch      = unix - lillian   // 两个纪元之间的天数
	g1582      = epoch * 86400    // 两个纪元之间的秒数
	g1582ns100 = g1582 * 10000000 // 两个纪元之间的100纳秒数
)

其先定义Unix 纪元和格里历改日的儒略日常量,然后将其做差得到两个纪元之间的天数,乘以每天的总秒数 86400,最后乘以 10^7 得到两个纪元之间的100纳秒数。

所以最后时间戳的实现如下:

now := uint64(t.UnixNano()/100) + g1582ns100

时钟序列

时钟序列(clock sequence)在UUID(特别是版本1和版本2)中的使用主要是为了处理两种特定的情况,以确保UUID的唯一性:

  1. 时钟回拨:如果系统时钟被设置回一个较早的时间,那么在此期间生成的UUID可能会与之前生成的UUID发生冲突,因为它们可能会有相同的时间戳部分。通过在时钟回拨时改变时钟序列,可以保证即使在相同的时间戳下,生成的UUID也是唯一的。

  2. 快速生成UUID:在非常短的时间内(小于UUID时间戳分辨率的时间内)生成大量的UUID时,可能会耗尽给定时间戳内的所有可能的UUID。时钟序列提供了一种机制,允许在这种情况下通过改变时钟序列来继续生成唯一的UUID,而不是等待下一个时间戳。

但在 google/uuid 库中只用于解决时钟回拨,只有但发生时钟回拨时,才会增加时钟序列。

时钟序列通常占 16 位(2字节),最高的两位被固定用于特定目的,在此库中用于标识变体。

时钟序列在 google/uuid 库中的实现逻辑书写于 setClockSequence 函数中,实现原理是先生成两个 byte(uint8) 的序列然后拼凑成一个 uint16 的序列,最后抹除高2位并在最高位设置变体标识1。

func setClockSequence(seq int) {
	// 当传入 -1 时,代表着需要随机生成一个时钟序列值。
	if seq == -1 {
		// 长度为 2 的字节切片数组,用于存储随机生成的字节。
		var b [2]byte
		// randomBits 向 b 中随机填充字节。
		randomBits(b[:]) 
		// b[0] 成为 seq 的高 8 位,b[1] 为低 8 位。
		seq = int(b[0])<<8 | int(b[1])
	}
	// 将当前的时钟序列值保存到oldSeq变量中,以便后续比较。
	oldSeq := clockSeq
	// 设置我们的变体
	clockSeq = uint16(seq&0x3fff) | 0x8000
	// 时钟回拨导致时钟序列调整,调整时钟序列后更新 lasttime
	if oldSeq != clockSeq {
		lasttime = 0
	}
}

这段代码的难点有两处:

首先是:

seq = int(b[0])<<8 | int(b[1])

实现我们知道,我么使用 randomBits(b[:]) 的目的就是往切片数组 b 中填充数据,并且 byte 在 Go 中也等同于 uint8。那么在这段代码中,b 数组相当于存储了两个 uint8 的数。int(b[0]) 意味着将 uint8 转为 intint 虽然不同系统位数不同,但是相比于 uint8 都有一个特点,就是拥有更多的位数。int(b[1]) 也同理。现在我们假设 b[0] 二进制数表示为:10101010b[1] 二进制数表示为:01010101。则int(b[0]) 二进制数表示为 00000000 00000000 00000000 10101010int(b[1]) 二进制数表示为 00000000 00000000 00000000 01010101int(b[0])<<8 便是 00000000 00000000 10101010 00000000。于是 int(b[0])<<8 | int(b[1]) 二进制表示为 00000000 00000000 10101010 01010101

总而言之seq = int(b[0])<<8 | int(b[1])就是将 b 数组的两个字节转换为一个整数,b[0]作为高8位,b[1]作为低 8 位。这个整数将作为创建最终时钟序列值的蓝图,此时 seq 虽然是 int 类型,但是其只有低 16 位。

再就是:

clockSeq = uint16(seq&0x3fff) | 0x8000

我们知道 seq 类型虽然为 int,但其只有低 16 位,而 0x3fff 其实是 0011 11111111,所以 seq&0x3fff 其实就是保留低 14 位的值并抹去高 2 位的值。uint16(seq&0x3fff) 便是将结果转位 uint160x80001000 0000 0000 0000,所以 | 0x8000 其实是将最高位设置为 1,表示变体1。

完整时钟信息生成

  1. 获取当前时间
  2. 如果未初始化时钟序列则随机生成时钟序列
  3. 得到时间戳
  4. 当发生时钟回拨时,增加时钟序列避免重复 UUID 的出现。
  5. 记录此次生成 UUID 的时间
func getTime() (Time, uint16, error) {
	t := timeNow()

	// 如果我们还没有一个时钟序列,就设置一个。
	if clockSeq == 0 {
		setClockSequence(-1)
	}
	now := uint64(t.UnixNano()/100) + g1582ns100

	// 如果当前时间与上次生成 UUID 时间相比有倒退,则我们增加时钟序列
	if now <= lasttime {
		clockSeq = ((clockSeq + 1) & 0x3fff) | 0x8000
	}
	// 记录此次生成 UUID 的时间
	lasttime = now
	return Time(now), clockSeq, nil
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/775326.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

昇思25天学习打卡营第17天(+1)|Diffusion扩散模型

1. 学习内容复盘 本文基于Hugging Face&#xff1a;The Annotated Diffusion Model一文翻译迁移而来&#xff0c;同时参考了由浅入深了解Diffusion Model一文。 本教程在Jupyter Notebook上成功运行。如您下载本文档为Python文件&#xff0c;执行Python文件时&#xff0c;请确…

安乃达:看不懂的募资

不好玩啊&#xff0c;高标接连被S&#xff0c;市场激进资金找到了新股作为抱团方向。 首日大涨超100%&#xff0c;两日涨幅133%&#xff0c;今天果不其然被电风扇刮走了&#xff0c;今天我们聊聊新加入A股大本营的公司——安乃达。 首先&#xff0c;安乃达是国内直驱轮毂电机头…

vulnhub--IMF

环境 攻击机&#xff1a;192.168.96.4 靶机&#xff1a;ip未知 主机探测 确定靶机ip为32的主机 端口扫描 访问80端口 外围打点 在contact.php页面源码中找到了flag1 之后没啥突破 但查看网络后发现contact.php页面请求的三个js文件的文件名很有特点&#xff0c;猜测是base64编码…

数据结构——队列练习题

在C语言中&#xff0c;.和->运算符用于访问结构体的成员变量。它们之间的区别在于&#xff1a;.运算符用于访问结构体变量的成员。->运算符用于访问结构体指针变量的成员 1a&#xff08;rear指向队尾元素后一位&#xff0c;判空判满时牺牲一个存储单元&#xff09; 首先…

react 自定义 年-月-日 组件,单独选择年、月、日,并且产生联动

自定义 年-月-日 组件 code import { useState } from react function Year_Month_Date() {const [yearList, setYearList] useState([])const [monthList, setMonthList] useState([])const [dateList, setDateList] useState([])const [currentYear, setCurrentYear] u…

【React】上传文章封面基础实现

<Form.Item label"封面"><Form.Item name"type"><Radio.Group onChange{onTypeChange}><Radio value{1}>单图</Radio><Radio value{3}>三图</Radio><Radio value{0}>无图</Radio></Radio.Group&…

交叉测试的优点和缺点!

交叉测试在软件测试中具有重要的地位和作用。通过交叉测试&#xff0c;可以提高软件质量、提升用户体验、增加测试覆盖率、提高测试效率以及满足市场需求和竞争优势。因此&#xff0c;在软件开发和测试过程中&#xff0c;应充分重视交叉测试的实施和应用。 以下是对其优缺点的…

matplotlib下载安装

matplotlib下载安装过程同之前写的pygame很类似。 Pygame下载安装 python官网 1.搜索matplotlib 直接点进去 查看历史版本&#xff0c;因为新版本可能出现与python不匹配问题。 我选择3.6.3版本&#xff0c;因为我安装的python是3.8&#xff0c;可以匹配版本。同时window操…

泰迪智能科技企业项目试岗实训——数据分析类型

当今社会&#xff0c;就业技能的瞩目度正与日俱增。在竞争激烈的就业市场中&#xff0c;重视技能的培养和提升&#xff0c;通过学习与实践&#xff0c;增强自己的竞争力&#xff0c;为未来的职业发展打下坚实的基础&#xff0c;不仅有助于解决各类工作技能问题&#xff0c;更能…

使用Mybatis批量插入大量数据的实践

前言 在项目开发过程中&#xff0c;我们经常会有批量插入的需求&#xff0c;例如&#xff1a;定时统计任务 但是受限于MySQL中 max_allowed_packet 参数限制&#xff0c;5.7版本默认值为4M&#xff0c;这显然不太符合我们的需求&#xff0c;当然我们也可以通过修改此值来适应…

STM32和DHT11使用显示温湿度度(代码理解)+单总线协议

基于STM32CT&#xff0c;利用DHT11采集温湿度数据&#xff0c;在OLED上显示。一定要阅读DHT11数据手册。 1、 DHT11温湿度传感器 引脚说明 1、VDD 供电3.3&#xff5e;5.5V DC 2、DATA 串行数据&#xff0c;单总线 3、NC 空脚 4、GND 接地&#xff0c;电源负极 硬件电路 微…

【网络安全学习】漏洞利用:BurpSuite的使用-03-枚举攻击案例

如何使用BurpSuite进行枚举攻击 1.靶场选择 BurpSuite官方也是有渗透的教学与靶场的&#xff0c;这次就使用BurpSuite的靶场进行练习。 靶场地址&#xff1a;https://portswigger.net/web-security 登录后如下图所示&#xff0c;选择**【VIEW ALL PATHS】**&#xff1a; 找…

【fastadmin开发实战】经营数据自动识别录入

项目场景描述&#xff1a;每日录入各个门店的员工经营数据&#xff0c;直接从微信复制报数、系统识别录入。 解决方案&#xff1a;各个门店按照固定的汇报模板进行汇报&#xff08;如福田店有员工1、2、3、4、5号员工&#xff0c;每个员工按模板报数&#xff09; 例如&#xf…

快手矩阵系统源码:构建高效短视频生态的引擎

在短视频内容创作和管理领域&#xff0c;快手矩阵系统源码提供了一套全面的解决方案&#xff0c;帮助用户和企业高效地构建和管理自己的短视频平台。本文将深入探讨快手矩阵系统源码的核心功能&#xff0c;以及它如何助力用户在短视频领域取得成功。 快手矩阵系统源码概述 快…

2-3 图像分类数据集

MNIST数据集是图像分类任务中广泛使用的数据集之一&#xff0c;但作为基准数据集过于简单&#xff0c;我们将使用类似但更复杂的Fashion-MNIST数据集。 %matplotlib inline import torch import torchvision # pytorch模型关于计算机视觉模型实现的一个库 from torch.utils i…

windows启动Docker闪退Docker desktop stopped

Windows启动Docker闪退-Docker desktop stopped 电脑上很早就安装有Docker了&#xff0c;但是有一段时间都没有启动了&#xff0c;今天想启动启动不起来了&#xff0c;打开没几秒就闪退&#xff0c;记录一下解决方案。仅供参考 首先&#xff0c;参照其他解决方案&#xff0c;本…

对SRS媒体服务器进行漏洞扫描时,SRS的API模块会出现漏洞,如何修补这些漏洞的简单方法

目录 一、引言 1、srs介绍 2、媒体流介绍 3、应用场景 二、SRS的http_api介绍、及漏洞 1、概述 2、http_api模块的作用 &#xff08;1&#xff09;提供HTTP API服务 &#xff08;2&#xff09;管理和监控SRS服务器 &#xff08;3&#xff09;自定义开发 三、漏洞扫描…

昆虫学(书籍学习资料)

包括昆虫分类&#xff08;上下册&#xff09;、昆虫生态大图鉴等书籍资料。

搜索+动态规划

刷题刷题刷题刷题 ​​​​​​​​​​​​​​Forgery - 洛谷 | 计算机科学教育新生态 (luogu.com.cn) 思路&#xff1a; 需要两个数组&#xff0c;一个数组全部初始化为".",另一个数组输入数据&#xff0c;每碰到一个“.”就进行染色操作&#xff0c;将其周围的…

Django学习第五天

启动项目命令 python manage.py runserver 图像验证码生成随机字母或者数字 import random from PIL import Image, ImageDraw, ImageFont, ImageFilterdef check_code(width120, height40, char_length5, font_fileZixunHappyBold.ttf, font_size28):code []img Image.new…