持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第1天,点击查看活动详情
需求背景
在用户注册的时候,需要给用户分配一个 userID ,该 userID 有以下要求:
- 全局唯一,不同用户的userID一定不同。
- userID 最好的递增,数据库在插入记录时可以保证性能。
一、数据库自增ID
什么是自增ID
自增ID是在设计数据库表的时候将主键设置为自增,当数据库每插入一条数据时,主键的值就自动根据前一天记录的值+1进行填充。
自增ID的好处
自增ID是由数据库层面控制能够保证原子性,插入记录效率高,业务方无需关注具体的实现细节,只需要把相对应的记录往数据库中插入,数据库会自动填充主键的自增ID,业务方可以通过该记录反查得到自增ID的值。
自增ID的坏处
自增ID在分布式数据库时,不能用于数据合并,主键是一个唯一值,当合并时会冲突,解决办法很简单,比如现在有两个数据库,只需要让数据库1的主键是单数递增即1,3,5,7,9...;数据库2偶数递增即2,4,6,8...,在后续合并时主键就不会冲突了。
为什么自增ID不适用于userID:容易泄露系统用户量,而且自增ID也不是一个随机值
二、UUID
什么是UUID
UUID 是通用唯一识别码(Universally Unique Identifier)的缩写,是一种软件建构的标准。UUID 的目的是让所有的分布式系统中所生成的UUID都不一样,这样UUID就具有了全局唯一性。UUID是一个128比特的数值,这个数值可以通过一定的算法计算出来,标准的UUID格式为:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx (8-4-4-4-12)
UUID的好处
实现简单,有现成的包可以直接调API生成,并且保证全局唯一。
UUID的缺点
生成无序,在数据库插入记录时性能差,
三、雪花算法
什么是雪花算法
雪花算法是Twitter公司发明的一种算法,主要的目的是解决在系统的分布式环境中,ID无重复生成的问题,主要是由64bit的long型生成的全局ID,引入了时间戳和ID保持自增的属性。
主要分为 5 个部分:
是 1 个 bit:默认是0, 0代表正数,1代表负数 是 41 个 bit:表示的是时间戳。 是 10 个 bit:表示的是5位数据中心id和5位机器id,0000000000, 是 12 个 bit:表示的序号,就是某个机房某台机器上这一毫秒内同时生成的 id 的序号,0000 0000 0000。
雪花算法的优点
雪花算法生成是全局唯一的,分布式系统内不会产生ID碰撞,生成效率高,经测试snowflake每秒能生成26万个自增可排序的ID。并且雪花ID是递增的,在数据库插入时
雪花算法的缺点
雪花算法在时间回溯时可能会生成重复的ID,所以在分布式环境中,需要注意数据中心ID和机器ID不能重复。
四、Golang雪花算法的实现
import (
"sync"
"sync/atomic"
"time"
)
type SnowFlake struct {
fixedTimestamp int64 // 填充时间戳
lastTimestamp int64 // 上次生成的时间戳
sequenceNumber int64 // 序列号
machineID int64 // 数据中心+机器号组成 共十位
mutex sync.Mutex
}
// NewSnowFlake machineID
func NewSnowFlake(machineID int64) *SnowFlake {
return &SnowFlake{
fixedTimestamp: int64(1653374552582),
lastTimestamp: int64(-1),
sequenceNumber: int64(0),
machineID: machineID,
}
}
func (s *SnowFlake) GenSnowID() int64 {
s.mutex.Lock()
defer s.mutex.Unlock()
currentTimestamp := time.Now().UnixMilli()
if currentTimestamp == atomic.LoadInt64(&s.lastTimestamp) {
atomic.AddInt64(&s.sequenceNumber, 1)
if atomic.LoadInt64(&s.sequenceNumber) > 4096 {
for currentTimestamp <= atomic.LoadInt64(&s.lastTimestamp) {
currentTimestamp = time.Now().UnixMilli()
}
atomic.StoreInt64(&s.sequenceNumber, 0)
}
} else {
atomic.StoreInt64(&s.sequenceNumber, 0)
}
atomic.StoreInt64(&s.lastTimestamp, currentTimestamp)
return (currentTimestamp-s.fixedTimestamp)<<22 | int64(s.machineID<<12) | s.sequenceNumber
}
五、测试效果
import (
"fmt"
"testing"
)
func TestGenID(t *testing.T) {
m := make(map[int64]int32, 1000)
flake := NewSnowFlake(1024416752)
for i := 0; i < 1000; i++ {
//time.Sleep(1 * time.Second)
id := flake.GenSnowID()
if _, ok := m[id]; ok {
fmt.Println("重复了id:", id)
} else {
fmt.Println("初次id:", id)
}
m[id] = 1
}
fmt.Println("--------------------------")
fmt.Println(len(m))
}
结果: