snowflake雪花算法
雪花算法是由Twitter开源的由64位整数组成分布式ID,性能较高,并且在单机上递增。
为什么叫分布式ID生成器?
因为10bit-工作机器id。
雪花算法(Snowflake Algorithm)之所以适用于分布式系统,并能够被称为分布式ID生成算法,主要是因为它考虑到了分布式系统中不同节点独立生成ID的需求,通过结构化的ID格式和算法设计,确保了每个节点生成的ID既唯一又有序。
了解了原理,接下来看看实现:
package snowflake
import (
"time"
//这里导包,然后给这个包取个别名,这样的好处是方便调用。
sf "github.com/bwmarrin/snowflake"
)
//定义一个sf.Node类型的全局变量,这个在雪花算法中代表一个结点或服务实例。
//每个结点有唯一的machineID
var node *sf.Node
//初始化函数,用于初始化雪花算法的节点。
//startTime字符串表示雪花算法的起始时间,这个时间是算法生成ID时参考的基点
//machineID参数是节点或服务实例的唯一标识
func Init(startTime string, machineID int64) (err error) {
首先使用time.Parse解析startTime字符串位time.Time对象 st
var st time.Time
st, err = time.Parse("2006-01-02", startTime)
if err != nil {
return
}
//设置雪花算法的起始时间Epoch time。
//雪花算法中,时间戳部分通常是以算法定义的起始时间为基准,然后计算当前时间与起始时间的差值,这样做可以在64位整数中,有效的利用起时间戳部分,从而在很长的时间生成一个很长的id。
//sf.Epoch在雪花算法库中就是用来定义起始时间的变量。Epoch time是雪花算法生成ID时参考的基准时间点。
//st.UnixNano()这个方法返回st时间对应的Unix纳秒时间戳,这个时间戳就是以纳秒为单位。
// 除1000000是因为雪花算法中通常使用的是毫秒级的时间戳,所以这里是纳秒转毫秒。
//所以总的来说这里是自定义的起始时间st转换为毫秒级的Unix时间戳,并将其设置为雪花算法额起始时间。经过这样的配置之后,后续生成id的时间戳部分就是基于这个自定义起始时间来计算的。
sf.Epoch = st.UnixNano() / 1000000
//创建一个新的雪花算法节点。machineID用于确保每个节点生成的id是唯一的
node, err = sf.NewNode(machineID)
return
}
//这个函数就是调用了全局变量node的Generate方法来生成一个新的ID。
//但是这个函数生成的ID是sf.ID类型,所以这里要进行一个强制类型转换成int64类型的ID。
//这样得到的ID是唯一的,并且随时间自增。
func GenID() int64 {
return node.Generate().Int64()
}
func Parse(layout, value string) (Time, error)
这个我要解读一下,这个是go标准库的函数,用于解析时间字符串的函数,它的作用是将一个指定格式的字符串解析位time.Time类型的时间对象。
layout 参数制定了输入的时间字符串应该匹配的格式,go语言中使用一种特殊的表示方法来定义时间格式:“2006-01-02 15:04:05”。这个字符串代表的是Go的诞生时间(2006年1月2日15时4分5秒),你可以通过改变这个字符串中的日期和时间部分来定义自己的时间格式。例如,仅使用日期部分"2006-01-02"表示年-月-日格式。
value参数是实际要解析的时间字符串,它应该与layout参数指定的格式相匹配。
所以说这个函数的作用就是把starttime解析成time.Time格式。
再来看看生成ID的源码:
func (n *Node) Generate() ID {
//加锁,这就是保证生成ID的过程线程安全
n.mu.Lock()
//计算当前时间戳与起始时间之前的差值,单位是纳秒,然后再除1000000转毫秒级
now := time.Since(n.epoch).Nanoseconds() / 1000000
//处理序列号
//如果当前的毫秒时间戳now与上一次生成ID时的时间戳n.time相同,则序列号+1,保证再统一毫秒内生成的ID是唯一的。序列号通过n.stepMask进行位与操作,确保序列号的范围有效如果序列号用尽(即n.step变为0),则通过循环等待直到下一毫秒。
if now == n.time {
n.step = (n.step + 1) & n.stepMask
//这里就是如果序列号用尽即n.step==0,就循环等待直到下一毫秒。
if n.step == 0 {
for now <= n.time {
//到了下一毫秒这里再更新时间戳
now = time.Since(n.epoch).Nanoseconds() / 1000000
}
}
} else {
//每次判断过后序列号都要从置为0
n.step = 0
}
//更新时间戳
n.time = now
//这里是根据雪花算法的位数规则,进行唯一,将时间戳,结点id和序列号组成一个64位的ID,时间戳部分通过左移n.timeShift位,结点id通过左移n.nodeShift位,序列号直接加在最低位。
r := ID((now)<<n.timeShift |
(n.node << n.nodeShift) |
(n.step),
)
//解锁并返回ID。
n.mu.Unlock()
return r
}