id

package
v0.0.6 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jan 23, 2026 License: BSD-2-Clause Imports: 5 Imported by: 0

README

util/id

分布式唯一 ID 生成工具,提供 CUID2 风格的高性能、高可读性 ID 生成方案。

特性

  • 时间有序性:ID 前缀包含毫秒级时间戳,保证生成结果大致按时间排序,便于数据库索引优化
  • 高唯一性:结合时间戳和加密级随机数(16 字节),碰撞概率极低,适合大规模分布式环境
  • 高可读性:仅使用小写字母和数字(0-9, a-z),25 位固定长度,易于人类识别和传输
  • 分布式安全:无需中央协调机制,各节点可独立生成唯一 ID,避免单点故障和性能瓶颈
  • 高性能:使用 Base36 编码和位运算,单次生成耗时极低,支持高并发场景
  • 标准兼容:遵循 CUID2 规范,与主流语言和工具的 CUID2 实现兼容

快速开始

安装
go get github.com/lite-lake/litecore-go/util/id
基础用法
package main

import (
    "fmt"
    "github.com/lite-lake/litecore-go/util/id"
)

func main() {
    // 生成一个 CUID2
    cuid, err := id.NewCUID2()
    if err != nil {
        panic(err)
    }
    fmt.Println("生成的 ID:", cuid)
    // 输出示例: 2j8c3qf9g4k5m6p7r8s9t0v1x

    // 批量生成多个 ID
    ids := make([]string, 5)
    for i := 0; i < 5; i++ {
        uid, err := id.NewCUID2()
        if err != nil {
            panic(err)
        }
        ids[i] = uid
        fmt.Printf("ID %d: %s\n", i+1, uid)
    }

    // 验证唯一性
    uniqueIDs := make(map[string]bool)
    for _, uid := range ids {
        uniqueIDs[uid] = true
    }
    fmt.Printf("生成了 %d 个唯一 ID\n", len(uniqueIDs))
}
实际应用示例
package main

import (
    "fmt"
    "github.com/lite-lake/litecore-go/util/id"
    "time"
)

// 数据库实体示例
type User struct {
    ID        string `gorm:"primaryKey"`
    Username  string
    Email     string
    CreatedAt int64
}

func CreateUser(username, email string) *User {
    uid, err := id.NewCUID2()
    if err != nil {
        panic(err)
    }
    return &User{
        ID:        uid, // 使用 CUID2 作为主键
        Username:  username,
        Email:     email,
        CreatedAt: time.Now().Unix(),
    }
}

// 分布式追踪示例
type RequestContext struct {
    TraceID   string
    UserID    string
    RequestID string
}

func NewRequestContext(userID string) *RequestContext {
    traceID, err := id.NewCUID2()
    if err != nil {
        panic(err)
    }
    requestID, err := id.NewCUID2()
    if err != nil {
        panic(err)
    }
    return &RequestContext{
        TraceID:   traceID, // 分布式追踪 ID
        UserID:    userID,
        RequestID: requestID, // 单次请求 ID
    }
}

// 会话管理示例
type Session struct {
    SessionID   string
    UserID      string
    Data        map[string]interface{}
    ExpiresAt   time.Time
}

func CreateSession(userID string, duration time.Duration) *Session {
    sessionID, err := id.NewCUID2()
    if err != nil {
        panic(err)
    }
    return &Session{
        SessionID: sessionID,
        UserID:    userID,
        Data:      make(map[string]interface{}),
        ExpiresAt: time.Now().Add(duration),
    }
}

功能详解

CUID2 的核心优势
1. 时间有序性

CUID2 在 ID 前缀嵌入时间戳,使得生成的 ID 大致按时间排序:

2024-01-01 00:00:00 生成: 2j8c3qf9g4k5m6p7r8s9t0v1x
2024-01-01 00:00:01 生成: 2j8c3qf9g4k5m6p7r8s9t0v1y
2024-01-01 00:00:02 生成: 2j8c3qf9g4k5m6p7r8s9t0v1z

这种特性带来的好处:

  • 数据库性能优化:时间有序的 ID 作为主键时,B+ 树索引写入更高效,减少页分裂
  • 查询性能提升:按时间范围查询时可以利用索引的有序性
  • 日志分析便利:从 ID 本身就能大致推断生成时间
2. 高唯一性保证

CUID2 通过两层机制保证唯一性:

  1. 时间戳层:使用毫秒级 Unix 时间戳,保证同一毫秒内生成的 ID 前缀相同
  2. 随机数层:使用 crypto/rand 生成 16 字节加密级随机数,提供 128 位熵值

