segmentio/ksuid
GitHub: segmentio/ksuid
Segment 出品的高性能 Go 库,用于生成天然按时间排序且无需协调的全局唯一标识符 KSUID。
Stars: 5261 | Forks: 199
# ksuid [](https://goreportcard.com/report/github.com/segmentio/ksuid) [](https://godoc.org/github.com/segmentio/ksuid) [](https://circleci.com/gh/segmentio/ksuid.svg?style=shield)
ksuid 是一个高效、全面且经过实战检验的 Go 库,用于
生成和解析一种被称为 *KSUID* 的特定全局唯一标识符。
本库作为其参考实现。
## 安装
```
go get -u github.com/segmentio/ksuid
```
## 什么是 KSUID?
KSUID 代表 K-Sortable Unique IDentifier(K-可排序唯一标识符)。它是一种类似于 [RFC 4122 UUID](https://en.wikipedia.org/wiki/Universally_unique_identifier) 的全局唯一标识符,从底层构建为可按生成时间戳进行“自然”排序,而无需任何特殊的类型感知逻辑。
简而言之,将一组 KSUID 通过 UNIX `sort` 命令处理,将得到一个按生成时间排序的列表。
## 为什么使用 KSUIDs?
生成唯一标识符的方法有很多,那么为什么要选择 KSUID 呢?
1. 自然按生成时间排序
2. 无碰撞、无需协调、无依赖
3. 高度可移植的表示形式
即使上述特性中只有一项对您很重要,KSUID 也是绝佳的选择!:) 许多项目选择使用 KSUIDs,*仅仅*是因为它的文本表示形式非常适合复制和粘贴。
有关此主题的延伸阅读:[UUID 简史](https://segment.com/blog/a-brief-history-of-the-uuid/)
### 1. 自然按生成时间排序
与更普及的 UUIDv4 不同,KSUID 包含一个时间戳组件,允许它们大致按生成时间排序。这不是一个严格的保证(不变量),因为它依赖于挂钟时间,但在实践中仍然非常有用。无论是二进制表示还是文本表示,无需任何特殊的排序逻辑即可按创建时间排序。
### 2. 无碰撞、无需协调、无依赖
虽然 RFC 4122 UUIDv1s *确实*包含时间组件,但没有足够的随机字节来提供强大的防碰撞(重复)保护。在如此低的熵的情况下,恶意方有可能猜出生成的 ID,这对于那些在隐式或显式情况下对对手猜测标识符很敏感的系统来说,会造成安全隐患。
为了适应 64 位数字空间,[Snowflake IDs](https://blog.twitter.com/2010/announcing-snowflake) 及其衍生产品需要进行协调以避免冲突,这大大增加了部署的复杂性和运维负担。
一个 KSUID 包含 128 位的伪随机数据(“熵”)。这个数字空间是广受认可的 RFC 4122 UUIDv4 标准所使用的 122 位的 64 倍。额外的时间戳组件可以看作是“额外熵”,这进一步降低了碰撞概率,在任何实际实现中几乎不可能发生物理碰撞。
### 3. 高度可移植的表示形式
其文本*和*二进制表示形式均可按字典序排序,这使得它们可以被直接放入原生不支持 KSUIDs 的系统中,并保留其按时间排序的特性。
文本表示形式是字母数字的 base62 编码,因此它可以“适应”任何接受字母数字字符串的地方。不使用任何分隔符,因此当设计用于处理人类可读文本的软件解析字符串化的 KSUIDs 时,它们不会被意外截断或标记化,而这正是 RFC 4122 UUIDs 文本表示形式常见的问题。
## KSUIDs 是如何工作的?
二进制 KSUIDs 是 20 字节的:一个 32 位无符号整数 UTC 时间戳和一个 128 位随机生成的 payload。时间戳使用大端序编码,以支持按字典序排序。时间戳的纪元调整为 2014 年 5 月 13 日,可提供超过 100 年的使用寿命。Payload 由加密强度高的伪随机数生成器生成。
文本表示形式固定为 27 个字符,采用字母数字 base62 编码,可按时间戳进行字典序排序。
## 高性能
本库专为在性能关键的代码路径中使用而设计。其代码已经过调整,消除了所有非必要的开销。`KSUID` 类型派生自固定大小的数组,从而消除了可变宽度类型所带来的额外引用追踪和内存分配。
API 为对内存分配敏感的代码路径提供了一个接口。例如,可以使用 `Append` 方法来解析文本表示形式,并在不进行额外堆分配的情况下替换 `KSUID` 值的内容。
所有公共包级别的“纯”函数都是并发安全的,并受全局互斥锁保护。对于在单个 Goroutine 中生成大量 KSUIDs 的热循环,提供了 `Sequence` 类型来消除潜在的锁竞争。
出于谨慎考虑,默认情况下使用加密安全的 PRNG 来生成 KSUID 的随机位。在性能极其关键的代码中,可以使用包含的 `FastRander` 类型来放宽这一限制。`FastRander` 使用标准的 PRNG,其种子由加密安全的 PRNG 生成。
*_注意:_虽然没有证据表明 `FastRander` 会增加碰撞的概率,但在唯一性对安全性至关重要的场景中不应使用它,因为生成的 ID 被对手预测的几率会增加。*
## 实战检验
这段代码已经在 Segment 的生产环境中使用了数年,跨越了各种不同的项目。在 Segment 一些对性能至关重要的大规模分布式系统中,已经生成了数以万亿计的 KSUIDs。
## 与其他组件良好兼容
为了便于与其他库集成,`KSUID` 类型实现了许多标准库接口,包括:
* `Stringer`
* `database/sql.Scanner` 和 `database/sql/driver.Valuer`
* `encoding.BinaryMarshal` 和 `encoding.BinaryUnmarshal`
* `encoding.TextMarshal` 和 `encoding.TextUnmarshal`
(对 `encoding/json` 友好!)
## 命令行工具
本包附带了一个命令行工具 `ksuid`,可用于生成 KSUIDs 以及检查现有 KSUIDs 的内部组件。提供机器友好的输出,适用于脚本化用例。
如果有 Go 构建环境,可以使用以下命令进行安装:
```
$ go install github.com/segmentio/ksuid/cmd/ksuid
```
## CLI 使用示例
### 生成一个 KSUID
```
$ ksuid
0ujsswThIGTUYm2K8FjOOfXtY1K
```
### 生成 4 个 KSUIDs
```
$ ksuid -n 4
0ujsszwN8NRY24YaXiTIE2VWDTS
0ujsswThIGTUYm2K8FjOOfXtY1K
0ujssxh0cECutqzMgbtXSGnjorm
0ujsszgFvbiEr7CDgE3z8MAUPFt
```
### 检查 KSUID 的组件
```
$ ksuid -f inspect 0ujtsYcgvSTl8PAuAdqWYSMnLOv
REPRESENTATION:
String: 0ujtsYcgvSTl8PAuAdqWYSMnLOv
Raw: 0669F7EFB5A1CD34B5F99D1154FB6853345C9735
COMPONENTS:
Time: 2017-10-09 21:00:47 -0700 PDT
Timestamp: 107608047
Payload: B5A1CD34B5F99D1154FB6853345C9735
```
### 生成一个 KSUID 并检查其组件
```
$ ksuid -f inspect
REPRESENTATION:
String: 0ujzPyRiIAffKhBux4PvQdDqMHY
Raw: 066A029C73FC1AA3B2446246D6E89FCD909E8FE8
COMPONENTS:
Time: 2017-10-09 21:46:20 -0700 PDT
Timestamp: 107610780
Payload: 73FC1AA3B2446246D6E89FCD909E8FE8
```
### 使用模板格式化的检查输出来检查 KSUID
```
$ ksuid -f template -t '{{ .Time }}: {{ .Payload }}' 0ujtsYcgvSTl8PAuAdqWYSMnLOv
2017-10-09 21:00:47 -0700 PDT: B5A1CD34B5F99D1154FB6853345C9735
```
### 使用模板格式化输出检查多个 KSUIDs
```
$ ksuid -f template -t '{{ .Time }}: {{ .Payload }}' $(ksuid -n 4)
2017-10-09 21:05:37 -0700 PDT: 304102BC687E087CC3A811F21D113CCF
2017-10-09 21:05:37 -0700 PDT: EAF0B240A9BFA55E079D887120D962F0
2017-10-09 21:05:37 -0700 PDT: DF0761769909ABB0C7BB9D66F79FC041
2017-10-09 21:05:37 -0700 PDT: 1A8F0E3D0BDEB84A5FAD702876F46543
```
### 生成 KSUIDs 并使用模板格式化输出 JSON
```
$ ksuid -f template -t '{ "timestamp": "{{ .Timestamp }}", "payload": "{{ .Payload }}", "ksuid": "{{.String}}"}' -n 4
{ "timestamp": "107611700", "payload": "9850EEEC191BF4FF26F99315CE43B0C8", "ksuid": "0uk1Hbc9dQ9pxyTqJ93IUrfhdGq"}
{ "timestamp": "107611700", "payload": "CC55072555316F45B8CA2D2979D3ED0A", "ksuid": "0uk1HdCJ6hUZKDgcxhpJwUl5ZEI"}
{ "timestamp": "107611700", "payload": "BA1C205D6177F0992D15EE606AE32238", "ksuid": "0uk1HcdvF0p8C20KtTfdRSB9XIm"}
{ "timestamp": "107611700", "payload": "67517BA309EA62AE7991B27BB6F2FCAC", "ksuid": "0uk1Ha7hGJ1Q9Xbnkt0yZgNwg3g"}
```
## OrNil 函数
有时候您确定您的 ksuid 是正确的。但是您需要从字节或字符串中获取它,并将其传递给结构体。为此,存在 OrNil 函数,这些函数在出错时返回 ksuid.Nil,并且可以直接在结构体中调用。
**函数:**
- `ParseOrNil()`
- `FromPartsOrNil()`
- `FromBytesOrNil()`
不使用 OrNil 的函数使用示例:
```
func getPosts(before, after []byte) {
b, err := ksuid.FromBytes(before)
if err != nil {
// handle error
}
a, err := ksuid.FromBytes(after)
if err != nil {
// handle error
}
sortOptions := SortOptions{Before: b, After: a}
}
```
这样操作会方便得多:
```
func getPosts(before, after []byte) {
sortOptions := SortOptions{
Before: ksuid.FromBytesOrNil(before),
After: ksuid.FromBytesOrNil(after),
}
}
```
OrNil 函数在许多其他库中也有使用:
- [satori/go.uuid](https://github.com/satori/go.uuid)
- [oklog/ulid](https://github.com/oklog/ulid) (panic)
## 其他语言的实现
- Python: [svix-ksuid](https://github.com/svixhq/python-ksuid/)
- Python: [cyksuid](https://github.com/timonwong/cyksuid)
- Ruby: [ksuid-ruby](https://github.com/michaelherold/ksuid-ruby)
- Java: [ksuid](https://github.com/ksuid/ksuid)
- Java: [ksuid-creator](https://github.com/f4b6a3/ksuid-creator)
- Rust: [svix-ksuid](https://github.com/svix/rust-ksuid)
- dotNet: [Ksuid.Net](https://github.com/JoyMoe/Ksuid.Net)
- dotnet: [KsuidDotNet](https://github.com/steve-warren/ksuid)
- Erlang: [erl-ksuid](https://github.com/exograd/erl-ksuid)
- Zig: [zig-ksuid](https://github.com/toffaletti/zig-ksuid)
## 许可证
ksuid 源代码在 MIT [许可证](/LICENSE.md)下提供。
标签:EVTX分析, Go, KSUID, Ruby工具, 分布式ID, 唯一标识符, 开发库, 日志审计