分布式系统中生成全局唯一ID方案

本文主要介绍在一个分布式系统中, 如何去生成全局唯一的 ID。

前言

单纯的生成全局ID并不是什么难题,生成全局的 unique ID 要满足以下需求:

  • 保证生成的 ID 全局唯一
  • 今后数据在多个 Shards 之间迁移不会受到 ID 生成方式的限制
  • 生成的 ID 中最好能带上时间信息, 例如 ID 的前 k 位是 Timestamp, 这样能够直接通过对 ID 的前 k位的排序来对数据按时间排序
  • 生成的 ID 最好不大于 64 bits
  • 生成 ID 的速度有要求. 例如, 在一个高吞吐量的场景中, 需要每秒生成几万个 ID (Twitter 最新的峰值到达了 143,199
    Tweets/s, 也就是 10万+/秒)
  • 整个服务最好没有单点

问题描述

当用户量激增 系统架构演进到一定的阶段,常常会设计到分库分表,
例如根据id对用户表(t_user)进行分表,[0,999999]保存在t_user_0表,[1000000,1999999]保存在t_user_1表中,依次类推,怎么给这些用户生成全局的 unique ID?

全局ID产生的几种方式

1、数据库自增id

当服务使用的数据库只有单库单表时,可以利用数据库的auto_increment来生成全局唯一递增ID.

优势:

  • 简单,无需程序任何附加操作
  • 保持定长的增量
  • 在单表中能保持唯一性

劣势:

  • 高并发下性能不佳,主键产生的性能上限是数据库服务器单机的上限。
  • 水平扩展困难,在分布式数据库环境下,无法保证唯一性。

2、UUID

一般的编程语言中会自带UUID的实现,比如Java中UUID方式UUID.randomUUID().toString(),可以通过服务程序本地产生,ID的生成不依赖数据库的实现。

优势:

  • 本地生成ID,不需要进行远程调用。
  • 全局唯一不重复。
  • 水平扩展能力非常好。

劣势:

  • ID有128 bits,占用的空间较大,需要存成字符串类型,索引效率极低。
  • 生成的ID中没有带Timestamp,无法保证趋势递增

3、Flickr 的全局主键生成方案

flickr巧妙地使用了MySQL的自增ID,及replace into语法,十分简洁地实现了分片ID生成功能。详见 :http://code.flickr.net/2010/02/08/ticket-servers-distributed-unique-primary-keys-on-the-cheap/

比如创建64位的自增id:
首先,创建一个表:

CREATE TABLE `uid_sequence` (
  `id` bigint(20) unsigned NOT NULL auto_increment,
  `stub` char(1) NOT NULL default '',
  PRIMARY KEY  (`id`),
  UNIQUE KEY `stub` (`stub`)
) ENGINE=MyISAM;

SELECT * from uid_sequence 输出:
+-------------------+------+
| id | stub |
+-------------------+------+
| 72157623227190423 | a |

如果我需要一个全局的唯一的64位uid,则执行:

REPLACE INTO uid_sequence (stub) VALUES ('a');
SELECT LAST_INSERT_ID();

说明:

  • 用 REPLACE INTO 代替 INSERT INTO 的好处是避免表行数太大,还要另外定期清理。
  • stub 字段要设为唯一索引,这个 sequence 表只有一条纪录,但也可以同时为多张表生成全局主键,例如user_order_id。除非你需要表的主键是连续的,那么就另建一个 user_order_id_sequence 表。
  • 经过实际对比测试,使用 MyISAM 比 Innodb 有更高的性能。

这里flickr使用两台数据库(也可以更多)作为自增序列生成,通过这两台机器做主备和负载均衡。

TicketServer1:
auto-increment-increment = 2
auto-increment-offset = 1

TicketServer2:
auto-increment-increment = 2
auto-increment-offset = 2

优点:

  • 简单可靠。

缺点:

  • id只是一个ID,没有带入时间,shardingId等信息。

4、Twitter Snowflake

twitter利用zookeeper实现了一个全局ID生成的服务Snowflake:https://github.com/twitter/snowflake

Snowflake 生成的 unique ID 的组成 (由高位到低位):

  • 41 bits: Timestamp (毫秒级)
  • 10 bits: 节点 ID (datacenter ID 5 bits + worker ID 5 bits)
  • 12 bits: sequence number

一共 63 bits (最高位是 0)

