分布式id生成
一、 MySQL自增ID
二、Twitter的snowflake算法
2.1 snowflake的id生成
首先确定我们的数值是64位,int64类型,被划分为五部分:
- 第一个bit位不用,代表符号位
- 用41位来表示收到请求时的时间戳,单位为毫秒
- 5位数表示数据中心的ID
- 再5位数表示机器的实例ID
- 最后是12位的循环自增ID,到达 1111 1111 1111 后会归0
这样的机制可以支持我们在同一台机器上,同一毫秒内产生2^12 = 4096
条消息,一秒则可以生成409.6w条消息,从值域上来讲完全够用。
数据中心加上实例ID共10位,可以支持我们每个数据中心部署32台机器,32*32 = 1024 总共1024台实例。
表示timestamp
的41位,可以支持使用69年,这里的timestamp可以当做基于某个时间的增量,而不是从1970年开始计
2.2 worker_id分配
timestamp
,datacenter_id
,worker_id
,mermaid sequenceDiagram_id
这四个字段中,timestamp
和mermaid sequenceDiagram_id
是由程序在运行期生成的。但是 datacenter_id
,worker_id
需要我们在部署阶段就能够获取得到,并且一旦程序启动之后就无法修改(否则会导致生成的id会有冲突)
一般针对于不同的数据中心的机器,会提供对应的获取数据中心id的API,而worker_id是我们逻辑给机器分配的一个id。
- 使用能够提供这种自增id功能的工具来支持
比如MySQL,但是使用MySQL的同时相当于也产生了依赖,依赖越多,服务的可运维性就越差。 - 直接定义好worker_id,写入到worker的配置中,上线时由部署脚本完成worker_id字段的替换
2.3 开源实例
2.3.1 snowflake
github.com/bwmarrin/snowflake
这是一个轻量化的snowflake的Go实现,使用上比较简单:
package main
import (
"fmt"
"os"
"github.com/bwmarrin/snowflake"
)
func main() {
n, err := snowflake.NewNode(1)
if err != nil {
println(err)
os.Exit(1)
}
for i := 0; i < 3; i++ {
id := n.Generate()
fmt.Println("id", id)
fmt.Println( "node: ", id.Node(), "step: ", id.Step(), "time: ", id.Time(), "\n", )
}
}
这个库也预留了一些可定制的字段:
Epoch int64 = 1288834974657
// Number of bits to use for Node
// Remember, you have a total 22 bits to share between Node/Step
NodeBits uint8 = 10
// Number of bits to use for Step
// Remember, you have a total 22 bits to share between Node/Step
StepBits uint8 = 12
Epoch 就是本节开头讲的起始时间, NodeBits 指的是机器编号的位长, StepBits 指的是自增序列的位长
2.3.2 sonyflake
sonyflake基本思路和snowflake差不多,不过位分配上稍有不同
这里的时间只用了39个bit,但时间的单位变成了10ms,所以理论上比41位表示的时间还要久(2^39*10/1000/86400/365≈174年)
Sequence ID 和之前的定义一致, Machine ID其实就是节点ID(nodeID),sonyflake与众不同的地方在于其启动阶段的配置参数:
func NewSonyflake(st Settings) *Sonyflake
Settings数据结构如下:
type Settings struct {
StartTime time.Time
MachineID func() (uint16, error)
CheckMachineID func(uint16) bool
}
StartTime
选项和snowflake的Epoch差不多,如果不设置的话默认从2014-09-01 00:00:00 +0000 UTC
开始MachineID
可以由用户自定义的函数,如果用户不定义的话,会默认将本机IP的低16位作为MachineIDCheckMachineID
是由用户提供的检查MachineID是否冲突的函数。如果有redis集群,可以很轻松地用set来检查冲突
127.0.0.1:6379> SADD machine_id 10086
(integer) 1
127.0.0.1:6379> SADD machine_id 10086
(integer) 0