这是我参与「第三届青训营 -后端场」笔记创作活动的第6篇笔记
在搜索引擎项目的开发过程中,我们遇到了为每条记录生成一个全局唯一ID的问题。这个问题在分布式场景中是一个十分常见的问题,经过调研,总结出常用的唯一ID生成的算法有以下几种:
常见的全局唯一ID生成算法
UUID(Universisally Unique Identifier,通用唯一识别码)
根据实现原理可以大致分为基于时间戳或时钟序列生成、基于名字散列值(MD5/SHA1)生成、基于随机数生成三种类型。 标准格式:32个十六进制数组成的分为5个部分的字符串,各部分数字个数为:8-4-4-4-12 优点有: - 不依赖网络,本地生成 - 速度快(支持高并发) - 有现成的库可供调用 缺点有: - 利用字符串类型存储,数据库查询效率低 -无序
数据库自增ID
顾名思义,数据库每增加一条记录,ID加1
优点:简单、有序 缺点:并发一致性问题,已删除的ID无法复用,数据库故障后无法使用 为了解决并发问题:可以对数据库水平拆分,分别设置初始值和步长。这样为每个数据库产生ID时就不需要同步其他数据库当前的ID了。另外也可以采用预先批量分配ID的方式为每台机器分配一个可以使用的ID区间。
雪花算法
产生以下格式的一个64bit有符号整数,对于同一机器、同一时刻、同一并发序列是唯一的。
| 1bit | 41bit | 10bit | 12bit |
|---|---|---|---|
| 符号位(未使用) | 时间戳(最长为69年) | 机器ID | 序列号(区分同一ms内产生的ID) |
在实际应用中也可以根据机器数量、使用年限对上述结构进行适合自身业务的变化。例如百度的UidGenerator和美团的Leaf等都是基于雪花算法结合自身业务需求的产品。 优点:支持高并发,极限QPS可达400w/s
缺点:依赖时钟,如果时钟回拨可能会导致ID重复
雪花算法实践
下面基于雪花算法的原理编码实现一个64位全局唯一ID生成器,使用时先用Init函数产生全局静态变量s并初始化,之后调用静态GetID方法生成ID
type Snowflake struct {
sync.Mutex // 互斥锁
tstamp int64 // 时间戳/ms
wid int64 // 机器编号
sequence int64 // 序列号
}
var s *Snowflake
func Init() {
s = new(Snowflake)
}
func GetID() int64 {
s.Lock()
//得到当前ms时间戳
now := time.Now().UnixNano() / 1000000
//如果当前ms时间戳等于上一个时间戳则序列号加1
//如果序列号用尽(等于零),则等下一个ms
if s.tstamp == now {
s.sequence = (s.sequence + 1) & 0b111111111111
if s.sequence == 0 {
for now <= s.tstamp {
now = time.Now().UnixNano() / 1000000
}
}
} else {
s.sequence = 0
}
//如果当前时间减去时间原点(2020-01-01 00:00:00)超过最大限制(69年)返回0
t := now - int64(1577808000000)
if t > int64(-1 ^ (-1 << 41)) {
s.Unlock()
return 0
}
s.tstamp = now
//生成ID
r := int64((t<<22) | (s.wid<<12) | (s.sequence))
s.Unlock()
return r
}
参考文章
cloud.tencent.com/developer/a… 唯一ID生成算法剖析,看看这篇就够了 www.zhangshengrong.com/p/zD1yD94eX… Go语言实现Snowflake雪花算法