首页 > 运维 > linux运维 > 正文

Go全局唯一ID选型集合

看不見的法師
发布: 2025-07-17 10:40:29
原创
269人浏览过

本地id生成器

  1. UUID

UUID有两种包:

  • github.com/google/uuid,仅支持V1和V4版本
  • github.com/gofrs/uuid,支持所有五个版本

以下简要介绍五个版本的区别

  • Version 1:基于时间戳和MAC地址(RFC-4122)
  • Version 2:基于时间戳、MAC地址和POSIX UID/GID(DCE 1.1)
  • Version 3:基于命名值的MD5哈希(RFC-4122)
  • Version 4:基于随机数(RFC-4122)
  • Version 5:基于命名值的SHA-1哈希(RFC-4122)

特点

  • 提供五个版本选择
  • 固定长度为36字节,较长
  • 无序

代码

package main

import (
    "github.com/gofrs/uuid"
    "fmt"
)

func main() {
    // Version 1: 时间 + MAC地址
    id, err := uuid.NewV1()
    if err != nil {
        fmt.Printf("uuid NewUUID err:%+v", err)
    }
    // id: f0629b9a-0cee-11ed-8d44-784f435f60a4 length: 36
    fmt.Println("id:", id.String(), "length:", len(id.String()))

    // Version 4: 纯随机数,错误会在内部引发panic
    id, err = uuid.NewV4()
    if err != nil {
        fmt.Printf("uuid NewUUID err:%+v", err)
    }
    // id: 3b4d1268-9150-447c-a0b7-bbf8c271f6a7 length: 36
    fmt.Println("id:", id.String(), "length:", len(id.String()))
}
登录后复制
  1. ShortUUID

初始值基于UUID Version 4;第二步根据alphabet变量长度(固定57)计算ID长度(固定22);第三步依次使用DivMod(欧几里得除法和模)返回值与alphabet做映射,合并生成ID。

特点

  • 基于UUID,但长度比UUID短,固定22字节

代码

package main

import (
    "github.com/lithammer/shortuuid/v4"
    "fmt"
)

func main() {
    id := shortuuid.New()
    // id: iDeUtXY5JymyMSGXqsqLYX length: 22
    fmt.Println("id:", id, "length:", len(id))
    // V22s2vag9bQEZCWcyv5SzL 固定不变
    id = shortuuid.NewWithNamespace("http://127.0.0.1.com")
    // id: K7pnGHAp7WLKUSducPeCXq length: 22
    fmt.Println("id:", id, "length:", len(id))

    // NewWithAlphabet函数可以用于自定义的基础字符串,字符串要求不重复、固定长度57
    str := "12345#$%^&*67890qwerty/;'~!@uiopasdfghjklzxcvbnm,.()_+·"
}
登录后复制
  1. XID

由时间戳、进程ID、MAC地址和随机数组成。有序性来源于对随机数部分的原子+1操作。

Go全局唯一ID选型集合

特点

  • 长度短
  • 有序
  • 不重复
  • 时间戳和随机数原子+1操作,避免了时钟回拨的问题

代码

package main

import (
    "github.com/rs/xid"
    "fmt"
    "crypto/md5"
)

func main() {
    // hostname+pid+atomic.AddUint32
    id := xid.New()
    containerName := "test"
    // 由于xid默认使用可重复ip地址填充4 5 6位。
    // 实际场景中,服务都是部署在docker中,这里吧ip地址位替换成了容器名
    // 这里制取了容器名MD5的前3位,验证会重复,放弃使用
    containerNameID := make([]byte, 3)
    hw := md5.New()
    hw.Write([]byte(containerName))
    copy(containerNameID, hw.Sum(nil))
    id[4] = containerNameID[0]
    id[5] = containerNameID[1]
    id[6] = containerNameID[2]

    // id: cbgjhf89htlrr1955d5g length: 12
    fmt.Println("id:", id, "length:", len(id))
}
登录后复制
  1. KSUID

由随机数和时间戳组成。时间戳占前4字节,后面均为随机数。

代码

package main

import (
    "github.com/segmentio/ksuid"
    "fmt"
)

func main() {
    id := ksuid.New()
    // id: 2CWvPg766SUvezbiiV9nzrTZsgf length: 20
    fmt.Println("id:", id, "length:", len(id))

    id1 := ksuid.New()
    id2 := ksuid.New()
    // id1:2CTqTLRxCh48y7oUQzQHrgONT2k id2:2CTqTHf07C09CXyRMHdGKXnY5HP
    fmt.Println(id1, id2)
    // 支持ID对比,这个功能比较鸡肋了,目前没想到有用的地方
    compareResult := ksuid.Compare(id1, id2)
    fmt.Println(compareResult) // 1
    // 判断顺序性
    isSorted := ksuid.IsSorted([]ksuid.KSUID{id2, id1})
    fmt.Println(isSorted) // true 降序
}
登录后复制
  1. ULID

