如何通过 go 语言实现雪花算法?

196 阅读3分钟

在 Go 语言中,可以通过实现雪花算法(Snowflake)来生成分布式唯一ID。雪花算法是 Twitter 提出的一种生成分布式唯一ID的算法,其核心思想是将一个64位的ID划分为多个部分,分别表示时间戳、机器ID和序列号。

以下是雪花算法的 Go 语言实现:


1. 雪花算法结构

雪花算法的64位ID结构如下:

| 1 bit (未使用) | 41 bits (时间戳) | 10 bits (机器ID) | 12 bits (序列号) |
  • 时间戳:41位,表示从某个起始时间到当前时间的毫秒数。
  • 机器ID:10位,用于标识不同的机器或节点。
  • 序列号:12位,用于同一毫秒内生成多个ID。

2. Go 实现代码

以下是雪花算法的 Go 实现:

package main

import (
	"errors"
	"fmt"
	"sync"
	"time"
)

const (
	// 定义各部分的位数
	epoch          int64 = 1609459200000 // 起始时间:2021-01-01 00:00:00 UTC
	timestampBits  uint8 = 41
	machineIDBits  uint8 = 10
	sequenceBits   uint8 = 12

	// 定义最大值
	maxMachineID int64 = -1 ^ (-1 << machineIDBits)
	maxSequence  int64 = -1 ^ (-1 << sequenceBits)

	// 定义位移
	timestampShift = machineIDBits + sequenceBits
	machineIDShift = sequenceBits
)

// Snowflake 结构体
type Snowflake struct {
	mutex      sync.Mutex
	lastStamp  int64
	machineID  int64
	sequence   int64
}

// NewSnowflake 创建一个 Snowflake 实例
func NewSnowflake(machineID int64) (*Snowflake, error) {
	if machineID < 0 || machineID > maxMachineID {
		return nil, errors.New("machine ID out of range")
	}
	return &Snowflake{
		lastStamp: 0,
		machineID: machineID,
		sequence:  0,
	}, nil
}

// NextID 生成下一个唯一ID
func (s *Snowflake) NextID() int64 {
	s.mutex.Lock()
	defer s.mutex.Unlock()

	// 获取当前时间戳
	currentStamp := time.Now().UnixMilli()

	// 如果当前时间戳小于上次生成ID的时间戳,说明时钟回拨
	if currentStamp < s.lastStamp {
		panic("clock moved backwards")
	}

	// 如果是同一毫秒内生成的ID,则递增序列号
	if currentStamp == s.lastStamp {
		s.sequence = (s.sequence + 1) & maxSequence
		// 如果序列号溢出,则等待下一毫秒
		if s.sequence == 0 {
			for currentStamp <= s.lastStamp {
				currentStamp = time.Now().UnixMilli()
			}
		}
	} else {
		// 如果不是同一毫秒,则重置序列号
		s.sequence = 0
	}

	// 更新上次生成ID的时间戳
	s.lastStamp = currentStamp

	// 生成ID
	id := ((currentStamp - epoch) << timestampShift) |
		(s.machineID << machineIDShift) |
		s.sequence

	return id
}

func main() {
	// 创建一个 Snowflake 实例,机器ID为 1
	snowflake, err := NewSnowflake(1)
	if err != nil {
		panic(err)
	}

	// 生成10个唯一ID
	for i := 0; i < 10; i++ {
		id := snowflake.NextID()
		fmt.Println(id)
	}
}

3. 代码说明

  • 起始时间epoch 是算法的起始时间,可以根据需要调整。
  • 机器IDmachineID 用于标识不同的机器或节点,必须在 [0, maxMachineID] 范围内。
  • 序列号sequence 用于同一毫秒内生成多个ID,范围为 [0, maxSequence]
  • 线程安全:使用 sync.Mutex 确保并发安全。
  • 时钟回拨处理:如果当前时间戳小于上次生成ID的时间戳,说明时钟回拨,直接抛出异常。

4. 示例输出

运行上述代码,会生成类似以下的唯一ID:

6928334669447168
6928334669447169
6928334669447170
6928334669447171
6928334669447172
6928334669447173
6928334669447174
6928334669447175
6928334669447176
6928334669447177

5. 优点

  • 高性能:生成ID的速度非常快。
  • 分布式:通过机器ID支持分布式部署。
  • 有序性:ID 按时间递增,适合作为数据库主键。

6. 注意事项

  • 机器ID分配:确保每台机器的 machineID 唯一。
  • 时钟同步:确保各机器的时钟同步,避免时钟回拨问题。
  • ID长度:生成的ID是64位整数,适合大多数场景。