oklog/ulid
GitHub: oklog/ulid
该项目是 ULID(通用唯一词典可排序标识符)的 Go 实现,解决了 UUID 无法按时间排序且编码冗长的问题。
Stars: 5043 | Forks: 187
# 通用唯一词典可排序标识符
[](https://github.com/oklog/ulid/releases/latest)

[](https://goreportcard.com/report/oklog/ulid)
[](https://coveralls.io/github/oklog/ulid?branch=master)
[](https://pkg.go.dev/github.com/oklog/ulid/v2)
[](https://raw.githubusercontent.com/oklog/ulid/master/LICENSE)
这是 [ulid/javascript](https://github.com/ulid/javascript) 的 Go 移植版本,并实现了二进制格式。
## 背景
GUID/UUID 在许多用例中可能不是最优的,因为:
- 它不是编码 128 位的最节省字符的方式
- UUID v1/v2 在许多环境中不实用,因为它需要访问唯一的、稳定的 MAC 地址
- UUID v3/v5 需要唯一的种子,并生成随机分布的 ID,这可能会导致许多数据结构出现碎片化
- UUID v4 除了随机性外不提供任何其他信息,这可能会导致许多数据结构出现碎片化
然而,ULID:
- 与 UUID/GUID 兼容
- 每毫秒可生成 1.21e+24 个唯一的 ULID(准确地说是 1,208,925,819,614,629,174,706,176 个)
- 支持按词典顺序排序
- 规范编码为 26 个字符的字符串,而 UUID 为 36 个字符
- 使用 Crockford 的 base32 以获得更好的效率和可读性(每个字符 5 位)
- 不区分大小写
- 无特殊字符(URL 安全)
- 单调的排序顺序(正确检测并处理同一毫秒)
## 安装
此包需要使用 Go modules。
```
go get github.com/oklog/ulid/v2
```
## 用法
ULID 由两部分构成:一个是毫秒级精度的时间戳,
另一部分是一些随机数据。
时间戳被建模为表示 Unix 时间(以毫秒为单位)的 uint64 值。
可以通过将 [time.Time](https://pkg.go.dev/time#Time) 传递给
[ulid.Timestamp](https://pkg.go.dev/github.com/oklog/ulid/v2#Timestamp) 来生成它们,
或者通过调用 [time.Time.UnixMilli](https://pkg.go.dev/time#Time.UnixMilli)
并将返回的值转换为 `uint64` 来生成。
随机数据取自提供的 [io.Reader](https://pkg.go.dev/io#Reader)。
这种设计在选择权衡时提供了更大的灵活性,但对新手来说
可能会有些困惑。
如果你只是想生成一个 ULID,并且(暂时)不关心
性能、加密安全性等细节,请使用
[ulid.Make](https://pkg.go.dev/github.com/oklog/ulid/v2#Make) 辅助函数。
此函数会调用 [time.Now](https://pkg.go.dev/time#Now) 来获取时间戳,
并使用一个进程全局的、
[伪随机](https://pkg.go.dev/math/rand)且[单调的](https://pkg.go.dev/github.com/oklog/ulid/v2#LockedMonotonicReader)熵源。
```
fmt.Println(ulid.Make())
// 01G65Z755AFWAKHE12NY0CQ9FH
```
更高级的用例应该使用
[ulid.New](https://pkg.go.dev/github.com/oklog/ulid/v2#New)。
```
entropy := rand.New(rand.NewSource(time.Now().UnixNano()))
ms := ulid.Timestamp(time.Now())
fmt.Println(ulid.New(ms, entropy))
// 01G65Z755AFWAKHE12NY0CQ9FH
```
在提供熵源时应当小心。
上面的示例使用了 [math/rand.Rand](https://pkg.go.dev/math/rand#Rand),
它不支持多个 goroutine 的并发使用。请考虑诸如
[x/exp/rand](https://pkg.go.dev/golang.org/x/exp/rand#LockedSource) 之类的替代方案。
对安全性敏感的用例应始终使用由 [crypto/rand](https://pkg.go.dev/crypto/rand) 提供的加密安全的熵。
对性能敏感的用例在生成 ID 时应避免同步操作。
一种选择是为每个并发的 goroutine 使用独立的熵源,
这不会导致锁竞争,但无法对随机数据提供强有力的保证,
并且不能在给定的毫秒内提供单调性。
一种常见的性能优化是使用 [sync.Pool](https://pkg.go.dev/sync#Pool) 来池化熵源。
单调性是指每个 ULID 都“大于”前一个 ULID 的属性。
ULID 自动具有单调性,但仅限于毫秒精度。
在同一毫秒内生成的 ULID 会根据其随机分量进行排序,
这意味着默认情况下它们是无序的。
你可以使用
[ulid.MonotonicEntropy](https://pkg.go.dev/github.com/oklog/ulid/v2#MonotonicEntropy) 或
[ulid.LockedMonotonicEntropy](https://pkg.go.dev/github.com/oklog/ulid/v2#LockedMonotonicEntropy)
来创建在给定毫秒内保持单调的 ULID,但需注意一些限制。
详情请参阅文档。
如果你不关心生成的 ID 基于时间的排序,那么就
没有理由使用 ULID!
还有许多其他类型的 ID,它们更简单、更快速、更小等等。
请考虑使用 UUID。
## 命令行工具
此仓库还提供了一个用于在命令行生成和解析 ULID 的工具。
```
go install github.com/oklog/ulid/v2/cmd/ulid@latest
```
用法:
```
Usage: ulid [-hlqz] [-f ] [parameters ...]
-f, --format= when parsing, show times in this format: default, rfc3339, unix, ms
-h, --help print this help text
-l, --local when parsing, show local time instead of UTC
-q, --quick when generating, use non-crypto-grade entropy
-z, --zero when generating, fix entropy to all-zeroes
```
示例:
```
$ ulid
01D78XYFJ1PRM1WPBCBT3VHMNV
$ ulid -z
01D78XZ44G0000000000000000
$ ulid 01D78XZ44G0000000000000000
Sun Mar 31 03:51:23.536 UTC 2019
$ ulid --format=rfc3339 --local 01D78XZ44G0000000000000000
2019-03-31T05:51:23.536+02:00
```
## 规范
以下是本仓库中实现的 ULID 的当前规范。
### 组件
**时间戳**
- 48 位
- UNIX 时间(以毫秒为单位)
- 直到公元 10889 年才会耗尽空间
**熵**
- 80 位
- 用户定义的熵源。
- 使用 [`ulid.Monotonic`](https://godoc.org/github.com/oklog/ulid#Monotonic) 实现同一毫秒内的单调性
### 编码
使用 [Crockford 的 Base32](http://www.crockford.com/wrmg/base32.html),如下所示。
此字母表排除了字母 I、L、O 和 U,以避免混淆和滥用。
```
0123456789ABCDEFGHJKMNPQRSTVWXYZ
```
### 二进制布局和字节顺序
各组件被编码为 16 个字节。每个组件均以最高有效字在前(网络字节顺序)进行编码。
```
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 |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
```
### 字符串表示
```
01AN4Z07BY 79KA1307SR9X4MV3
|----------| |----------------|
Timestamp Entropy
10 chars 16 chars
48bits 80bits
base32 base32
```
## 测试
```
go test ./...
```
## 基准测试
在 Intel Core i7 Ivy Bridge 2.7 GHz、MacOS 10.12.1 和 Go 1.8.0beta1 环境下
```
BenchmarkNew/WithCryptoEntropy-8 2000000 771 ns/op 20.73 MB/s 16 B/op 1 allocs/op
BenchmarkNew/WithEntropy-8 20000000 65.8 ns/op 243.01 MB/s 16 B/op 1 allocs/op
BenchmarkNew/WithoutEntropy-8 50000000 30.0 ns/op 534.06 MB/s 16 B/op 1 allocs/op
BenchmarkMustNew/WithCryptoEntropy-8 2000000 781 ns/op 20.48 MB/s 16 B/op 1 allocs/op
BenchmarkMustNew/WithEntropy-8 20000000 70.0 ns/op 228.51 MB/s 16 B/op 1 allocs/op
BenchmarkMustNew/WithoutEntropy-8 50000000 34.6 ns/op 462.98 MB/s 16 B/op 1 allocs/op
BenchmarkParse-8 50000000 30.0 ns/op 866.16 MB/s 0 B/op 0 allocs/op
BenchmarkMustParse-8 50000000 35.2 ns/op 738.94 MB/s 0 B/op 0 allocs/op
BenchmarkString-8 20000000 64.9 ns/op 246.40 MB/s 32 B/op 1 allocs/op
BenchmarkMarshal/Text-8 20000000 55.8 ns/op 286.84 MB/s 32 B/op 1 allocs/op
BenchmarkMarshal/TextTo-8 100000000 22.4 ns/op 714.91 MB/s 0 B/op 0 allocs/op
BenchmarkMarshal/Binary-8 300000000 4.02 ns/op 3981.77 MB/s 0 B/op 0 allocs/op
BenchmarkMarshal/BinaryTo-8 2000000000 1.18 ns/op 13551.75 MB/s 0 B/op 0 allocs/op
BenchmarkUnmarshal/Text-8 100000000 20.5 ns/op 1265.27 MB/s 0 B/op 0 allocs/op
BenchmarkUnmarshal/Binary-8 300000000 4.94 ns/op 3240.01 MB/s 0 B/op 0 allocs/op
BenchmarkNow-8 100000000 15.1 ns/op 528.09 MB/s 0 B/op 0 allocs/op
BenchmarkTimestamp-8 2000000000 0.29 ns/op 27271.59 MB/s 0 B/op 0 allocs/op
BenchmarkTime-8 2000000000 0.58 ns/op 13717.80 MB/s 0 B/op 0 allocs/op
BenchmarkSetTime-8 2000000000 0.89 ns/op 9023.95 MB/s 0 B/op 0 allocs/op
BenchmarkEntropy-8 200000000 7.62 ns/op 1311.66 MB/s 0 B/op 0 allocs/op
BenchmarkSetEntropy-8 2000000000 0.88 ns/op 11376.54 MB/s 0 B/op 0 allocs/op
BenchmarkCompare-8 200000000 7.34 ns/op 4359.23 MB/s 0 B/op 0 allocs/op
```
## 先前作品
- [ulid/javascript](https://github.com/ulid/javascript)
- [RobThree/NUlid](https://github.com/RobThree/NUlid)
- [imdario/go-ulid](https://github.com/imdario/go-ulid)
标签:EVTX分析, 日志审计