由随机数和时间戳组成。

代码

package main

import (
    "github.com/oklog/ulid"
    "fmt"
    "time"
    "math/rand"
)

func main() {
    t := time.Now().UTC()
    entropy := rand.New(rand.NewSource(t.UnixNano()))
    id := ulid.MustNew(ulid.Timestamp(t), entropy)
    // id: 01G902ZSM96WV5D5DC5WFHF8WY length: 26
    fmt.Println("id:", id.String(), "length:", len(id.String()))
}
登录后复制
  1. Snowflake

大名鼎鼎的雪花算法,这里不做过多介绍。相对于UUID来说,雪花算法不会暴露MAC地址更安全、生成的ID也不会过于冗余。雪花的一部分ID序列是基于时间戳的,那么时钟回拨的问题就来了。上面提到的XID,一定程度上避免了时钟回拨的影响。那么什么是时钟回拨,后面会提到。

代码

package main

import (
    "fmt"
    "github.com/bwmarrin/snowflake"
)

func main() {
    node, err := snowflake.NewNode(1)
    if err != nil {
        fmt.Println(err)
        return
    }
    id := node.Generate().String()

    // id: 1552614118060462080 length: 19
    fmt.Println("id:", id, "length:", len(id))
}
登录后复制

数据库自增ID

这里常规是指数据库主键自增索引。

优点

  • 架构简单,容易实现
  • ID有序递增,IO写入连续性好
  • INT和BIGINT类型占用空间较小

缺点

  • 由于有序递增,易暴露业务量
  • 受到数据库性能限制,对高并发场景不友好
  • BIGINT最大是2^64-1,但是数据库单表肯定放不了这么多,那么就涉及到分表
  • 如果业务量真的太大了,主键的自增ID涨到头了,会发生什么?报错:主键冲突

Redis生成ID

Vinteo AI
Vinteo AI

利用人工智能在逼真的室内环境中创建产品可视化。无需设计师和产品照片拍摄

Vinteo AI 62
查看详情 Vinteo AI

通过Redis的原子操作INCR和INCRBY获得ID。相比数据库自增ID,Redis性能更好、更加灵活。不过架构强依赖Redis,Redis在整个架构中会产生单点问题。在流量较大的场景下,网络耗时也可能成为瓶颈。

ZooKeeper唯一ID

ZooKeeper是使用了Znode结构中的Zxid实现顺序增ID。Zookeeper类似一个文件系统,每个节点都有唯一路径名(Znode),Zxid是个全局事务计数器,每个节点发生变化时都会记录响应的版本(Zxid),这个版本号是全局唯一且顺序递增的。这种架构还是出现了ZooKeeper的单点问题。

号段模式Leaf-segment

把数据库自增主键换成了计数法。每个业务分配一个biz_tag,并记录各业务最大ID(max_id)、号段跨度(step)等数据。这样每次取号只需要更新biz_tag对应的max_id,就可以拿到step个ID。

Go全局唯一ID选型集合

优点

  • 除了拥有自增ID的优点之外,在性能上比自增ID更好
  • 扩展灵活
  • 使用灵活、可配置性强
  • 缓存机制,突发状况下短时间内能保证服务正常运转

缺点

  • ID是有序自增,容易暴露信息,不可用于订单
  • 在Leaf的缓存ID用完再去获取新号段的间隙,性能会有波动
  • 强依赖DB

增强版Leaf-segment

增强版是对上面描述的缺点2进行的改进——双cache。在Leaf的ID消耗到一定百分比时,常驻的后台进程会预先去号段服务获取新的号段并缓存。具体消耗百分比、及号段step根据业务消耗速度来定。

Go全局唯一ID选型集合

Tinyid

和增强版Leaf-segment类似,也是号段模式,提前加载号段。

Go全局唯一ID选型集合

Leaf-snowflake时钟回拨

服务器上的时间突然倒退回之前的时间。可能是认为的调整时间;也可能是服务器之间的时间校对。

实现方案

Go全局唯一ID选型集合

使用Zookeeper顺序增、全局唯一的节点版本号,替换了原有的机器地址。解决了时钟回拨的问题。前面介绍ZooKeeper的缺点,强依赖ZooKeeper、大流量下的网络瓶颈。下图的方案在Leaf-snowflake中通过缓存一个ZooKeeper文件夹,提高可用性。运行时,运行时,时差小于5ms会等待时差两倍时间,如果时差大于5ms报警并停止启动。

Go全局唯一ID选型集合

以上就是Go全局唯一ID选型集合的详细内容,更多请关注php中文网其它相关文章!

最佳 Windows 性能的顶级免费优化软件
最佳 Windows 性能的顶级免费优化软件

每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。

下载
来源:php中文网
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn
最新问题
开源免费商场系统广告
热门教程
更多>
最新下载
更多>
网站特效
网站源码
网站素材
前端模板
关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新 English
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送
PHP中文网APP
随时随地碎片化学习

Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号