Golang-snowFlake解析

2,122 阅读2分钟

简介

Snowflake(雪花)是Twitter开源的高性能ID生成算法,它具有以下优缺点:

优点:

  • 毫秒数在高位、自增序列在低位,整个ID都是趋势递增的
  • 不依赖数据库等第三方系统、以服务方式部署、稳定性更高
  • 可以根据自身业务特性分配bit位,较为灵活 缺点:
  • 存在时钟回拨问题
  • workid不易管理等问题

SnowFlake具体实现

image.png

snowflake以64bit来存储组成ID的几个部分,如上图所示:

  • 最高位占1bit,值固定为0,以保证生成的ID为正数(二进制中最高位为符号位,1表示负数,0表示正数)
  • 时间41bit长度,使用毫秒级别精度,带有一个epoch,大概可以使用69年
  • 10bit的可配置机器ID
    • 这里可以拆分成5bit的Worker ID 和5bit Data Center ID(数据中心ID)
  • 末位占12bit,值为当前毫秒内生成的不同ID,值得上限为2^12 = 4096

首先先定义一些常量用于实现snowflake的基础数据:

const (
   twepoch        = int64(1525705533000)             // 开始时间截
   workeridBits   = uint(10)                         // 机器id所占的位数  
   sequenceBits   = uint(12)                         // 序列所占的位数  
   workeridMax    = int64(-1 ^ (-1 << workeridBits)) // 支持的最大机器id数量
   sequenceMask   = int64(-1 ^ (-1 << sequenceBits)) // 毫秒内存列的最大值
   workeridShift  = sequenceBits                     // 机器id左移位数 
   timestampShift = sequenceBits + workeridBits      // 时间戳左移位数 22位
)
// 定义结构体struct
type SnowFlake struct{
    sync.Mutex
    timestamp int64
    workerid  int64
    sequence  int64
}

// 实例化snowflake
func NewSnowFlake(wokerid int64)(*SnowFlake,error){
    // 判定workerid是否在合理范围内
    if workerid < 0 || workerid > workeridMax{
        return nil,errors.New("error happen")
    }
    return &Snowflake{
       timestamp: 0,
       workerid:  workerid,
       sequence:  0,
     }, nil
}

snowflake生成ID的具体实现

func (s *Snowflake) Generate() int64 {
   s.Lock()
   defer s.Unlock()
  
   now := time.Now().UnixNano() / 1000000
   // 如果是微妙时间戳、且处于同一秒
   if s.timestamp == now {
      // 确保sequence不会溢出,最大值为4095、保证1ms内最多生成4096个ID值
      s.sequence = (s.sequence + 1) & sequenceMask
      if s.sequence == 0 {
          // 如果当前时间小于等于当前时间戳
         for now <= s.timestamp {
            // 死循环等待下一个毫秒值
            now = time.Now().UnixNano() / 1000000
         }
      }
   } else {
      s.sequence = 0
   }
   
   // todo: 时钟回拨判断、如果获取到的时间戳比上一个小,则存在时钟回拨状态,需要抛出异常
  
   s.timestamp = now
   // 时间戳左移22位 | workeid左移12位 | sequence兜底
   r := (now-twepoch)<<timestampShift | (s.workerid << workeridShift) | (s.sequence)
   return r
}

位运算

上面代码实现中有一些位运算相关实现。

sequence = (sequence + 1) & sequenceMask  // 防止溢出

ntp导致的时钟回拨问题

服务器时间校准一般是通过ntp进程去校准的。但是由于校准这个动作,会导致时钟跳跃变化现象。

image.png

如何解决时钟回拨问题

  • 关闭NTP服务
  • 当时钟回拨过程直接抛出异常

workid管理复杂

Reference