unique ID 生成过程:

  • 10 bits 的机器号, 在 ID 分配 Worker 启动的时候,从一个 Zookeeper 集群获取 (保证所有的 Worker 不会有重复的机器号);
  • 41 bits 的 Timestamp: 每次要生成一个新 ID 的时候,都会获取一下当前的 Timestamp, 然后分两种情况生成 sequence number;
  • 如果当前的 Timestamp 和前一个已生成 ID 的 Timestamp 相同 (在同一毫秒中),就用前一个 ID 的 sequence number + 1 作为新的 sequence number (12 bits); 如果本毫秒内的所有 ID 用完,等到下一毫秒继续 (这个等待过程中, 不能分配出新的 ID);
  • 如果当前的 Timestamp 比前一个 ID 的 Timestamp 大, 随机生成一个初始 sequence number (12bits) 作为本毫秒内的第一个 sequence number;

整个过程中只是在 Worker 启动的时候会对外部有依赖 (需要从 Zookeeper 获取 Worker 号) 之后就可以独立工作了,做到了去中心化。

Twitter官方提供的snowflake是使用Scala编写的,本人使用Java语言重写了,链接:https://github.com/TiFG/id-generator/tree/master/snowflake-java

5、Instagram的做法

instagram参考了flickr的方案,再结合twitter的经验,利用Postgre数据库的特性,实现了一个更简单可靠的ID生成服务。链接:http://instagram-engineering.tumblr.com/post/10853187575/sharding-ids-at-instagram


instagram unique ID 的组成:

  • 41 bits: Timestamp (毫秒)
  • 13 bits: 每个 logic Shard 的代号 (最大支持 8 x 1024 个 logic Shards)
  • 10 bits: sequence number; 每个 Shard 每毫秒最多可以生成 1024 个 ID

以instagram举的例子为说明:
假定时间是September 9th, 2011, at 5:00pm,则毫秒数是1387263000(直接使用系统得到的从1970年开始的毫秒数)。那么先把时间数据放到ID里:
id = 1387263000 << (64-41)
再把分片ID放到时间里,假定用户ID是31341,有2000个逻辑分片,则分片ID是31341 % 2000 -> 1341:
id |= 1341 << (64-41-13)
最后,把自增序列放ID里,假定前一个序列是5000,则新的序列是5001:
id |= (5001 % 1024)
这样就得到了一个全局的分片ID。

我们可以通过INSERT语句的RETURNING 关键字,将ID返回给应用程序;
这里是the PL/PGSQL的完整例子(例子的schema :insta5):

CREATE OR REPLACE FUNCTION insta5.next_id(OUT result bigint) AS $$ 
DECLARE 
    our_epoch bigint := 1314220021721; 
    seq_id bigint; 
    now_millis bigint; 
    shard_id int := 5; 
BEGIN 
    SELECT nextval('insta5.table_id_seq') %% 1024 INTO seq_id;

    SELECT FLOOR(EXTRACT(EPOCH FROM clock_timestamp()) * 1000) INTO now_millis; 
    result := (now_millis - our_epoch) << 23; 
    result := result | (shard_id << 10); 
    result := result | (seq_id); 
END; 
$$ LANGUAGE PLPGSQL; 
And when creating the table, we do:

CREATE TABLE insta5.our_table ( 
    "id" bigint NOT NULL DEFAULT insta5.next_id(), 
    ...rest of table schema... 
)

6、其他方案

例如:MongoDB的ObjectId,采用12个字节的长度,并且将时间戳进行编码。链接:https://docs.mongodb.com/manual/reference/method/ObjectId/

参考资料

分布式唯一主键生成器

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,456评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,370评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,337评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,583评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,596评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,572评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,936评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,595评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,850评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,601评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,685评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,371评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,951评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,934评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,167评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,636评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,411评论 2 342

推荐阅读更多精彩内容

  • 本文主要介绍在一个分布式系统中, 怎么样生成全局唯一的 ID 一, 问题描述 在分布式系统存在多个 Shard 的...
    hanayona阅读 1,984评论 0 5
  • 序 本文主要来聊聊分布式id的生成方案。 目标 业务系统需要什么样的ID生成器中提出了几点目标: 唯一性 时间相关...
    go4it阅读 615评论 0 3
  • 数据在分片时,典型的是分库分表,就有一个全局ID生成的问题。 单纯的生成全局ID并不是什么难题,但是生成的ID通常...
    小楼听烟雨阅读 4,311评论 0 8
  • 在分布式系统中,经常需要使用全局唯一ID查找对应的数据。产生这种ID需要保证系统全局唯一,而且要高性能以及占用相对...
    whthomas阅读 13,877评论 2 21
  • 大千世界, 芸芸众生, 选拔人才, 方法多多。 不同的人, 方法迥异, 老师选人, 出类拔萃, 老板选人, 不选闲...
    旖旎i阅读 375评论 2 10