简介

Twitter 早期用 MySQL 存储数据,随着用户的增长,单一的 MySQL 实例没法承受海量的数据,后来团队就研究如何产生完美的自增ID,以满足两个基本的要求:

  • 每秒能生成几十万条 ID 用于标识不同的 记录;

  • 这些 ID 应该可以有个大致的顺序,也就是说发布时间相近的两条记录,它们的 ID也应当相近,这样才能方便各种客户端对记录 进行排序。

Twitter-Snowflake算法就是在这样的背景下产生的。

核心

Twitter 解决这两个问题的方案非常简单高效:每一个 ID 都是 64 位数字,由时间戳、工作机器节点和序列号组成, ID是由当前所在的机器节点生成的。如图:

下面先说明一下各个区间的作用。

  • 符号位:用于区分正负数。1为负数,0为整数。一般不需要负数,所以值固定为0。

  • 时间戳:一共预留41bit保存毫秒级时间戳。因为毫秒级时间戳长度是13位:41位二进制最大值(T)是:$2^{41}-1 = 2199023255551 $ , 刚好13位。可以表示的年份 = T / (3600 * 24 * 365 * 1000) = 69.7年。换算成Unix时间也就是可以表示到:2039-09-07 23:47:35:

大家会觉得这个时间不够用啊,没关系,后面会讲如何优化。

  • 工作机器:预留了10bit保存机器ID。只要机器ID不一样,每毫秒生成的ID是不一样的。一共可以支持多少台机器同时生成ID呢? 答案是 1023 台(\(2^{10}-1\))。

    如果工作机器比较少,可以使用配置文件来设置这个id,或者使用随机数。如果机器过多就得单独实现一共工作机器ID分配器了,比如使用redis自增,或者利用Mysql auto_increment机制也可以达到效果。

  • 序列号:序列号一共是12bit,为了处理在同一机器同一毫秒内需要给多条消息分配id的情况,一共可以产生4095个序列号(0~4095, \(2^{12}-1\))。

综上:同一台机器1毫秒内可产生4095个ID,全部机器1毫秒内可产生 4095 * 1023 个ID。由于全是在各个机器本地生成,效率非常高。

简单实现

下面是一个简单实现:仅有时间戳,机器位为0,序列号为0:

#include <stdio.h>

int main()
{
long long id;
id = 1572057648000 << 22; //相当于 id = 1572057648000 << 22 | 0 << 12 | 0;
printf("id=%lld\n", id); return 0;
}

输出:

id=6593687681236992000

代码实现主要用到了左移和或位运算(或运算),各个语言类似。上面的实现输出的结果是一个19位长度的整数。

优化

1、时间戳优化

如果时间戳取当前毫秒级时间戳,那么只能表示到2039年,远远不够。我们发现,1970到当前时间这个区间其实是永远都不会用了,那么,为何不使用偏移量呢?也就是时间戳部分不直接取当前毫秒级时间戳,而是在此基础上减去一个过去时间:

id = (1572057648000 - 1569859200000) << 22;

输出:

id=9220959240192000

上面代码中,第一个时间戳是当前毫秒级时间戳,第二个则是一个过去时间戳(1569859200000表示2019-10-01 00:00:00)。这样我们可以表示的年大概是 当前年份(例如2019) + 69 = 2088 年,很长一段时间内都够用。

2、序列号

序列号默认取0,如果已经使用了则自增。若自增到4096,也就是同一毫秒内的序列号用完了,怎么办呢?需要等待至下一毫秒。部分代码示例:

//同一毫秒并发调用
if (ts == (iw.last_time_stamp)) {
//序列号自增
iw.sequence = (iw.sequence+1) & MASK_SEQUENCE; //序列号自增到最大值4096,4095 & 4096 = 0
if (iw.sequence == 0) {
//等待至下一毫秒
ts = time_re_gen(ts);
}
} else { //同一毫秒没有重复的
iw.last_time_stamp = ts;
}

算法变种

1、53bits版本:因为js只支持53位bit的数值

* 0 32 51 53
+-----------+------+------+
|0|time(32) |workid(8) |seq(12) |
+-----------+------+------+

2、其它版本

我们也可以根据自己的业务需求,将不同区间的bit位进行调整。机器位和序列号ID并不是必须的,可以合并。或者拆分出更多的区间表示更多的意义。例如订单号:

* 0 41 47 59  64
+-----------+------+------+------+------+
|0|time(41) |workid(6) |seq(12) | uid(4)
+-----------+------+------+------+------+

我们对订单分16个(2^4)表,每次将 uid & 0xF(也就是 uid & 15)的结果放到后四位,这样以后根据uid查订单的时候,uid mod 16 就能得到数据在哪个分表;同时根据订单ID本身也能找到对应的分表。示例:

php > echo 1572070381000 << 22 | 1 << 16 | 0 << 4 | (1820 & 15);
6593741087309889548
php > echo 1572070381000 << 22 | 1 << 16 | 0 << 4 | (5177331 & 15);
6593741087309889539

验证测试:

php > echo 1572070381000 << 22 | 1 << 16 | 0 << 4 | (5177331 & 15);
6593741087309889539
php > echo 6593741087309889548 % 16;
12
php > echo 1820 % 16;
12
php > echo 6593741087309889539 % 16;
3
php > echo 5177331 % 16;
3

从上面的结果可以看出来,uid、订单号都能定位到相同的分表。

对一个2的n次幂的数num取模(2^n),本质就是num对应二进制的末尾n个bit的和取模。

代码实现

参考网上其它语言的版本,自己写了C和PHP版本的:

github上其它版本:

参考

1、Twitter-Snowflake,64位自增ID算法详解 - 漫漫路

https://www.lanindex.com/twitter-snowflake,64位自增id算法详解/

2、多key业务,数据库水平切分架构一次搞定

https://mp.weixin.qq.com/s/PCzRAZa9n4aJwHOX-kAhtA

Twitter-Snowflake:自增ID算法的更多相关文章

  1. Twitter分布式自增ID算法snowflake原理解析

    以JAVA为例 Twitter分布式自增ID算法snowflake,生成的是Long类型的id,一个Long类型占8个字节,每个字节占8比特,也就是说一个Long类型占64个比特(0和1). 那么一个 ...

  2. Twitter分布式自增ID算法snowflake原理解析(Long类型)

    Twitter分布式自增ID算法snowflake,生成的是Long类型的id,一个Long类型占8个字节,每个字节占8比特,也就是说一个Long类型占64个比特(0和1). 那么一个Long类型的6 ...

  3. snowflake自增ID算法 (PHP版)

    /** * SnowFlake ID Generator * Based on Twitter Snowflake to generate unique ID across multiple * da ...

  4. 详解Twitter开源分布式自增ID算法snowflake(附演算验证过程)

    详解Twitter开源分布式自增ID算法snowflake,附演算验证过程 2017年01月22日 14:44:40 url: http://blog.csdn.net/li396864285/art ...

  5. 分布式自增ID算法-Snowflake详解

    1.Snowflake简介 互联网快速发展的今天,分布式应用系统已经见怪不怪,在分布式系统中,我们需要各种各样的ID,既然是ID那么必然是要保证全局唯一,除此之外,不同当业务还需要不同的特性,比如像并 ...

  6. Twitter的分布式自增ID算法snowflake (Java版)

    概述 分布式系统中,有一些需要使用全局唯一ID的场景,这种时候为了防止ID冲突可以使用36位的UUID,但是UUID有一些缺点,首先他相对比较长,另外UUID一般是无序的. 有些时候我们希望能使用一种 ...

  7. Twitter的分布式自增ID算法snowflake

    snowflake 分布式场景下获取自增id git:https://github.com/twitter/snowflake 解读: http://www.cnblogs.com/relucent/ ...

  8. Twitter的分布式自增ID算法snowflake(雪花算法) - C#版

    概述 分布式系统中,有一些需要使用全局唯一ID的场景,这种时候为了防止ID冲突可以使用36位的UUID,但是UUID有一些缺点,首先他相对比较长,另外UUID一般是无序的.有些时候我们希望能使用一种简 ...

  9. 自增ID算法snowflake(雪花)

    在数据库主键设计上,比较常见的方法是采用自增ID(1开始,每次加1)和生成GUID.生成GUID的方式虽然简单,但是由于采用的是无意义的字符串,推测会在数据量增大时造成访问过慢,在基础互联网的系统设计 ...

随机推荐

  1. Day 15 文件打包与压缩

    1.什么是文件压缩? 将多个文件或目录合并成为一个特殊的文件.比如: 搬家...脑补画面 img. 2.为什么要对文件进行压缩? 当我们在传输大量的文件时,通常都会选择将该文件进行压缩,然后在进行传输 ...

  2. 基于RMAN搭建DataGuard,使用Broker管理DataGuard

    一.环境准备 1.数据库软件准备 (1).在主节点,安装单机数据库软件并创建数据库. (2).在备库, 安装单机数据库软件, 但是不创建数据库. 2.操作系统配置 在/etc/hosts下面配置主机名 ...

  3. java http/https后台上传服务器视频到接口

    没有代码得帖子都是耍流氓,废话不说,上代码 //上传视频    public static HttpEntity getMultiDefaultFileEntity(String pathlj,Str ...

  4. Spring boot使用log4j打印日志

    先将maven中spring-boot-starter的日志spring-boot-starter-logging去掉 <dependency> <groupId>org.sp ...

  5. 夯实Java基础系列10:深入理解Java中的异常体系

    目录 为什么要使用异常 异常基本定义 异常体系 初识异常 异常和错误 异常的处理方式 "不负责任"的throws 纠结的finally throw : JRE也使用的关键字 异常调 ...

  6. 【Python笔记】Python 基础语法

    Python 标识符 在 Python 里,标识符由字母.数字.下划线组成. 在 Python 中,所有标识符可以包括英文.数字以及下划线(_),但不能以数字开头. Python 中的标识符是区分大小 ...

  7. .net 更新access数据库 影响的行数为0

    在更新Access数据库的时候,明明传入的数据及参数类型都正确,但是一直更新不了,查看影响的行数一直为0 此原因为 C#操作Access数据库的时候更新的参数和条件参数要按照更新语句中的顺序进行设置, ...

  8. 报表统计——java实现查询某年某月每天数据,没数据补0

    一般图表绘制例如echarts等,返回数据格式都大同小异.重点是利用sql或者java实现数据格式的转型,接下来是关键部分: 1.前提:提供的工具方法——获取某月有多少天 //通过年份和月份确定该月的 ...

  9. 深度解密Go语言之反射

    目录 什么是反射 为什么要用反射 反射是如何实现的 types 和 interface 反射的基本函数 反射的三大定律 反射相关函数的使用 代码样例 未导出成员 反射的实际应用 json 序列化 De ...

  10. 免费申请 HTTPS 证书,开启全站 HTTPS

    作者:HelloGitHub-追梦人物 文中涉及的示例代码,已同步更新到 HelloGitHub-Team 仓库 HTTP 报文以明文形式传输,如果你的网站只支持 HTTP 协议,那么就有可能遭受到安 ...