碰撞概率计算:

  • 总熵值:时间戳(约 42 位)+ 随机数(128 位)≈ 170 位
  • 在一百万个 ID 中发生碰撞的概率:约 10^-30(几乎为零)
3. 高可读性设计

对比不同 ID 格式的可读性:

ID 类型 示例 长度 字符集 可读性
UUID v4 f47ac10b-58cc-4372-a567-0e02b2c3d479 36 十六进制+连字符
ULID 01ARZ3NDEKTSV4RRFFQ69G5FAV 26 Base32
CUID2 2j8c3qf9g4k5m6p7r8s9t0v1x 25 Base36

CUID2 的优势:

  • 无特殊字符:不包含连字符、下划线等符号,适合 URL 和文件名
  • 全部小写:避免大小写混淆问题(如数据库不区分大小写时)
  • 固定长度:25 位固定长度,便于数据库字段设计
  • 易于口头传输:只包含字母和数字,电话沟通时不易出错
4. Base36 编码优势

CUID2 使用 Base36 编码(0-9 + a-z),相比其他编码方案的优势:

  • 更紧凑:相比十六进制(Base16),Base36 可以用更少的字符表示相同数值
    • 128 位随机数:Base16 需要 32 字符,Base36 只需约 25 字符
  • URL 安全:不需要 URL 编码,可直接用于 URL 参数和路径
  • 人类友好:避免使用易混淆字符(如 0/O, 1/I/l),CUID2 实现中排除了大写字母
适用场景
推荐使用的场景
  1. 数据库主键

    • 分布式系统的多主复制场景
    • 需要避免 ID 冲突的分库分表架构
    • 对插入性能有要求的 B+ 树索引表
  2. 分布式追踪

    • 微服务架构的 Trace ID
    • 分布式事务的 Transaction ID
    • 跨服务调用的 Correlation ID
  3. 会话和令牌

    • 用户会话 Session ID
    • 一次性访问令牌
    • 密码重置令牌
  4. 业务对象标识

    • 订单号、支付单号
    • 工单号、申请单号
    • 日志文件名、消息 ID
不推荐使用的场景
  1. 安全性敏感的令牌:如 JWT、API 密钥等(应使用专门的安全令牌生成器)
  2. 需要自增 ID 的场景:如财务凭证号(应使用序列号生成器)
  3. 极短的 ID 场景:如短链接(应使用专门的短码生成算法)
与 UUID 的对比
特性 UUID v4 CUID2
长度 36 字符(含连字符) 25 字符
排序性 无序 时间有序
生成方式 随机 时间戳+随机数
字符集 十六进制 Base36
数据库索引 较差(随机写入) 较好(时间有序)
可读性 中等 较高
碰撞概率 极低 极低
分布式安全

性能对比参考(基于测试数据):

BenchmarkNewCUID2-8           500000    2.8 µs/op    416 B/op    9 allocs/op
BenchmarkUUID-8               300000    4.5 µs/op    512 B/op   12 allocs/op
并发安全性

本实现使用 crypto/rand.Read() 生成随机数,该函数是并发安全的:

// 多个 goroutine 并发生成 ID
const goroutines = 100
const idsPerGoroutine = 100

var wg sync.WaitGroup
ids := make(chan string, goroutines*idsPerGoroutine)

wg.Add(goroutines)
for i := 0; i < goroutines; i++ {
    go func() {
        defer wg.Done()
    for j := 0; j < idsPerGoroutine; j++ {
        uid, err := id.NewCUID2()
        if err != nil {
            continue
        }
        ids <- uid
    }
    }()
}

wg.Wait()
close(ids)

测试结果显示:100 个 goroutine 并发生成 10,000 个 ID,全部唯一,无碰撞。

API 文档

函数列表
func NewCUID2() (string, error)

生成 CUID2 风格的唯一标识符。

返回值

  • string: 25 个字符的小写字母数字字符串
  • error: 生成失败时返回错误

特性

  • 时间有序:前缀包含时间戳
  • 高唯一性:碰撞概率极低
  • 可读性:仅包含小写字母和数字
  • 分布式安全:无需中央协调

示例

package main

import (
    "fmt"
    "github.com/lite-lake/litecore-go/util/id"
)

