
分布式id生成器
1.分布式ID的特点
- 全局唯一性:不能出现有重复的ID标识,这是基本要求。
- 递增性:确保生成ID对于用户或业务是递增的。
- 高可用性:确保任何时候都能生成正确的ID。
- 高性能性:在高并发的环境下依然表现良好。
不仅仅是用于用户ID,实际互联网中有很多场景需要能够生成类似MySQL自增ID这样不断增大,同时又不会重复的id。以支持业务中的高并发场景。比较典型的,电商促销时,短时间内会有大量的订单涌入到系统,比如每秒10w+。明星出轨时,会有大量热情的粉丝发微博以表心意,同样会在短时间内产生大量的消息。
在插入数据库之前,我们需要给这些消息、订单先打上一个ID,然后再插入到我们的数据库。对这个id的要求是希望其中能带有一些时间信息,这样即使我们后端的系统对消息进行了分库分表,也能够以时间顺序对这些消息进行排序。
2.雪花算法介绍
雪花算法,它是Twitter开源的由64位整数组成分布式ID,性能较高,并且在单机上递增。
- 第一位 占用1bit,其值始终是0,没有实际作用。
- 时间戳 占用41bit,单位为毫秒,总共可以容纳约69年的时间。当然,我们的时间毫秒计数不会真的从1970年开始记,那样我们的系统跑到 2039/9/7 23:47:35 就不能用了,所以这里的时间戳只是相对于某个时间的增量,比如我们的系统上线是2018-08-01,那么我们可以把这个timestamp当作是从2018-08-01 00:00:00.000 的偏移量。
- 工作机器id 占用10bit,其中高位5bit是数据中心ID,低位5bit是工作节点ID,最多可以容纳1024个节点。
- 序列号 占用12bit,用来记录同毫秒内产生的不同id。每个节点每毫秒0开始不断累加,最多可以累加到4095,同一毫秒一共可以产生4096个ID。SnowFlake算法在同一毫秒内最多可以生成多少个全局唯一ID呢?同一毫秒的ID数量 = 1024 X 4096 = 4194304
3.snowflake实现
github.com/bwmarrin/snowflake 是一个相当轻量化的snowflake的Go实现
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()) }sonyflake是Sony公司的一个开源项目,基本思路和snowflake差不多,不过位分配上稍有不同:
这里的时间只用了39个bit,但时间的单位变成了10ms,所以理论上比41位表示的时间还要久(174年)。
Sequence ID 和之前的定义一致, Machine ID 其实就是节点id。 sonyflake 库有以下配置参数:type Settings struct { StartTime time.Time MachineID func() (uint16, error) CheckMachineID func(uint16) bool }其中:
- StartTime 选项和我们之前的 Epoch 差不多,如果不设置的话,默认是从 2014-09-0100:00:00 +0000 UTC 开始。
- MachineID 可以由用户自定义的函数,如果用户不定义的话,会默认将本机IP的低16位作为 machine id 。
- CheckMachineID 是由用户提供的检查 MachineID 是否冲突的函数。
使用示例:
var ( sonyFlake *sonyflake.Sonyflake sonyMachineID uint16 ) func getMachineID() (uint16, error) { return sonyMachineID, nil } // 需传入当前的机器ID func Init(machineId uint16) (err error) { sonyMachineID = machineId t, _ := time.Parse("2006-01-02", "2020-01-01") settings := sonyflake.Settings{ StartTime: t, MachineID: getMachineID, } sonyFlake = sonyflake.NewSonyflake(settings) return } // GetID 返回生成的id值 func GetID() (id uint64, err error) { if sonyFlake == nil { err = fmt.Errorf("snoy flake not inited") return } id, err = sonyFlake.NextID() return }