在分布式系统中,我们经常需要生成一个唯一的 ID,或用于订单,或用于其他场景。总之基本的要求就是要唯一且高效。对这个 ID 还希望其中能带有一些时间信息,这样即使我们后端的系统对记录进行了分库分表,也能够以时间顺序对这些记录进行排序。Twittersnowflake 算法是这种场景下的一个典型解法。

1. Twittersnowflake 算法说明

                                                               datacenter_id          sequence_id
    unused
                                                                      │                     │
       │                                                              │                     │
       │                                                              │                     │
       │  │                                                      │    │                     │
       │  │                                                      │    │                     │
       ▼  │◀──────────────────    41 bits   ────────────────────▶│    ▼                     ▼
    ┌─────┼──────────────────────────────────────────────────────┼────────┬────────┬────────────────┐
    │  0  │ 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0  │ 00000  │ 00000  │ 0000 0000 0000 │
    └─────┴──────────────────────────────────────────────────────┴────────┴────────┴────────────────┘
                                      ▲                                        ▲
                                      │                                        │
                                      │                                        │
                                      │                                        │
                                      │                                        │
                                      │                                        │
                                      │                                        │

                            time in milliseconds                          worker_id

首先确定我们的数值是 64 位,int64 类型,被划分为四部分,不含开头的第一个 bit,因为这个 bit 是符号位。用 41 位来表示收到请求时的时间戳,单位为毫秒,然后五位来表示数据中心的 id,然后再五位来表示机器的实例 id,最后是 12 位的循环自增 id(到达 1111 1111 1111 后会归 0)。

这样的机制可以支持我们在同一台机器上,同一毫秒内产生 2 ^ 12 = 4096 条消息。一秒共 409.6w 条消息。从值域上来讲完全够用了。

数据中心 + 实例 id 共有 10 位,可以支持我们每数据中心部署 32 台机器,所有数据中心共 1024 台实例。

表示 timestamp41 位,可以支持我们使用 69 年。当然,我们的时间毫秒计数不会真的从 1970 年开始记,那样我们的系统跑到 2039/9/7 23:47:35 就不能用了,所以这里的 timestamp 实际上只是相对于某个时间的增量,比如我们的系统上线是 2018-08-01,那么我们可以把这个 timestamp 当作是从 2018-08-01 00:00:00.000 的偏移量。

2. 基于 Golang 实现的 snowflake 算法

地址:https://github.com/bwmarrin/snowflake

下面我们做个简单测试:

package main

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

func main() {
    // Create a new Node with a Node number of 1
    node, err := snowflake.NewNode(1)
    if err != nil {
        fmt.Println(err)
        return
    }
    // Generate a snowflake ID.
    id := node.Generate()
    // Print out the ID in a few different ways.
    fmt.Printf("Int64  ID: %d\n", id)
    fmt.Printf("String ID: %s\n", id)
    fmt.Printf("Base2  ID: %s\n", id.Base2())
    fmt.Printf("Base64 ID: %s\n", id.Base64())
    // Print out the ID's timestamp
    fmt.Printf("ID Time  : %d\n", id.Time())
    // Print out the ID's node number
    fmt.Printf("ID Node  : %d\n", id.Node())
    // Print out the ID's sequence number
    fmt.Printf("ID Step  : %d\n", id.Step())
    // Generate and print, all in one.
    fmt.Printf("ID       : %d\n", node.Generate().Int64())
}

输出如下:

Int64  ID: 1062994665390739456
String ID: 1062994665390739456
Base2  ID: 111011000000100001000000010000100100110000000001000000000000
Base64 ID: MTA2Mjk5NDY2NTM5MDczOTQ1Ng==
ID Time  : 1542272652372
ID Node  : 1
ID Step  : 0
ID       : 1062994665390739457

标签:Golang