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

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

分布式唯一ID的生成

背景:

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

特性:

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

雪花算法:

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

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

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

在这里插入图片描述

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

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

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

拓展:什么是时间戳?

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

优势:

//实现方法:
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))
}

在这里插入图片描述

以上分析生成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 内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • Go 协程超时控制的实现

    Go 协程超时控制的实现

    本文主要介绍了Go 协程超时控制的实现,文中通过示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2021-08-08
  • 浅谈Golang Slice切片如何扩容的实现

    浅谈Golang Slice切片如何扩容的实现

    本文主要介绍了浅谈Golang Slice切片如何扩容的实现,文中通过示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2022-02-02
  • 基于Golang设计一套可控的定时任务系统

    基于Golang设计一套可控的定时任务系统

    这篇文章主要为大家学习介绍了如何基于Golang设计一套可控的定时任务系统,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下
    2023-07-07
  • 教你一分钟配置好Go语言开发环境(多种操作系统)

    教你一分钟配置好Go语言开发环境(多种操作系统)

    在这篇文章中,我们从头到尾一步步指导你配置Golang开发环境,并编写你的第一个"Hello, World!"程序,我们详细解释了在多种操作系统(包括Windows、Linux和macOS)下的安装过程、环境变量设置以及如何验证安装是否成功
    2023-09-09
  • Golang如何调用windows下的dll动态库中的函数

    Golang如何调用windows下的dll动态库中的函数

    这篇文章主要介绍了Golang如何调用windows下的dll动态库中的函数方式,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2021-05-05
  • golang基于websocket通信tcp keepalive研究记录

    golang基于websocket通信tcp keepalive研究记录

    这篇文章主要为大家介绍了golang基于websocket通信tcp keepalive研究记录,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-06-06
  • Go语言编程学习golang配置golint

    Go语言编程学习golang配置golint

    这篇文章主要为大家介绍了Go语言编程学习golang配置golint的过程示例详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2021-11-11
  • Go语言中的速率限流策略全面详解

    Go语言中的速率限流策略全面详解

    这篇文章主要为大家介绍了Go语言中的速率限流策略全面详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-11-11
  • golang打包成带图标的exe可执行文件

    golang打包成带图标的exe可执行文件

    这篇文章主要给大家介绍了关于golang打包成带图标的exe可执行文件的相关资料,文中通过实例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2023-06-06
  • GO中sync包自由控制并发示例详解

    GO中sync包自由控制并发示例详解

    这篇文章主要为大家介绍了GO中sync包自由控制并发示例详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-08-08

最新评论