这是我参与「第三届青训营 -后端场」笔记创作活动的第5篇笔记
1.雪花算法诞生背景
各种业务场景都需要唯一ID生成的需求,特别是在高并发的情况下,在这种背景下,Twitter公司发明了雪花算法,主要目的是解决在分布式环境下,唯一ID生成的问题,得益于twitter内部牛逼的技术,雪花算法能够流传于至今并且被广泛使用。
雪花算法的优点:
- 经测试snowflake每秒能生成26万个自增可排序的ID。
- 分布式系统内不会产生ID碰撞(datacenter和workerId作区分)并且效率高。
- 不依赖数据库等第三方系统,以服务的方式部署,稳定性更高,生成ID的性能也非常高,可以根据自身业务分配bit位,非常灵活。
2.雪花ID的结构
snowflake ID 结构是一个64bit的int型数据。
unused : 不使用,占1bit,为0
timestamp : 时间戳,占41bit
work id : 工作机器id,占10bit,记录工作机器的id,范围为2^10=1024,所以允许分布式最大节点数为1024个节点
sequence : 序列号,占12bit,记录同毫秒内产生的不同id,范围为2^12-1=4095,表示同一机器同一时间戳(毫秒)内产生的4095个ID序号
3.通过go使用雪花算法
3.1实现大致步骤
- 获取当前的毫秒时间戳;
- 用当前的毫秒时间戳和上次保存的时间戳进行比较;
- 如果和上次保存的时间戳相等,那么对序列号 sequence 加一;
- 如果不相等,那么直接设置 sequence 为 0 即可;
- 然后通过或运算拼接雪花算法需要返回的 int64 返回值。
首先我们需要定义一个 Snowflake 结构体:
type Snowflake struct {
sync.Mutex // 锁
timestamp int64 // 时间戳 ,毫秒
workerid int64 // 该节点的ID
datacenterid int64 // 该节点的数据中心ID
sequence int64 // 序列号,1毫秒内最多生成4096个ID
}
再定义一些常数:
const (
epoch = int64(1577808000000) // 设置起始时间(时间戳/毫秒):2020-01-01 00:00:00,有效期69年
timestampBits = uint(41) // 时间戳占用位数
datacenteridBits = uint(2) // 数据中心id所占位数
workeridBits = uint(7) // 机器id所占位数
sequenceBits = uint(12) // 序列所占的位数
timestampMax = int64(-1 ^ (-1 << timestampBits)) // 时间戳最大值
datacenteridMax = int64(-1 ^ (-1 << datacenteridBits)) // 支持的最大数据中心id数量
workeridMax = int64(-1 ^ (-1 << workeridBits)) // 支持的最大机器id数量
sequenceMask = int64(-1 ^ (-1 << sequenceBits)) // 支持的最大序列id数量
workeridShift = sequenceBits // 机器id左移位数
datacenteridShift = sequenceBits + workeridBits // 数据中心id左移位数
timestampShift = sequenceBits + workeridBits + datacenteridBits // 时间戳左移位数
)
生成snowID
func (s *Snowflake) NextVal() int64 {
s.Lock()
now := time.Now().UnixNano() / 1000000 // 转毫秒
if s.timestamp == now {
// 当同一时间戳(精度:毫秒)下多次生成id会增加序列号
s.sequence = (s.sequence + 1) & sequenceMask
if s.sequence == 0 {
// 如果当前序列超出12bit长度,则需要等待下一毫秒
// 下一毫秒将使用sequence:0
for now <= s.timestamp {
now = time.Now().UnixNano() / 1000000
}
}
} else {
// 不同时间戳(精度:毫秒)下直接使用序列号:0
s.sequence = 0
}
t := now - epoch
if t > timestampMax {
s.Unlock()
glog.Errorf("epoch must be between 0 and %d", timestampMax-1)
return 0
}
s.timestamp = now
r := int64((t)<<timestampShift | (s.datacenterid << datacenteridShift) | (s.workerid << workeridShift) | (s.sequence))
s.Unlock()
return r
}
对r系列的位运算解释: 首先t表示的是现在距离epoch的时间差,我们epoch在初始化的时候设置的是2020-01-01 00:00:00,那么对于41bit的 timestamp来说会在69年之后才溢出。对t进行向左位移之后,低于timestampShift 位置上全是0 ,由datacenterid、workerid、sequence进行取或填充。