使用Golang实现SnowFlake雪花分布式ID生成器

简介

snowflake(雪花算法)是一个开源的分布式ID生成算法,结果是一个long型的ID。snowflake算法将64bit划分为多段,分开来标识机器、时间等信息,具体组成结构如下图所示:

1654399216-0321-629c20f007d88-766440

位置(从右到左)

大小

作用

0~11bit

12bits

序列号,用来对同一个毫秒之内产生不同的ID,可记录4095个

12~21bit

10bits

10bit用来记录机器ID,总共可以记录1024台机器

22~62bit

41bits

用来记录时间戳,这里可以记录69年

63bit

1bit

符号位,不做处理

特点

它有以下几个特点:

  • 能满足高并发分布式系统环境下ID不重复;
  • 基于时间戳,可以保证基本有序递增;
  • 不依赖于第三方的库或者中间件;
  • 不支持时间回拨;

代码实现

  1. 定义SnowFlake结构体// SnowFlake 雪花分布式ID结构体 type SnowFlake struct { epoch int64 // 起始时间戳 timestamp int64 // 当前时间戳,毫秒 centerId int64 // 数据中心机房ID workerId int64 // 机器ID sequence int64 // 毫秒内序列号 timestampBits int64 // 时间戳占用位数 centerIdBits int64 // 数据中心id所占位数 workerIdBits int64 // 机器id所占位数 sequenceBits int64 // 序列所占的位数 lastTimestamp int64 // 上一次生成ID的时间戳 sequenceMask int64 // 生成序列的掩码最大值 workerIdShift int64 // 机器id左移偏移量 centerIdShift int64 // 数据中心机房id左移偏移量 timestampShift int64 // 时间戳左移偏移量 maxTimeStamp int64 // 最大支持的时间 lock sync.Mutex // 锁 }
  2. 初始化Snowflake

由于 -1 在二进制上表示是:

11111111 11111111 11111111 11111111 11111111 11111111 11111111 11111111

所以想要求得 41bits 的 timestamp 最大值可以将 -1 向左位移 41 位,得到:

11111111 11111111 11111110 00000000 00000000 00000000 00000000 00000000

那么再和 -1 进行 ^异或运算:

00000000 00000000 00000001 11111111 11111111 11111111 11111111 11111111

这就可以表示 41bits 的 timestamp 最大值,maxWorkerId和maxCenterId同理。

// Init 初始化
func (s *SnowFlake) Init(centerId, workerId int64) error {
	s.epoch = int64(1622476800000) //设置起始时间戳:2022-01-01 00:00:00
	s.centerId = centerId
	s.workerId = workerId
	s.centerIdBits = 4   // 支持的最大机房ID占位数,最大是15
	s.workerIdBits = 6   // 支持的最大机器ID占位数,最大是63
	s.timestampBits = 41 // 时间戳占用位数
	s.maxTimeStamp = -1 ^ (-1 << s.timestampBits)

	maxWorkerId := -1 ^ (-1 << s.workerIdBits)
	maxCenterId := -1 ^ (-1 << s.centerIdBits)

	// 参数校验
	if int(centerId) > maxCenterId || centerId < 0 {
		return errors.New(fmt.Sprintf("Center ID can't be greater than %d or less than 0", maxCenterId))
	}
	if int(workerId) > maxWorkerId || workerId < 0 {
		return errors.New(fmt.Sprintf("Worker ID can't be greater than %d or less than 0", maxWorkerId))
	}

	s.sequenceBits = 12 // 序列在ID中占的位数,最大为4095
	s.sequence = -1

	s.lastTimestamp = -1                                                // 上次生成 ID 的时间戳
	s.sequenceMask = -1 ^ (-1 << s.sequenceBits)                        // 计算毫秒内,最大的序列号
	s.workerIdShift = s.sequenceBits                                    // 机器ID向左移12位
	s.centerIdShift = s.sequenceBits + s.workerIdBits                   // 机房ID向左移18位
	s.timestampShift = s.sequenceBits + s.workerIdBits + s.centerIdBits // 时间截向左移22位

	return nil
}
  1. 生成下一个ID// NextId 生成下一个ID func (s *SnowFlake) NextId() (int64, error) { s.lock.Lock() //设置锁,保证线程安全 defer s.lock.Unlock() now := time.Now().UnixNano() / 1000000 // 获取当前时间戳,转毫秒 if now < s.lastTimestamp { // 如果当前时间小于上一次 ID 生成的时间戳,说明发生时钟回拨 return 0, errors.New(fmt.Sprintf("Clock moved backwards. Refusing to generate id for %d milliseconds", s.lastTimestamp-now)) } t := now - s.epoch if t > s.maxTimeStamp { return 0, errors.New(fmt.Sprintf("epoch must be between 0 and %d", s.maxTimeStamp-1)) } // 同一时间生成的,则序号+1 if s.lastTimestamp == now { s.sequence = (s.sequence + 1) & s.sequenceMask // 毫秒内序列溢出:超过最大值; 阻塞到下一个毫秒,获得新的时间戳 if s.sequence == 0 { for now <= s.lastTimestamp { now = time.Now().UnixNano() / 1000000 } } } else { s.sequence = 0 // 时间戳改变,序列重置 } // 保存本次的时间戳 s.lastTimestamp = now // 根据偏移量,向左位移达到 return (t << s.timestampShift) | (s.centerId << s.centerIdShift) | (s.workerId << s.workerIdShift) | s.sequence, nil }
  2. 调用示例func main() { snowFlake := &SnowFlake{} _ = snowFlake.Init(1, 5) for i := 0; i < 10; i++ { fmt.Println(snowFlake.NextId()) } }

结果输出:

72644832480481280 <nil>
72644832480481281 <nil>
72644832480481282 <nil>
72644832480481283 <nil>
72644832480481284 <nil>
72644832480481285 <nil>
72644832480481286 <nil>
72644832480481287 <nil>
72644832480481288 <nil>
72644832480481289 <nil>

原文地址:https://cloud.tencent.com/developer/article/2063456

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


类型转换 1、int转string 2、string转int 3、string转float 4、用户结构类型转换
package main import s &quot;strings&quot; import &quot;fmt&quot; var p = fmt.Println func main() { p(&quot;Contains: &quot;, s.Contains(&quot;test&quo
类使用:实现一个people中有一个sayhi的方法调用功能,代码如下: 接口使用:实现上面功能,代码如下:
html代码: beego代码:
1、读取文件信息: 2、读取文件夹下的所有文件: 3、写入文件信息 4、删除文件,成功返回true,失败返回false
配置环境:Windows7+推荐IDE:LiteIDEGO下载地址:http://www.golangtc.com/downloadBeego开发文档地址:http://beego.me/docs/intro/ 安装步骤: 一、GO环境安装 二、配置系统变量 三、Beego安装 一、GO环境安装 根
golang获取程序运行路径:
Golang的文档和社区资源:为什么它可以帮助开发人员快速上手?
Golang:AI 开发者的实用工具
Golang的标准库:为什么它可以大幅度提高开发效率?
Golang的部署和运维:如何将应用程序部署到生产环境中?
高性能AI开发:Golang的优势所在
本篇文章和大家了解一下go语言开发优雅得关闭协程的方法。有一定的参考价值,有需要的朋友可以参考一下,希望对大家有所帮助。1.简介本文将介绍首先为什么需要主...
这篇文章主要介绍了Go关闭goroutine协程的方法,具有一定借鉴价值,需要的朋友可以参考下。下面就和我一起来看看吧。1.简介本文将介绍首先为什么需要主动关闭gor...
本篇文章和大家了解一下go关闭GracefulShutdown服务的几种方法。有一定的参考价值,有需要的朋友可以参考一下,希望对大家有所帮助。目录Shutdown方法Regi...
这篇文章主要介绍了Go语言如何实现LRU算法的核心思想和实现过程,具有一定借鉴价值,需要的朋友可以参考下。下面就和我一起来看看吧。GO实现Redis的LRU例子常
今天小编给大家分享的是Go简单实现多租户数据库隔离的方法,相信很多人都不太了解,为了让大家更加了解,所以给大家总结了以下内容,一起往下看吧。一定会...
这篇“Linux系统中怎么安装NSQ的Go语言客户端”文章的知识点大部分人都不太理解,所以小编给大家总结了以下内容,内容详细,步骤清晰,具有一定的借鉴价值,希
本文小编为大家详细介绍“怎么在Go语言中实现锁机制”,内容详细,步骤清晰,细节处理妥当,希望这篇“怎么在Go语言中实现锁机制”文章能帮助大家解决疑惑,下面...
今天小编给大家分享一下Go语言中interface类型怎么使用的相关知识点,内容详细,逻辑清晰,相信大部分人都还太了解这方面的知识,所以分享这篇文章给大家参考