脚本之家,脚本语言编程技术及教程分享平台!
分类导航

Python|VBS|Ruby|Lua|perl|VBA|Golang|PowerShell|Erlang|autoit|Dos|bat|

服务器之家 - 脚本之家 - Golang - Go实现分布式唯一ID的生成之雪花算法

Go实现分布式唯一ID的生成之雪花算法

2022-09-30 20:26Blockchain210 Golang

本文主要介绍了Go实现分布式唯一ID的生成之雪花算法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧

分布式唯一ID的生成

背景:

在分布式架构下,唯一序列号生成是我们在设计一个尤其是数据库使用分库分表的时候会常见的一个问题

特性:

全局唯一,这是基本要求,不能出现重复数字类型,趋势递增,后面的ID必须比前面的大长度短,能够提高查询效率,这也是从MySQL数据库规范出发的,尤其是ID作为主键时**信息安全,**如果ID连续生成,势必会泄露业务信息,所以需要无规则不规则高可用低延时,ID生成快,能够扛住高并发,延时足够低不至于成为业务瓶颈.

雪花算法:

​ snowflake是推特开源的分布式ID生成算法

结果: long 型的ID号(64位的ID号)

核心思想(生成的ID号是64位那么就对64位进行划分赋予特别的含义):

Go实现分布式唯一ID的生成之雪花算法

41bit-时间戳决定了该算法生成ID号的可用年限.

10bit-工作机器编号决定了该分布式系统的扩容性即机器数量.

12bit-序列号决定了每毫秒单机系统可以生成的序列号

拓展:什么是时间戳?

北京时间1970年01月01日08时00分00秒到此时时刻的总秒数

优势:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
//实现方法:
package main
 
 
import (
    "errors"
    "fmt"
    "sync"
    "time"
)
 
/*
    雪花算法(snowFlake)的具体实现方案:
 */
 
type SnowFlake struct{
    mu sync.Mutex
    //雪花算法开启时的起始时间戳
    twepoch int64
 
    //每一部分占用的位数
    workerIdBits     int64 //每个数据中心的工作机器的编号位数
    datacenterIdBits int64 //数据中心的编号位数
    sequenceBits     int64 //每个工作机器每毫秒递增的位数
 
    //每一部分最大的数值
    maxWorkerId int64
    maxDatacenterId int64
    maxSequence int64
 
    //每一部分向左移动的位数
    workerIdShift int64
    datacenterIdShift int64
    timestampShift int64
 
    //当前数据中心ID号
    datacenterId int64
    //当前机器的ID号
    workerId int64
    //序列号
    sequence int64
    //上一次生成ID号前41位的毫秒时间戳
    lastTimestamp int64
}
 
/*
    获取毫秒的时间戳
 */
func (s *SnowFlake)timeGen()int64{
    return time.Now().UnixMilli()
}
/*
    获取比lastTimestamp大的当前毫秒时间戳
 */
func (s *SnowFlake)tilNextMills()int64{
    timeStampMill:=s.timeGen()
    for timeStampMill<=s.lastTimestamp{
        timeStampMill=s.timeGen()
    }
    return timeStampMill
}
func (s *SnowFlake)NextId()(int64,error){
    s.mu.Lock()
    defer s.mu.Unlock()
    nowTimestamp:=s.timeGen()//获取当前的毫秒级别的时间戳
    if nowTimestamp<s.lastTimestamp{
        //系统时钟倒退,倒退了s.lastTimestamp-nowTimestamp
        return -1,errors.New(fmt.Sprintf("clock moved backwards, Refusing to generate id for %d milliseconds",s.lastTimestamp-nowTimestamp))
    }
    if nowTimestamp==s.lastTimestamp{
        s.sequence=(s.sequence+1)&s.maxSequence
        if s.sequence==0{
             //tilNextMills中有一个循环等候当前毫秒时间戳到达lastTimestamp的下一个毫秒时间戳
            nowTimestamp=s.tilNextMills()
        }
    }else{
        s.sequence=0
    }
    s.lastTimestamp=nowTimestamp
    return (nowTimestamp-s.twepoch)<<s.timestampShift| //时间戳差值部分
        s.datacenterId<<s.datacenterIdShift| //数据中心部分
        s.workerId<<s.workerIdShift| //工作机器编号部分
        s.sequence, //序列号部分
        nil
}
 
func NewSnowFlake(workerId int64,datacenterId int64)(*SnowFlake,error){
    mySnow:=new(SnowFlake)
    mySnow.twepoch=time.Now().Unix() //返回当前时间的时间戳(时间戳是指北京时间1970年01月01日8时0分0秒到此时时刻的总秒数)
    if workerId<0||datacenterId<0{
        return nil,errors.New("workerId or datacenterId must not lower than 0 ")
    }
    /*
        标准的雪花算法
     */
    mySnow.workerIdBits =5
    mySnow.datacenterIdBits=5
    mySnow.sequenceBits=12
 
    mySnow.maxWorkerId=-1^(-1<<mySnow.workerIdBits)         //64位末尾workerIdBits位均设为1,其余设为0
    mySnow.maxDatacenterId=-1^(-1<<mySnow.datacenterIdBits) //64位末尾datacenterIdBits位均设为1,其余设为0
    mySnow.maxSequence=-1^(-1<<mySnow.sequenceBits)  //64位末尾sequenceBits位均设为1,其余设为0
 
    if workerId>=mySnow.maxWorkerId||datacenterId>=mySnow.maxDatacenterId{
        return nil,errors.New("workerId or datacenterId must not higher than max value ")
    }
    mySnow.workerIdShift=mySnow.sequenceBits
    mySnow.datacenterIdShift=mySnow.sequenceBits+mySnow.workerIdBits
    mySnow.timestampShift=mySnow.sequenceBits+mySnow.workerIdBits+mySnow.datacenterIdBits
 
    mySnow.lastTimestamp=-1
    mySnow.workerId=workerId
    mySnow.datacenterId=datacenterId
 
    return mySnow,nil
}
 
 
 
