分布式 ID 发号器方案

135 阅读3分钟

一、需求

1.1 功能需求

  1. ID 全局唯一;
  2. ID 趋势递增(不保证全局单调递增);
  3. 安全,ID生成不可猜测,避免爬虫抓取;

1.2 非功能需求

  1. TPS 万级别
  2. 响应时间 ms 级别(平均 TP 线,TP999 线)
  3. 可用性5 个 9(每年 5 分钟不可用时间)

二、方案

2.1 UUID

  1. ID 由128 BIT位,16 个字节,32 个 16 进制字符组成;

  2. 字符过长,作为 MySQL 主键索引不推荐(二级索引中都要引用,空间浪费);

  3. ID 无序,数据库读写位置是跳跃的,性能不好

All indexes other than the clustered index are known as secondary indexes. In InnoDB, each record in a secondary index contains the primary key columns for the row, as well as the columns specified for the secondary index. InnoDB uses this primary key value to search for the row in the clustered index.* If the primary key is long, the secondary indexes use more space, so it is advantageous to have a short primary key***.**

2.2 根据 ID位移和步长预分配

  1. 方案 1 缺点:数据库服务器单点失败SPOF(master-master 双主不允许,双向同步存在 ID重复) ;
  2. 方案 2 通过不同服务器对应不同auto_increment_offset解决了数据库单点问题,但是服务器扩缩容方案会特别麻烦;
  3. 方案 1 和 2 都有 DB 操作性能问题,每次获取 ID都要读写 DB;

code.flickr.net/2010/02/08/…

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

begin;
REPLACE INTO Tickets64 (stub) VALUES ('a');
SELECT LAST_INSERT_ID();
commit;

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

TicketServer2:
auto-increment-increment = 

2.3 美团 Leaf

There are no two identical leaves in the world

Leaf——美团点评分布式ID生成系统

Leaf:美团分布式ID生成服务开源

  1. Leaf-segment
  2. Leaf-snowflake

2.3.1 Leaf-segment

  1. ID 为64 BIT 位数,依赖 DB;
  2. 预分配号段给 Leaf Server,不用每取一次 ID更新一次 DB;更新 max id 时需要更新 DB;
  3. 采用双缓冲区 segment 解决号段分配完成时重新分配号段时更新 DB 带来的性能抖动和延迟问题,当前号段使用过程中(使用了 10%)另外一个线程提前准备好下一个号段,当前号段使用完后做一下切换就行;
  4. DB 高可用依赖主从同步一致性(半同步方式);
  5. 不足的地方是 ID趋势递增(全局不有序,Leaf Server 上有序),容易大致猜测下一个 ID和ID 数量,对于订单类业务不适用;

Begin
UPDATE table SET max_id=max_id+step WHERE biz_tag=xxx
SELECT tag, max_id, step FROM table WHERE biz_tag=xxx
Commit

2.3.2 Leaf-snowflake

  1. 64 位数(bit)的 ID,趋势递增;
  2. ZooKeeper 中保存 workerId;弱依赖ZK,本地文件中缓存 workerId;
  3. 不足:时钟回拨,时间校验不通过时摘除该主机;
  4. 猜测订单等 ID敏感业务会采用类似方案的变种;

  1. 若写过,则用自身系统时间与leaf_forever/self节点记录时间做比较,若小于leafforever/{self}节点记录时间做比较,若小于leaf_forever/{self}时间则认为机器时间发生了大步长回拨,服务启动失败并报警;
  2. 若未写过,判断机器当前时间和 temporary 节点下主机时间平均值之差是否在阈值内;
  3. 每隔一段时间(3s)上报自身系统时间写入leaf_forever/${self};