雪花算法
雪花算法用一个64bit的数字表示其生成的ID
[0] [0000000 00000000 00000000 00000000 00000000 00] [000000 0000] [0000 00000000]
最高位
弃用,该位表示数字的正负,一般置0。
后续 41 位
表示时间戳。2的41次幂的范围,可以使用69年。按照时间戳生成的ID在整体的趋势上是递增的。
再后续 10 位
表示机器ID,2的10次幂的范围,可以有1024台服务器。
低 12 位
表示序列号。序列号的意义是,同一毫秒内生成的第几个ID。很显然1毫秒内可以生成2的12次幂个序列号。即2的12次幂个ID,即4096个ID。超出则等下一毫秒再生成。
优点:
高效率,不重复
缺点:
严重依赖机器时间
- ->[nacos 服务注册中心] <--
| |----[发号器服务1]<---------.
| | |
[商品服务]-----------/------------------------->\-----[发号器服务2]<--------|----[zookeeper]
| | |
| `--[法号器服务3]<----------'
| |
[订单服务]----------/------------------>------------------
若发生时钟回拨情况的解决方案,该时间根据具体业务场景而定。
回拨时间很短( <= 100ms ),等待相应毫秒
回拨时间适中( > 100ms <= 1s ),将最近1s分成1000ms,将每1毫秒中最大的ID放入hash map,当发生回拨的时候,取出对应毫秒的最大ID,做自增操作,生成新的ID。
回拨时间较长(> 1s <= 5s),切换ID发放服务。
回拨时间很长( > 5s ),让该服务下线,发信息给运维人员,人工维护。
ID发放,在分布式微服务架构中通常需要单独的部署与维护,在ID发放服务少的情况下,可以通过配置文件,指定machineID(机房ID),若机器增多,可使用zookeeper生成唯一的进程ID,实现对machineID的控制。
代码
简单实现
package main
import (
"fmt"
"sync"
"time"
)
type snowFlake struct {
sync.Mutex
lastMilliSecond int64
currentMilSecId uint64
}
func (s *snowFlake) generate(machineID int) uint64 {
s.Lock()
defer s.Unlock()
machineID &= 1<<10 - 1
low12bit := s.currentMilSecId
nowMicro := time.Now().UnixMicro()
if nowMicro/1000 < s.lastMilliSecond {
panic("发生时钟回拨")
}
if last := s.lastMilliSecond; nowMicro/1000 == last { // 同一毫秒
if s.currentMilSecId < 1<<12 {
s.currentMilSecId++
low12bit = s.currentMilSecId
} else {
time.Sleep(time.Microsecond * time.Duration(1+nowMicro-last*1000))
nowMicro = time.Now().UnixMicro()
}
} else {
s.currentMilSecId = 0
}
s.lastMilliSecond = nowMicro / 1000
id := uint64(nowMicro)<<22 | uint64(machineID)<<12 | low12bit
return id &^ (1 << 63) // 高位简单处理一下
}
func main() {
s := snowFlake{}
for i := 0; i < 1000; i++ {
go func() {
fmt.Printf("%b\n", s.generate(3))
}()
}
time.Sleep(time.Second)
}
//10110001 00000011 01100100 10100010 00010100 00000000 01100000 0111100