func main(){
    //模拟实验是生成并发400W个ID,所需要的时间
    mySnow,_:=NewSnowFlake(0,0)//生成雪花算法
    group:=sync.WaitGroup{}
    startTime:=time.Now()
    generateId:=func (s SnowFlake,requestNumber int){
        for i:=0;i<requestNumber;i++{
            s.NextId()
            group.Done()
        }
    }
    group.Add(4000000)
    //生成并发的数为4000000
    currentThreadNum:=400
    for i:=0;i<currentThreadNum;i++{
        generateId(*mySnow,10000)
    }
    group.Wait()
    fmt.Printf("time: %v\n",time.Now().Sub(startTime))
}

Go实现分布式唯一ID的生成之雪花算法

以上分析生成400WID号只需要803.1006ms(所以单机上可以每秒生成的ID数在400W以上)

优点:

毫秒数在高位,自增序列在低位,整个ID都是趋势递增不依赖数据库等第三方系统,以服务的方式部署,稳定性更高,生成的ID性能也是非常高的可以根据自身业务特性分配bit位,非常灵活

缺陷:

1. 依赖机器时钟,如果**机器时钟回拨**,会导致重复ID生成.
2. 在单机上是递增的,但是由于设计到分布式环境下,每台机器上的时钟不可能完全同步,有时候会出现不是全局递增的情况.

如何解决单机系统中时钟回拨问题:

​ 可以分为两种情况:

1. 如果**时间回拨时间较短,比如配置5ms以内**,那么可以直接等候一定的时间,让机器时间追上来
2. 如果**时间回拨时间较长**,我们不能接收这么长的阻塞等候,那么就有两个策略,直接拒绝,抛出异常;或者通过RD时钟回滚

布式环境下,每台机器上的时钟不可能完全同步,有时候会出现不是全局递增的情况.

如何解决单机系统中时钟回拨问题:

​ 可以分为两种情况:

1. 如果**时间回拨时间较短,比如配置5ms以内**,那么可以直接等候一定的时间,让机器时间追上来
2. 如果**时间回拨时间较长**,我们不能接收这么长的阻塞等候,那么就有两个策略,直接拒绝,抛出异常;或者通过RD时钟回滚

参考博客高并发情况下,雪花ID一秒400W个,以及分布式ID算法(详析)

到此这篇关于Go实现分布式唯一ID的生成之雪花算法的文章就介绍到这了,更多相关Go分布式唯一ID 内容请搜索服务器之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持服务器之家!

原文链接:https://blog.csdn.net/Blockchain210/article/details/124217374

延伸 · 阅读

精彩推荐
  • GolangGoLand如何设置中文

    GoLand如何设置中文

    这篇文章主要介绍了GoLand如何设置中文,本文通过图文并茂的形式给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参...

    正在攀登的小蜗牛25512021-02-21
  • Golang深入理解Golang之http server的实现

    深入理解Golang之http server的实现

    这篇文章主要介绍了深入理解Golang之http server的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们...

    Turling_hu4632020-05-29
  • Golang这个 Go 语言的经典 “坑”,我算是服了

    这个 Go 语言的经典 “坑”,我算是服了

    在开始之前,先考你一个非常 Go 味的经典问题:如何判断一个 interface{} 的值是否为 nil ?这也是面试有可能会被问到的一个问题,这个问题很 “迷”,平时...

    Go编程时光5382022-01-04
  • Golang深入分析golang多值返回以及闭包的实现

    深入分析golang多值返回以及闭包的实现

    相对于C/C++,golang有很多新颖的特性,例如goroutine,channel等等,这些特性其实从golang源码是可以理解其实现的原理。今天这篇文章主要来分析下golang多值返回...

    daisy4342020-05-01
  • Golang详解Golang语言中的interface

    详解Golang语言中的interface

    这篇文章主要介绍了Golang语言中的interface的相关资料,帮助大家更好的理解和使用golang,感兴趣的朋友可以了解下...

    雨燕3842021-03-24
  • Golanggo grpc安装使用教程

    go grpc安装使用教程

    gRPC是由Google主导开发的RPC框架,使用HTTP/2协议并用ProtoBuf作为序列化工具。这篇文章主要介绍了go grpc安装使用教程,需要的朋友可以参考下 ...

    hncscwc5712020-05-14
  • Golanggolang log4go的日志输出优化详解

    golang log4go的日志输出优化详解

    log4go源于google的一项log工程,但官方已经停止维护更新,下面这篇文章主要给大家介绍了关于golang log4go的日志输出优化的相关资料,文中通过示例代码介绍...

    ccpaging4032020-05-13
  • Golanggraphql---go http请求使用详解

    graphql---go http请求使用详解

    这篇文章主要介绍了graphql---go http请求使用详解,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...

    逆月林12302021-03-02