在分布式系统中生成唯一ID的方案有很多,常见的方式有以下几种。
| 方式 | 优点 | 缺点 |
|---|---|---|
| 依赖数据库,使用如MySQL自增列 | 2、实现简单 | 1、容易被第三方通过自增ID爬取到业务增长信息,影响数据库隐私。 2、auto_increment 锁机制会造成自增锁的抢夺,存在一定的性能影响。 3、在分库分表时,数据迁移合并比较麻烦,因为不同的数据库自增列的值可能相同。 |
| UUID | 1、实现简单 | 1、作为乱序序列,会严重影响到innodb新行的写入性能。 2、采用无意义字符串,没有排序。 3、使用字符串形式存储,数据量大时查询效率比较低。 |
| Redis自增原子性生成 | 1、性能较好 2、数据有序且无冲突 | 如果系统中没有Redis,还需要引入新的组件,增加系统复杂度。 |
雪花算法生成的ID是纯数字且有序的,最早是 Twitter 公司在其内部用于分布式环境下生成唯一 ID,其原始版本是scala版。

由首位无效符、时间戳差值,机器编码(由机器id和服务id组成),序号四部分组成。
注意,默认的雪花算法是 64 bit,具体的长度可以自行配置。
如果希望运行更久,增加时间戳的位数;如果需要支持更多节点部署,增加标识位长度;如果并发很高,增加序列号位数。
雪花算法并非一成不变的,可以根据具体场景进行定制。
优点:
缺点:
实现代码由golang编写
代码:
const (
workerBits uint8 = 10 //机器码位数
numberBits uint8 = 12 //序列号位数
workerMax int64 = -1 ^ (-1 << workerBits) //机器码最大值(即1023)
numberMax int64 = -1 ^ (-1 << numberBits) //序列号最大值(即4095)
timeShift uint8 = workerBits + numberBits //时间戳偏移量
workerShift uint8 = numberBits //机器码偏移量
epoch int64 = 1656856144640 //起始常量时间戳(毫秒),此处选取的时间是2022-07-03 21:49:04
)
type Worker struct {
mu sync.Mutex
timeStamp int64
workerId int64
number int64
}
func NewWorker(workerId int64) (*Worker, error) {
if workerId < 0 || workerId > workerMax {
return nil, errors.New("WorkerId超过了限制!")
}
return &Worker{
timeStamp: 0,
workerId: workerId,
number: 0,
}, nil
}
func (w *Worker) NextId() int64 {
w.mu.Lock()
defer w.mu.Unlock()
//当前时间的毫秒时间戳
now := time.Now().UnixNano() / 1e6
//如果时间戳与当前时间相同,则增加序列号
if w.timeStamp == now {
w.number++
//如果序列号超过了最大值,则更新时间戳
if w.number > numberMax {
for now <= w.timeStamp {
now = time.Now().UnixNano() / 1e6
}
}
} else { //如果时间戳与当前时间不同,则直接更新时间戳
w.number = 0
w.timeStamp = now
}
//ID由时间戳、机器编码、序列号组成
ID := (now-epoch)<<timeShift | (w.workerId << workerShift) | (w.number)
return ID
}
测试:
func main() {
worker, err := snow.NewWorker(0)
if err != nil {
fmt.Println(err)
}
for i := 0; i < 5; i++ {
id := worker.NextId()
fmt.Println(id)
}
}
// 结果:
// 7783621591040
// 7783621591041
// 7783621591042
// 7783621591043
// 7783621591044
func BenchmarkID(b *testing.B) {
worker, _ := NewWorker(0)
for i := 0; i < b.N; i++ {
worker.NextId()
}
}
//BenchmarkID-16 4902658(执行次数) 244.5 ns/op(平均每次执行所需时间)