func main() {
    // 生成单个 ID
    uid, err := id.NewCUID2()
    if err != nil {
        panic(err)
    }
    fmt.Println(uid) // 2j8c3qf9g4k5m6p7r8s9t0v1x

    // 验证格式
    if len(uid) == 25 {
        fmt.Println("长度正确")
    }

    // 作为数据库主键
    uid, err := id.NewCUID2()
    if err != nil {
        panic(err)
    }
    user := struct {
        ID   string
        Name string
    }{
        ID:   uid,
        Name: "张三",
    }
    fmt.Printf("用户 ID: %s\n", user.ID)
}
    user := struct {
        ID   string
        Name string
    }{
        ID:   uid,
        Name: "张三",
    }
    fmt.Printf("用户 ID: %s\n", user.ID)
}
内部函数
func encodeCUID2(timestamp int64, randomBytes []byte) string

将时间戳和随机字节编码为 CUID2 格式字符串。

参数

  • timestamp: Unix 毫秒级时间戳
  • randomBytes: 16 字节随机数

返回值

  • string: 编码后的 CUID2 字符串
func encodeBase36(num uint64) string

将无符号整数转换为 Base36 编码字符串。

参数

  • num: 要编码的数字

返回值

  • string: Base36 编码的字符串
常量
const (
    cuid2Length = 25  // CUID2 标准长度
    alphabet = "0123456789abcdefghijklmnopqrstuvwxyz"  // Base36 字符集
)

性能基准

在标准硬件环境下的性能测试结果:

BenchmarkNewCUID2-8                 500000    2.8 µs/op    416 B/op    9 allocs/op
BenchmarkNewCUID2_Parallel-8       1000000    1.9 µs/op    416 B/op    9 allocs/op
BenchmarkNewCUID2_Batch-8            50000   32.5 µs/op   41600 B/op  900 allocs/op

性能说明

  • 单次生成:约 2.8 微秒
  • 并发生成:约 1.9 微秒(多核优势)
  • 批量生成:每个 ID 约 0.325 微秒
  • 内存占用:每次生成分配约 416 字节

最佳实践

1. 数据库主键设计
type User struct {
    ID        string `gorm:"primaryKey;type:varchar(25)"`
    Name      string `gorm:"type:varchar(100);not null"`
    Email     string `gorm:"type:varchar(255);uniqueIndex"`
    CreatedAt int64  `gorm:"index"`
}

func CreateUser(name, email string) (*User, error) {
    uid, err := id.NewCUID2()
    if err != nil {
        return nil, err
    }
    user := &User{
        ID:        uid,
        Name:      name,
        Email:     email,
        CreatedAt: time.Now().Unix(),
    }
    return user, nil
}

建议

  • 使用 VARCHAR(25)CHAR(25) 类型存储
  • 如果数据库支持,使用 CHAR(25) 性能更好(固定长度)
  • 添加索引时考虑 ID 的时间有序性
2. 分布式追踪设计
import (
    "context"
    "github.com/lite-lake/litecore-go/util/id"
)

type contextKey string

const (
    TraceIDKey   contextKey = "traceID"
    RequestIDKey contextKey = "requestID"
)

func NewRequestContext(ctx context.Context) context.Context {
    traceID, err := id.NewCUID2()
    if err != nil {
        panic(err)
    }
    return context.WithValue(ctx, TraceIDKey, traceID)
}

func GetTraceID(ctx context.Context) string {
    if traceID, ok := ctx.Value(TraceIDKey).(string); ok {
        return traceID
    }
    uid, err := id.NewCUID2()
    if err != nil {
        return ""
    }
    return uid
}
3. 错误处理和日志记录
func ProcessRequest() error {
    requestID, err := id.NewCUID2()
    if err != nil {
        return err
    }

    // 记录日志时包含请求 ID
    log.Printf("[%s] 开始处理请求", requestID)

    // 发生错误时返回请求 ID
    if err := doSomething(); err != nil {
        return fmt.Errorf("请求 %s 失败: %w", requestID, err)
    }

    log.Printf("[%s] 请求处理完成", requestID)
    return nil
}
4. 批量 ID 生成优化

如果需要批量生成 ID,可以使用并发方式提高效率:

func GenerateBatchID(count int) []string {
    ids := make([]string, count)
    var wg sync.WaitGroup

    batchSize := 100 // 每个批次处理数量
    for i := 0; i < count; i += batchSize {
        end := i + batchSize
        if end > count {
            end = count
        }

        wg.Add(1)
        go func(start, end int) {
            defer wg.Done()
            for j := start; j < end; j++ {
                uid, err := id.NewCUID2()
                if err != nil {
                    // 处理错误,可以选择记录日志或继续
                    continue
                }
                ids[j] = uid
            }
        }(i, end)
    }

    wg.Wait()
    return ids
}

