[翻译]Golang中生成良好的唯一ID的方式

想像一下你正在开发一个记事本App。
每一条记事都需要一个唯一ID。
如果你能协调,生成唯一ID是一件非常简单的事。
最简单的方式就是通过使用数据库:使用AUTOINCREMENT属性的列,然后当你插入一条新的记事的时候,数据库将会生成一个唯一ID。

但假如你不能协调呢?
列如,你想要你的App离线的时候也能生成唯一ID,这时候它是无法连接上数据库的。

在无法协调的情况下生成唯一ID的请求通常来自于分布式系统。
一个简单的解决方案是生成一个随机ID。
假如你使用16字节长度的随机串,将不存在产生一样的随机串的几率。
这是一个常见的问题,30多年前我们为此创建了一个名为UUID / GUID的标准。

我们可以比GUID做的更好,一个好的随机唯一ID遵循以下原则:
1、唯一性:基本原则,必须满足。
2、可排序的:可以使用随机唯一ID字符串进行排序。
3、具有时间属性:同一时间内,生成的ID彼此相近。
4、随机唯一ID字符串无需转义就要以做为URL的一部份。
5、越短越好。

使用Go实现的类似代码不多,它们遵循以下规则:
1、使用时间做为ID的一部份,使ID具有时间属性。
2、使用随机数据填充剩余的部份。
3、编码唯一ID为字符串,满足可排序及URL安全的条件。

下面列出的是一些生成唯一ID的Go包,以及它们生成的ID字符串的格式:

Package Id Format
github.com/segmentio/ksuid 0pPKHjWprnVxGH7dEsAoXX2YQvU 4 bytes of time (seconds) + 16 random bytes
github.com/rs/xid b50vl5e54p1000fo3gh0 4 bytes of time (seconds) + 3 byte machine id + 2 byte process id + 3 bytes random
github.com/kjk/betterguid -Kmdih_fs4ZZccpx2Hl1 8 bytes of time (milliseconds) + 9 random bytes
github.com/sony/sonyflake 20f8707d6000108 ~6 bytes of time (10 ms) + 1 byte sequence + 2 bytes machine id
github.com/oklog/ulid 01BJMVNPBBZC3E36FJTGVF0C4S 6 bytes of time (milliseconds) + 8 bytes random
github.com/chilts/sid 1JADkqpWxPx-4qaWY47~FqI 8 bytes of time (ns) + 8 random bytes
github.com/satori/go.uuid 5b52d72c-82b3-4f8e-beb5-437a974842c UUIDv4 from

你可以刷新测试页面看看不同时间ID格式的变化。
使用不同的包生成唯一ID的实例代码:

import (
    "github.com/chilts/sid"
    "github.com/kjk/betterguid"
    "github.com/oklog/ulid"
    "github.com/rs/xid"
    "github.com/satori/go.uuid"
    "github.com/segmentio/ksuid"
    "github.com/sony/sonyflake"
)

// To run:
// go run main.go

func genXid() {
    id := xid.New()
    fmt.Printf("github.com/rs/xid:           %s\n", id.String())
}

func genKsuid() {
    id := ksuid.New()
    fmt.Printf("github.com/segmentio/ksuid:  %s\n", id.String())
}

func genBetterGUID() {
    id := betterguid.New()
    fmt.Printf("github.com/kjk/betterguid:   %s\n", id)
}

func genUlid() {
    t := time.Now().UTC()
    entropy := rand.New(rand.NewSource(t.UnixNano()))
    id := ulid.MustNew(ulid.Timestamp(t), entropy)
    fmt.Printf("github.com/oklog/ulid:       %s\n", id.String())
}

func genSonyflake() {
    flake := sonyflake.NewSonyflake(sonyflake.Settings{})
    id, err := flake.NextID()
    if err != nil {
        log.Fatalf("flake.NextID() failed with %s\n", err)
    }
    // Note: this is base16, could shorten by encoding as base62 string
    fmt.Printf("github.com/sony/sonyflake:   %x\n", id)
}

func genSid() {
    id := sid.Id()
    fmt.Printf("github.com/chilts/sid:       %s\n", id)
}

func genUUIDv4() {
    id := uuid.NewV4()
    fmt.Printf("github.com/satori/go.uuid:   %s\n", id)
}

func main() {
    genXid()
    genKsuid()
    genBetterGUID()
    genUlid()
    genSonyflake()
    genSid()
    genUUIDv4()
}

完整的实例代码请查看:generate-unique-id/main.go

我该使用哪一个包?

上面所列的所有包都很不错。
但个人更喜欢rs/xidsegmentio/ksuid这两个包。
oklog / ulid允许使用自定义熵(随机)源,但需要使用复杂的API。
sony / sonyflake是最小的但也是最随机的。 它基于Twitter的设计,用于为推文生成ID。
为简单起见,示例代码序列化了base16 中的sony / snoflake。 在其他库使用的base62编码中它会更短,但是其他库提供了开箱即用的功能,对于sony / snoflake,我必须自己实现它。
最后一个是来自RFC 4112的UUID v4,用于比较。

更多相关知识:

本文的代码: https://github.com/kjk/go-cookbook/tree/master/generate-unique-id
本文翻译自:Generating good unique ids in Go

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,529评论 5 475
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,015评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,409评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,385评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,387评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,466评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,880评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,528评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,727评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,528评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,602评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,302评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,873评论 3 306
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,890评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,132评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,777评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,310评论 2 342

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,579评论 18 139
  • 本文主要介绍在一个分布式系统中, 怎么样生成全局唯一的 ID 一, 问题描述 在分布式系统存在多个 Shard 的...
    hanayona阅读 1,984评论 0 5
  • 关于Mongodb的全面总结 MongoDB的内部构造《MongoDB The Definitive Guide》...
    中v中阅读 31,881评论 2 89
  • 店铺淘客就是利用店长宝把商家放在淘宝里面的商品,采集到自己的店铺中去,自己加上差价去推广。 举个例子说吧: 1、先...
    蔚蓝色的天空c阅读 376评论 0 0
  • 一河奔流,它来到陡崖边。 崖边一行苦楝树,还有黄楝树,就连那四围的土,也有很深的苦味了。 河水到此,深皱眉头。就在...
    八里山人程远河阅读 435评论 8 8