分布式系统中生成全局唯一ID方案
一.前言
单纯的生成全局ID并不是什么难题,生成全局的 unique ID 要满足以下需求:
1.保证生成的 ID 全局唯一
2.今后数据在多个 Shards 之间迁移不会受到 ID 生成方式的限制
3.生成的 ID 中最好能带上时间信息, 例如 ID 的前 k 位是 Timestamp, 这样能够直接通过对 ID 的前 k位的排序来对数据按时间排序
4.生成的 ID 最好不大于 64 bits
5.生成 ID 的速度有要求. 例如, 在一个高吞吐量的场景中, 需要每秒生成几万个 ID (Twitter 最新的峰值到达了143,199Tweets/s, 也就是 10万+/秒)
6.整个服务最好没有单点
二.问题描述
当用户量激增 系统架构演进到一定的阶段,常常会设计到分库分表,例如根据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 号) 之后就可以独立工作了,做到了去中心化。
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/
posted on 2017-07-06 00:35 bijian1013 阅读(1009) 评论(0) 编辑 收藏 举报