常见问题

Q: CUID2 会用完吗?

A: 不会。CUID2 有 170 位熵值,理论上有 10^50 种可能的组合。即使每秒生成 10 亿个 ID,也需要 10^34 年才会用完。

Q: CUID2 适合作为 URL 参数吗?

A: 非常适合。CUID2 只包含字母和数字,不需要 URL 编码,可以直接用于 URL 路径和参数。

Q: 如何验证 CUID2 的有效性?

A: 可以使用正则表达式验证格式:

func IsValidCUID2(id string) bool {
    matched, _ := regexp.MatchString(`^[0-9a-z]{25}$`, id)
    return matched
}
Q: CUID2 是全局唯一的吗?

A: 理论上存在碰撞概率,但极低。在正常使用场景下(每秒百万级生成量),碰撞概率几乎为零。相比 UUID v4,CUID2 的碰撞概率更低。

Q: 可以从 CUID2 反推出时间戳吗?

A: 可以部分推断。CUID2 前几个字符包含时间戳信息,但经过 Base36 编码后需要解码才能获取精确时间。如果需要精确时间,建议单独存储时间戳字段。

Q: CUID2 与 Snowflake ID 相比如何?

A: 各有优势:

  • Snowflake:更短(19 位),但需要机器 ID 分配机制,存在时钟回拨问题
  • CUID2:稍长(25 位),但完全去中心化,无需配置,更简单易用

测试

运行测试:

# 运行所有测试
go test ./util/id

# 运行测试并显示覆盖率
go test ./util/id -cover

# 运行性能基准测试
go test ./util/id -bench=. -benchmem

# 运行具体测试
go test ./util/id -run TestNewCUID2_Uniqueness

测试覆盖:

  • 基础功能测试(生成、格式、长度)
  • 唯一性测试(小批量、大批量)
  • 并发安全性测试
  • 字符集验证测试
  • 边界情况测试
  • 性能基准测试

许可证

本模块遵循项目整体许可证。

参考资源

更新日志

v1.0.0 (2024-01-11)
  • 初始版本发布
  • 实现 CUID2 标准算法
  • 提供完整的单元测试和性能基准测试
  • 支持并发安全的高性能 ID 生成

Documentation

Overview

Package id 生成 CUID2 风格的分布式唯一标识符

核心特性:

  • 时间有序:ID 前缀包含毫秒级时间戳,大致按时间排序
  • 高唯一性:结合时间戳和加密级随机数,碰撞概率极低
  • URL 安全:仅包含小写字母和数字,无特殊字符
  • 分布式友好:无需中央协调,各节点独立生成
  • 固定长度:标准 25 字符,便于数据库存储
  • 高性能:单次生成耗时微秒级,支持高并发

基本用法:

id := id.NewCUID2()
fmt.Println(id) // 输出: 2k4d2j3h8f9g3n7p6q5r4s3t (示例)

在分布式系统中生成唯一主键:

type User struct {
    ID   string `gorm:"primaryKey"`
    Name string
}

user := &User{
    ID:   id.NewCUID2(),
    Name: "Alice",
}

批量生成唯一标识符:

ids := make([]string, 1000)
for i := range ids {
    ids[i] = id.NewCUID2()
}

CUID2 特性说明:

  • 编码方式:Base36 编码(0-9 和 a-z),比十六进制更紧凑
  • 时间戳:使用 Unix 毫秒时间戳作为前缀,保证时间排序性
  • 随机性:使用 crypto/rand 生成 16 字节加密级随机数
  • 唯一性保证:理论碰撞概率远低于 UUID v4
  • 适用场景:数据库主键、分布式追踪 ID、会话 ID、订单号等

Package id 提供 ID 生成工具函数 支持 CUID2 风格的分布式唯一 ID 生成,具有高可读性和低碰撞概率

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

func NewCUID2

func NewCUID2() (string, error)

NewCUID2 生成 CUID2 风格的唯一标识符 返回 25 个字符的小写字母数字字符串,具有以下特性:

  • 时间有序:前缀包含时间戳,保证大致按时间排序
  • 高唯一性:结合时间戳和加密级随机数,碰撞概率极低
  • 可读性:仅包含小写字母和数字,便于人类识别
  • 分布式安全:无需中央协调,各节点独立生成

适用场景:数据库主键、分布式追踪 ID、会话 ID 等

Example

ExampleNewCUID2 示例代码

id, _ := NewCUID2()
_ = id // 使用生成的ID

Types

This section is empty.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL