分布式ID生成方案-snowflake算法
Posted 码农在新加坡
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了分布式ID生成方案-snowflake算法相关的知识,希望对你有一定的参考价值。
背景
在互联网的业务系统中,涉及到各种各样的ID,这些ID需要保证全局唯一。我们称之为分布式ID,分布式ID需要满足 唯一性、趋势递增性、高可用性、高性能等特点。
snowflake算法,也叫雪花算法,是其中的一种分布式ID生成方案。是twitter公司内部分布式项目采用的ID生成算法,开源后广受国内大厂的好评,在该算法影响下各大公司相继开发出各具特色的分布式生成器。
讲解雪花算法前,我们先概述一下分布式ID有哪些生成方案。
分布式ID生成方案
分布式ID有以下几种生成方式:
UUID
算法的核心思想是结合机器的网卡、当地时间、一个随记数来生成UUID。
优点:本地生成,生成简单,性能好,没有高可用风险
缺点:长度过长,存储冗余,且无序不可读,查询效率低
数据库自增ID
使用数据库的id自增策略,如 mysql 的 auto_increment
。并且可以使用多台数据库分别设置不同步长,生成不重复ID的策略来实现高可用。
优点:数据库生成的ID绝对有序,高可用实现方式简单
缺点:需要独立部署数据库实例,成本高,有性能瓶颈
Redis生成ID
Redis的所有命令操作都是单线程的,本身提供像 incr 和 increby 这样的自增原子命令,所以能保证生成的 ID 肯定是唯一有序的。
优点:不依赖于数据库,灵活方便,且性能优于数据库;数字ID天然排序,对分页或者需要排序的结果很有帮助。
缺点:如果系统中没有Redis,还需要引入新的组件,增加系统复杂度;需要编码和配置的工作量比较大。
考虑到单节点的性能瓶颈,可以使用 Redis 集群来获取更高的吞吐量。假如一个集群中有5台 Redis。可以初始化每台 Redis 的值分别是1, 2, 3, 4, 5,然后步长都是 5。各个 Redis 生成的 ID 为:
A:1, 6, 11, 16, 21
B:2, 7, 12, 17, 22
C:3, 8, 13, 18, 23
D:4, 9, 14, 19, 24
E:5, 10, 15, 20, 25
步长和初始值一定需要事先确定。使用 Redis 集群也可以防止单点故障的问题。
另外,比较适合使用 Redis 来生成每天从0开始的流水号。比如订单号 = 日期 + 当日自增长号。可以每天在 Redis 中生成一个 Key ,使用 INCR 进行累加。
snowflake算法
雪花算法(Snowflake)是twitter公司内部分布式项目采用的ID生成算法,开源后广受国内大厂的好评,在该算法影响下各大公司相继开发出各具特色的分布式生成器。
Twitter的snowflake分布式ID的算法是目前广泛使用的分布式ID算法,尽管有很多变种,比如位数的不同,时间片大小不同、node bit数放在最后等各种变种,但是主要思想还是来自于snowflake的思想。
Twitter在2010年儿童节的时候在官方博客上介绍了snowflake算法,内部用来表示每一条tweet。
snowflake算法采用64bit存储ID, 最高位备用,暂时不使用。接下来的41 bit做时间戳,最小时间单位为毫秒。再接下来的10 bit做机器ID(worker id),然后最后12 bit在单位时间(毫秒)递增。
41 bit表示时间戳大约可以使用69年(2^41 -1), 为了尽可能的表示时间,时间戳可以从第一次部署的时候开始计算,比如2020-02-02 00:00:00, 这样69年内可以无虞。
10 bit区分机器,所以可以支持1024台机器。 你也可以把10bit分成两部分,一部分做数据中心的ID,一部分做机器的ID,比如55分的化,可以支持32个数据中心,每个数据中心最多可以支持32台机器。
12 bit自增值可以表示4096的ID,也就是说每台机器每毫秒最多产生4096个ID,这是它的最大性能。
正如前面所说,时间戳、机器ID、自增ID所占的位数可以根据你实际的情况做调整。
snowflake还有一个很好的特性就是基本保持顺序性,因为它的前几位是时间戳,可以对ID按照时间进行排序。
snowflake算法详解
golang核心代码如下:
var (
// 可以设置成系统第一次上线的时间,单位:毫秒。占用41位,可以使用69年。
Epoch int64 = 1288834974657
// 实例的ID占用10位,最多支持1024个实例。
NodeBits uint8 = 10
// 步长占用12位,每一台实例上每一毫秒最多产生2^12=4096个不重复的数据。
StepBits uint8 = 12
)
// 生成算法
func (n *Node) Generate() ID
n.mu.Lock()
// 从设定的时间戳到现在经历的毫秒数
now := time.Since(n.epoch).Nanoseconds() / 1000000
//和上次记录一样,step加1,否则step重置为0
if now == n.time
n.step = (n.step + 1) & n.stepMask
if n.step == 0
for now <= n.time
now = time.Since(n.epoch).Nanoseconds() / 1000000
else
n.step = 0
n.time = now
// 时间戳 41 位 | node 10位 | step 12位
r := ID((now)<<n.timeShift |
(n.node << n.nodeShift) |
(n.step),
)
n.mu.Unlock()
return r
具体的bit可以根据自己的需求进行调整,比如既可以是(41,10,12),也可以是(41,6,16)…
完整的golang代码repo:
snowflake github
优点:
- 存储少, 8个字节
- 可读性高
- 性能好,可以中心化的产生ID,也可以独立节点生成
缺点:
- 时间回拨会重复产生ID
时钟回拨
如果我们的场景需要解决时钟回拨的问题:
可以找2bit位作为时钟回拨位,发现有时钟回拨就将回拨位加1,达到最大位后再从0开始进行循环。
例如上图中的, 10 bit的机器号=>8 bit的机器号+2 bit的回拨位.
每次发现时钟回拨,就把回拨位+1.大于最大值(3)后重设为0.
如图所示,在同一个时间段,最多支持时钟回拨3次。
每次回拨后,时钟回拨位不同,导致最终生成的ID也不同。
<全文完>
原文地址:分布式ID生成方案-snowflake算法 (微信公众号)
欢迎关注我的微信公众号:【码农在新加坡】的微信公众号,有更多好的技术分享。
以上是关于分布式ID生成方案-snowflake算法的主要内容,如果未能解决你的问题,请参考以下文章
雪花算法snowflake分布式id生成原理详解,以及对解决时钟回拨问题几种方案讨论