雪花算法

533 阅读3分钟

推特雪花算法

标准格式如下

id 是64位整型的

image.png

第一个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开始进行循环。

image.png

简单实现

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位整型的

image.png

第一个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,睡眠一会儿。

这个对处理时间回拨就是简单粗暴的睡眠等待。