想像一下你正在開發一個記事本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/xid
和 segmentio/ksuid
這兩個包。
oklog / ulid
允許使用自定義熵(隨機)源,但需要使用復雜的API。
sony / sonyflake
是最小的但也是最隨機的。 它基于Twitter的設計,用于為推文生成ID。
為簡單起見,示例代碼序列化了base16 中的sony / snoflake
。 在其他庫使用的base62編碼中它會更短,但是其他庫提供了開箱即用的功能,對于sony / snoflake
,我必須自己實現它。
最后一個是來自RFC 4112的UUID v4,用于比較。
更多相關知識:
- https://segment.com/blog/a-brief-history-of-the-uuid/ : ksuid的發展歷史
- https://firebase.googleblog.com/2015/02/the-2120-ways-to-ensure-unique_68.html : Betterguid設計的靈感
- http://antoniomo.com/blog/2017/05/21/unique-ids-in-golang-part-1/
- http://antoniomo.com/blog/2017/05/28/unique-ids-in-golang-part-2/
- http://antoniomo.com/blog/2017/06/03/unique-ids-in-golang-part-3/
- https://blog.twitter.com/engineering/en_us/a/2010/announcing-snowflake.html : details of Twitter’s snowflake on which sonyflake is based
本文的代碼: https://github.com/kjk/go-cookbook/tree/master/generate-unique-id
本文翻譯自:Generating good unique ids in Go