SnowFlake雪花算法| 青训营

186 阅读2分钟

1 背景

​ 常见的分布式系统生成唯一ID的方式由很多,主要有:

  • 使用数据库,使用数据库的自增列
  • 使用UUID,实现简单,但是没有排序,查询效率低
  • 使用Redis自增原子性生成,但是要引入第三方组件

​ 本文介绍一个分布式系统中高效生成唯一ID的一种方案——雪花算法

2 雪花算法

2.1概述

​ SnowFlake雪花算法,最早是Twitter公司再其内部用于分布式环境下生成唯一ID。再2014年开源scala语言版本。

2.2 ID组成

​ 雪花算法的原理是生成一个64位比特位的long类型的唯一ID

image.png

​ 由符号位、时间戳、机器ID、服务ID、序号组成:

  • 固定值:占一位,固定为0,生成的ID为正数,为负数就是1
  • 时间戳:占41位,以当前时间位起始值,可以使用2^41ms,大概可以使用49年
  • 机器ID和服务ID:各占5位,可以部署2^10个节点
  • 序号:占12位,同一毫秒,统一机器通过序号区分,可以生成2^12个不同ID

3 代码实现

本代码由golang实现:

package main

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

const (
	// 当前时间 2023-07-29 14:59:31  毫秒级别
	epoch int64 = 1690613971279
	// 序列号位数
	numberBit int8 = 12
	// 服务ID位数
	serveIdBit int8 = 5
	// 机器ID位数
	machineIdBit int8 = 5
	// 服务ID的偏移量
	serveIdShift int8 = numberBit
	// 机器ID的偏移量
	machineIdShift int8 = numberBit + serveIdBit
	// 时间戳的偏移量
	timestampShift int8 = numberBit + serveIdBit + machineIdBit
	// 服务ID的最大值 31
	serverIdMax int64 = -1 ^ (-1 << serveIdBit)
	// 机器ID的最大值 31
	machineIdMax int64 = -1 ^ (-1 << machineIdBit)
	// 序列号的最大值 4095
	numberMax int64 = -1 ^ (-1 << numberBit)
)

type SnowFlake struct {
	// 每次生产一个id都是一个原子操作
	lock sync.Mutex
	// 时间戳、机器ID、服务ID、序列号
	timestamp int64
	machineId int64
	serveId   int64
	number    int64
}

// NewSnowFlake 构造函数,传入机器ID和服务ID
func NewSnowFlake(machineId int64, serveId int64) (*SnowFlake, error) {
	if machineId < 0 || machineId > machineIdMax {
		return nil, errors.New("mechineId超出限制")
	}
	if serveId < 0 || serveId > serverIdMax {
		return nil, errors.New("serveId超出限制")
	}
	return &SnowFlake{
		timestamp: 0,
		machineId: machineId,
		serveId:   serveId,
		number:    0,
	}, nil
}

func (snow *SnowFlake) NextId() int64 {
	// 原子操作
	snow.lock.Lock()
	defer snow.lock.Unlock()

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

	// 如果时间戳还是当前时间错,则序列号增加
	if now == snow.timestamp {
		snow.number++
		// 如果超过了序列号的最大值,则更新时间戳
		if snow.number > numberMax {
			for now <= snow.timestamp {
				now = time.Now().UnixMilli()
			}
		}
	} else {
		snow.number = 0
		snow.timestamp = now
	}

	// 拼接最后的结果,将不同不服的数值移到指定位置
	id := (snow.timestamp-epoch)<<timestampShift | (snow.machineId << machineIdShift) |
		(snow.serveId << serveIdShift) | snow.number

	return id
}

测试:

package main

import (
	"fmt"
	"testing"
)

func TestSnowFlake_NextId(t *testing.T) {
	snow, _ := NewSnowFlake(10, 10)
	for i := 0; i < 10; i++ {
		fmt.Println(snow.NextId())
	}
}

4 总结

优点:

  • 不依赖第三方库或者中间件
  • 算法简单,性能高,在内存中进行
  • 容量大,每秒钟能生成百万ID
  • 雪花算法比特位不是固定死的,可以根据业务动态的改变各部分的占位

缺点:

  • 雪花算法在单机上是递增的,但是分布式情况下,可能因为节点时钟不同而不保证全局递增