专栏名称: 狗厂
目录
相关文章推荐
51好读  ›  专栏  ›  狗厂

记一次 golang 实现Twitter snowFlake算法 高效生成全局唯一ID

狗厂  · 掘金  ·  · 2018-05-09 03:01

正文

最近在着手准备一个H5游戏
因为这是我第一次接触游戏这个类目
即使量不大也想好好的做它一番
在设计表结构的时候想到了表全局唯一id这个问题
既然是游戏
那么一定是多人在线点点点(运营理想状态 哈哈哈)
一开始想使用mongoDB的objectId来作为全局唯一id
但是字符串作为索引的效率肯定不如整型来得实在

两者的主要差别就在于,字符类型有字符集的概念,每次从存储端到展现端之间都有一个字符集编码的过程。而这一过程主要消耗的就是CPU资源,对于In-memory的操作来说,这是一个不可忽视的消耗。如果使用整型替换可以减少CPU运算及内存和IO的开销。

所以最后考虑到理想状态下的效率及视觉效果(整型),考虑找一个纯整型的id替代方案 无意间看到了Twitter的snowFlake算法

这篇内容大部分借鉴网络内容,整合在一起只为帮助自己和各位看官更好的理解snowFlake的原理

snowFlake 雪花算法

snowflake ID 算法是 twitter 使用的唯一 ID 生成算法,为了满足 Twitter 每秒上万条消息的请求,使每条消息有唯一、有一定顺序的 ID ,且支持分布式生成。

原理

其实很简单,只需要理解:某一台拥有独立标识(为机器分配独立id)的机器在1毫秒内生成带有不同序号的id 所以生成出来的id是具有时序性和唯一性的

构成

这里直接借鉴前人的整理,只为给大家更加清楚的讲解

snowflake ID 的结构是一个 64 bit 的 int 型数据。

clipboard.png

  • 第1位bit:
    二进制中最高位为1的都是负数,但是我们所需要的id应该都是整数,所以这里最高位应该为0
  • 后面的41位bit:
    用来记录生成id时的毫秒时间戳,这里毫秒只用来表示正整数(计算机中正整数包含0),所以可以表示的数值范围是0至2^41 - 1(这里为什么要-1很多人会范迷糊,要记住,计算机中数值都是从0开始计算而不是1)
  • 再后面的10位bit:
    用来记录工作机器的id
    2^10 = 1024 所以当前规则允许分布式最大节点数为1024个节点 我们可以根据业务需求来具体分配worker数和每台机器1毫秒可生成的id序号number数
  • 最后的12位:
    用来表示单台机器每毫秒生成的id序号
    12位bit可以表示的最大正整数为2^12 - 1 = 4096,即可用0、1、2、3...4095这4096(注意是从0开始计算)个数字来表示1毫秒内机器生成的序号(这个算法限定单台机器1毫秒内最多生成4096个id,超出则等待下一毫秒再生成)

最后将上述4段bit通过位运算拼接起来组成64位bit

实现

这里我们用golang来实现一下snowFlake 首先定义一下snowFlake最基础的几个常量,每个常量的用户我都通过注释来详细的告诉大家

// 因为snowFlake目的是解决分布式下生成唯一id 所以ID中是包含集群和节点编号在内的

const (
    numberBits uint8 = 12 // 表示每个集群下的每个节点,1毫秒内可生成的id序号的二进制位 对应上图中的最后一段
    workerBits uint8 = 10 // 每台机器(节点)的ID位数 10位最大可以有2^10=1024个节点数 即每毫秒可生成 2^12-1=4096个唯一ID 对应上图中的倒数第二段  
    // 这里求最大值使用了位运算,-1 的二进制表示为 1 的补码,感兴趣的同学可以自己算算试试 -1 ^ (-1 << nodeBits) 这里是不是等于 1023
    workerMax int64 = -1 ^ (-1 << workerBits) // 节点ID的最大值,用于防止溢出 
    numberMax int64 = -1 ^ (-1 << numberBits) // 同上,用来表示生成id序号的最大值
    timeShift uint8 = workerBits + numberBits // 时间戳向左的偏移量
    workerShift uint8 = numberBits // 节点ID向左的偏移量
    // 41位字节作为时间戳数值的话,大约68年就会用完
    // 假如你2010年1月1日开始开发系统 如果不减去2010年1月1日的时间戳 那么白白浪费40年的时间戳啊!
    // 这个一旦定义且开始生成ID后千万不要改了 不然可能会生成相同的ID
    epoch int64 = 1525705533000 // 这个是我在写epoch这个常量时的时间戳(毫秒)
)

上述代码中 两个偏移量 timeShift 和 workerShift 是对应图中时间戳和工作节点的位置
时间戳是在从右往左的 workerBits + numberBits (即22)位开始,大家可以数数看就很容易理解了
workerShift 同理

Worker 工作节点

因为是分布式下的ID生成算法,所以我们要生成多个Worker,所以这里抽象出一个woker工作节点所需要的基本参数

// 定义一个woker工作节点所需要的基本参数
type Worker struct {
    mu sync.Mutex // 添加互斥锁 确保并发安全
    timestamp int64 // 记录上一次生成id的时间戳
    workerId int64 // 该节点的ID
    number int64 // 当前毫秒已经生成的id序列号(从0开始累加) 1毫秒内最多生成4096个ID
}

实例化工作节点

由于是分布式情况下,我们应该通过外部配置文件或者其他方式为每台机器分配独立的id







请到「今天看啥」查看全文