推特雪花算法
标准格式如下
id 是64位整型的
第一个bit未使用默认为0
41bit 存储毫秒级时间戳,时间单位为毫秒,最大可使用的年限是69年。
10bit 存储节点的id 10个bit表示最多可以扩展1024个节点。
12bit 自增id ,表示一个时间单位内(1毫秒),一个节点可生成的id最多为4096个。
对于这样一个结构,一个机器,在1秒内 最多可以生成4096*1000约 400万个id。
使用
package main
import (
"fmt"
"github.com/bwmarrin/snowflake"
)
func main() {
node, err := snowflake.NewNode(0)
if err != nil {
fmt.Println(err)
return
}
id := node.Generate()
fmt.Printf("Int64 ID: %d\n", id) // 1607646006491480064
fmt.Printf("String ID: %s\n", id) // 1607646006491480064
fmt.Printf("Base2 ID: %s\n", id.Base2()) // 1011001001111100000011000011001110101100000000000000000000000
fmt.Printf("Base64 ID: %s\n", id.Base64()) // MTYwNzY0NjAwNjQ5MTQ4MDA2NA==
}
源码阅读
(github.com/bwmarrin/snowflake)
数据结构定义
var (
Epoch int64 = 1288834974657
NodeBits uint8 = 10
StepBits uint8 = 12
)
// Node 结构定义
type Node struct {
mu sync.Mutex
epoch time.Time
time int64
node int64
step int64
nodeMax int64
nodeMask int64
stepMask int64
timeShift uint8
nodeShift uint8
}
type ID int64
生成工作节点
// NewNode 返回可用于生成雪花ID的新雪花节点
func NewNode(node int64) (*Node, error) {
// node值为节点id值,唯一
// re-calc in case custom NodeBits or StepBits were set
// DEPRECATED: the below block will be removed in a future release.
mu.Lock()
nodeMax = -1 ^ (-1 << NodeBits)
nodeMask = nodeMax << StepBits
stepMask = -1 ^ (-1 << StepBits)
timeShift = NodeBits + StepBits
nodeShift = StepBits
mu.Unlock()
n := Node{}
n.node = node
n.nodeMax = -1 ^ (-1 << NodeBits) //节点id最大值,NodeBits=10时,nodeMax=1023
n.nodeMask = n.nodeMax << StepBits //节点id掩码,nodeMax=1023时,nodeMask=4190208
n.stepMask = -1 ^ (-1 << StepBits) //自增id掩码,StepBits=12时,stepMask=4095
n.timeShift = NodeBits + StepBits //时间戳左移位数,10+12
n.nodeShift = StepBits //节点id左移位数,12
if n.node < 0 || n.node > n.nodeMax {
return nil, errors.New("Node number must be between 0 and " + strconv.FormatInt(n.nodeMax, 10))
}
var curTime = time.Now()
// 当前时间到默认开始时间Epoch的差值
// add time.Duration to curTime to make sure we use the monotonic clock if available
n.epoch = curTime.Add(time.Unix(Epoch/1000, (Epoch%1000)*1000000).Sub(curTime))
return &n, nil
}
生成唯一id
// Generate 创建并返回唯一的雪花ID以帮助确保唯一性
//- 确保您的系统保持准确的系统时间
//- 确保从未有多个节点使用相同的节点ID运行
func (n *Node) Generate() ID {
n.mu.Lock()
// 当前时间 与 默认起始时间 的差值(毫秒)(单调增)
now := time.Since(n.epoch).Nanoseconds() / 1000000
// 如果在同一时间,就对自增id+1
if now == n.time {
n.step = (n.step + 1) & n.stepMask
// 当step达到最大值,在加1,就为0,即表示当前时间,不能在生成跟多id了,需等到下一个时间点
if n.step == 0 {
for now <= n.time {
now = time.Since(n.epoch).Nanoseconds() / 1000000
}
}
} else { // 不在同一时间,自增id设为0,
n.step = 0
}
// 时间更新为now
n.time = now
// id 通过位操作,由三部分组成
r := ID((now)<<n.timeShift |
(n.node << n.nodeShift) |
(n.step),
)
n.mu.Unlock()
return r
}
原生的Snowflake算法是完全依赖于时间的,如果有时钟回拨的情况发生,会生成重复的ID,市场上的解决方案也是非常多的:
1.如果发现有时钟回拨,时间很短比如5毫秒,就等待,然后再生成。或者就直接报错,交给业务层去处理。
2.可以找2bit位作为时钟回拨位,发现有时钟回拨就将回拨位加1,达到最大位后再从0开始进行循环。
简单实现
package main
import (
"errors"
"fmt"
"sync"
"time"
)
const (
workerBits uint8 = 10
numberBits uint8 = 12
workerMax int64 = -1 ^ (-1 << workerBits)
numberMax int64 = -1 ^ (-1 << numberBits)
timeShift uint8 = workerBits + numberBits // 时间戳偏移量
workerShift uint8 = numberBits // 机器码偏移量
epoch int64 = 1656756144640 // 起始时间戳(毫秒)2022-07-03 21:49:04
)
type Worker struct {
mu sync.Mutex
timeStamp int64
workerId int64
number int64
}
func NewWorker(workerId int64) (*Worker, error) {
if workerId < 0 || workerId > workerMax {
return nil, errors.New("workerId > max")
}
return &Worker{
timeStamp: 0,
workerId: workerId,
number: 0,
}, nil
}
func (w *Worker)NextId() int64 {
w.mu.Lock()
defer w.mu.Unlock()
now := time.Now().UnixNano() / 1e6
if w.timeStamp == now {
w.number++
if w.number > numberMax {
for now <= w.timeStamp {
now = time.Now().UnixNano() / 1e6
}
}
} else {
w.number = 0
w.timeStamp = now
}
// 时间戳、机器编码、序列号 组成
Id := ((now - epoch) << timeShift) | (w.workerId << workerShift) | (w.number)
return Id
}
func main() {
worker, err := NewWorker(0)
if err != nil {
fmt.Println(err)
return
}
for i := 0; i < 5; i++ {
id := worker.NextId()
fmt.Println(id)
}
}
索尼雪花算法
Snowflake算法是相当灵活的,我们可以根据自己的业务需要,对63 bit的的各个部分进行增减。索尼公司的Sonyflake对原生的Snowflake进行改进,重新分配了各部分的bit位:
索尼雪花算法标准格式如下
id 是64位整型的
第一个bit未使用默认为0
39bit 存储10毫秒级时间戳,时间单位为10毫秒,可以使用的年限为 174年 比Snowflake长太多了
8bit 存储自增id,即一个时间单位内(10毫),一个节点可生成的id最多为256个,比原生的Snowflake少好多,如果感觉不够用,目前的解决方案是跑多个实例生成同一业务的ID来弥补。
16bit 机器(或者线程)id,最多有2^16个机器或者线程,做为机器号,默认的是当前机器的私有IP的最后两位
使用
func getMachineID() (uint16, error) {
return uint16(0), nil
}
func checkMachineID(machineID uint16) bool {
return true
}
func main() {
t := time.Unix(0,0) //1970-01-01 08:00:00 +0800 CST
settings := sonyflake.Settings{
StartTime: t,
MachineID: getMachineID,
CheckMachineID: checkMachineID,
}
sf := sonyflake.NewSonyflake(settings)
id, _ := sf.NextID()
fmt.Println(id) // 2805386550832005120
return
}
源码阅读
(github.com/sony/sonyflake)
结构定义
const (
BitLenTime = 39 // bit length of time
BitLenSequence = 8 // bit length of sequence number
BitLenMachineID = 63 - BitLenTime - BitLenSequence // bit length of machine id
)
type Settings struct {
StartTime time.Time
MachineID func() (uint16, error)
CheckMachineID func(uint16) bool
}
// Sonyflake is a distributed unique ID generator.
type Sonyflake struct {
mutex *sync.Mutex
startTime int64
elapsedTime int64 // 上次生成id的时间
sequence uint16 // 自增id
machineID uint16 // 机器id
}
生成工作节点
// NewSonyflake 返回使用给定设置配置的新Sonyflak。
//在以下情况下,NewSonyflake返回零:
//-Settings.StartTime早于当前时间。
//-Settings.MachineID返回错误。
//-Settings.CheckMachineID返回false。
func NewSonyflake(st Settings) *Sonyflake {
sf := new(Sonyflake)
sf.mutex = new(sync.Mutex)
sf.sequence = uint16(1<<BitLenSequence - 1)
if st.StartTime.After(time.Now()) { // startTime在当前时间之后,返回空值
return nil
}
if st.StartTime.IsZero() {
sf.startTime = toSonyflakeTime(time.Date(2014, 9, 1, 0, 0, 0, 0, time.UTC))
} else {
sf.startTime = toSonyflakeTime(st.StartTime)
}
var err error
if st.MachineID == nil {
sf.machineID, err = lower16BitPrivateIP()
} else {
sf.machineID, err = st.MachineID()
}
if err != nil || (st.CheckMachineID != nil && !st.CheckMachineID(sf.machineID)) {
return nil
}
return sf
}
机器id通过私有ip生成,保证唯一性,尽量保证不同服务单独部署
func lower16BitPrivateIP() (uint16, error) {
ip, err := privateIPv4()
if err != nil {
return 0, err
}
return uint16(ip[2])<<8 + uint16(ip[3]), nil
}
func privateIPv4() (net.IP, error) {
as, err := net.InterfaceAddrs()
if err != nil {
return nil, err
}
for _, a := range as {
ipnet, ok := a.(*net.IPNet)
if !ok || ipnet.IP.IsLoopback() {
continue
}
ip := ipnet.IP.To4()
if isPrivateIPv4(ip) {
return ip, nil
}
}
return nil, errors.New("no private ip address")
}
func isPrivateIPv4(ip net.IP) bool {
return ip != nil &&
(ip[0] == 10 || ip[0] == 172 && (ip[1] >= 16 && ip[1] < 32) || ip[0] == 192 && ip[1] == 168)
生成唯一id
// NextID 生成下一个唯一ID。
//Sonyflake时间溢出后,NextID返回错误。
func (sf *Sonyflake) NextID() (uint64, error) {
const maskSequence = uint16(1<<BitLenSequence - 1) // 2^8-1=255
sf.mutex.Lock()
defer sf.mutex.Unlock()
// 从起始时间到现在经过的时间
current := currentElapsedTime(sf.startTime)
if sf.elapsedTime < current { // 比上次生成id时间大,id设置为0
sf.elapsedTime = current
sf.sequence = 0
} else { // sf.elapsedTime >= current
// 如果相等,则还在同一时间单位内,则id+1
// 如果大于,则说明发生了时间回拨
sf.sequence = (sf.sequence + 1) & maskSequence
if sf.sequence == 0 { // 只有等于0时(即自增id达到了最大值)需要关注时间回拨,不等于0时,nextid的生成还是使用sf.elapsedTime,不关current的事
sf.elapsedTime++
overtime := sf.elapsedTime - current
time.Sleep(sleepTime((overtime)))
}
}
return sf.toID()
}
func (sf *Sonyflake) toID() (uint64, error) {
if sf.elapsedTime >= 1<<BitLenTime {
return 0, errors.New("over the time limit")
}
// 使用的是elapsedTime 而不是current
return uint64(sf.elapsedTime)<<(BitLenSequence+BitLenMachineID) |
uint64(sf.sequence)<<BitLenMachineID |
uint64(sf.machineID), nil
}
关于时间回拨问题
只有当current大于elapsedTime,才会将current赋值给elapsedTime,也就是说elapsedTime是一直增大的,即使时钟回拨,也不会改变elapsedTime。
如果没有发生时间回拨,就是sf.elapsedTime = current,自增id满了以后,这个单位时间内不能再生成id了,就需要睡眠一下,等到下一个时间单位。
当发生时间回拨,sequence自增加1。当sequence加满,重新变为0后,为了防止重复id,将elapsedTime+1,这个时候elapsedTime还大于current,睡眠一会儿。
这个对处理时间回拨就是简单粗暴的睡眠等待。