网上大多数文章都只是说了snowflake或sonyflake的原理,但好像都没有多少是对源码的解读,对于初学者来说还需要多一些从别的角度去学习这个算法的思想,所以写了这篇文章,尝试从源码的角度去理解这个算法
sonyflake官方说了是受Twitter的Snowflake启发而衍生的分布式唯一ID生成算法。
目录结构
既然我们是从源码的角度去学习,那么就要先了解清楚这个包的目录结构是如何的。
能看得出来,整个包的文件数都是非常少的,非常精简
├── awsutil
│ └── awsutil.go
├── example
│ ├── Dockerfile
│ ├── Dockerrun.aws.json
│ ├── linux64_build.sh
│ ├── README.md
│ └── sonyflake_server.go
├── go.mod
├── go.sum
├── LICENSE
├── README.md
├── sonyflake.go
└── sonyflake_test.go
awsutil:
awsutil包提供了AmazonEC2MachineID函数,它返回AmazonEC2实例的低16位私有IP地址。它还可以通过检索实例元数据在Docker上正确工作。
AWS VPC分配一个CIDR,子网掩码在/28 ~ /16之间。因此,如果每个EC2实例在AWS VPC中都有一个唯一的私有IP地址,那么地址的低16位也是唯一的。在这种情况下,您可以使用AmazonEC2MachineID作为Settings.MachineID。example:在AWS Elastic Beanstalk上运行Sonyflake的示例
sonyflake.go:算法主体
sonyflake_test.go:测试用例
介绍
官方README
上也说了ID的组成,ID是一个64位的数字
+-----------------------------------------------------------------------------+
| 标识位 | 39位保存时间戳 | 8位序列号 | 16位保存机器号 |
+-----------------------------------------------------------------------------+
| 1 Bit Unused | 39 Bit Timestamp | 8 Bit Sequence ID | 16 Bit Machine ID |
+-----------------------------------------------------------------------------+
sonyflake.go
整个算法不足200行,不会很长,所以只要肯付出一些时间,基本上都能看懂,不会出现因代码太多,太枯燥而出现中途放弃的情况
package sonyflake
import (
"errors"
"net"
"sync"
"time"
)
// 这些常量是Sonyflake ID组成部分的位长度
const (
BitLenTime = 39 // 时间戳的位长度
BitLenSequence = 8 // 序列号的位长度
BitLenMachineID = 63 - BitLenTime - BitLenSequence // 机器号的位长度
)
// Sonyflake的配置组成:
//
// StartTime 是Sonyflake ID进行生成的起始时间。
// 如果 StartTime 没有被配置或配置为0的话, 那么StartTime就会被设置为"2014-09-01 00:00:00 +0000 UTC".
// 如果配置的 StartTime 是超过当前的真实时间, Sonyflake对象是不会被创建的,那么就意味着会返回nil,所以上层不判断的话,直接调用 NextID() 就会发生panic,这个在golang里是一定要注意的.
//
// MachineID 返回实例机器的id.
// 如果 MachineID 返回的是error, Sonyflake对象是不会被创建的.
// 如果 MachineID 被配置为nil的话, 将使用默认的 MachineID.
// 默认的 MachineID 返回私有IP地址的低16位.
//
// CheckMachineID 校验实例机器id是否唯一.
// 如果 CheckMachineID 返回的是false, Sonyflake对象是不会被创建的.
// 如果 CheckMachineID 被配置为nil的话, 那么就不会去校验实例机器id是否唯一.
type Settings struct {
StartTime time.Time
MachineID func() (uint16, error)
CheckMachineID func(uint16) bool
}
// Sonyflake 类.
type Sonyflake struct {
mutex *sync.Mutex // 线程安全,主要是在多协程的时候通过锁去保证id唯一性
startTime int64 // 算法起始时间的时间戳,以10ms为单位时间
elapsedTime int64 // 自设置的startTime以来,总共经过了多少个单位时间
sequence uint16 // 单位时间内的序列号
machineID uint16 // 实例机器id号
}
// NewSonyflake 创建对象.
// NewSonyflake 在以下情况将会返回nil:
// - Settings.StartTime 超过当前的真实时间.
// - Settings.MachineID() 返回error.
// - 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() {
// 如果 StartTime 没有被配置或配置为0的话, 那么StartTime就会被设置为"2014-09-01 00:00:00 +0000 UTC"
sf.startTime = toSonyflakeTime(time.Date(2014, 9, 1, 0, 0, 0, 0, time.UTC))
} else {
// 如果配置的StartTime不为零且小于等于当前真实时间的话,StartTime就会以10ms为单位时间去转换
sf.startTime = toSonyflakeTime(st.StartTime)
}
var err error
if st.MachineID == nil {
// 如果不使用自定义的获取实例机器id号的函数的话,那么默认machineID就是私有IP地址的低16位
sf.machineID, err = lower16BitPrivateIP()
} else {
sf.machineID, err = st.MachineID()
}
if err != nil || (st.CheckMachineID != nil && !st.CheckMachineID(sf.machineID)) {
return nil
}
return sf
}
// NextID() 生成ID.
// 如果超过了能保存的时间戳上限, NextID() 将会返回error.
func (sf *Sonyflake) NextID() (uint64, error) {
const maskSequence = uint16