分布式 ID 解决方案之美团 Leaf
分布式 ID
在庞大复杂的分布式系统中,通常需要对海量数据进行唯一标识,随着数据日渐增长,对数据分库分表以后需要有一个唯一 ID 来标识一条数据,而数据库的自增 ID 显然不能满足需求,此时就需要有一个能够生成全局唯一 ID 的系统,需要满足以下条件:
- 全局唯一性:最基本的要求就是不能出现重复的 ID。
- 递增:保证下一个 ID 一定大于上一个 ID。
- 信息安全:如果 ID 是连续的,用户就可以按照顺序进行恶意爬取数据,所以 ID 生成无规则。
上述的 2 和 3 点需求是互斥的,无法使用同一个方案满足。
解决方案
数据库生成
以 MySQL 为例,利用给字段设置 auto_increment_increment
和 auto_increment_offset
来实现 ID 自增。每次业务可以使用下列 SQL 进行读写得到 ID:
begin;
REPLACE INTO Tickets64 (stub) VALUES ('a');
SELECT LAST_INSERT_ID();
commit;
- 优点:使用非常简单,ID 单调递增。
- 缺点:非常依赖数据库,当数据库异常时则整个系统不可用。
UUID
- 优点:本地生成,没有网络消耗,性能高。
- 缺点:过长不易于存储;造成信息不安全,基于 MAC 地址生成可能会造成 MAC 地址泄露。
Snowflake
Snowflake
(雪花算法)是由 Twitter
发布的分布式 ID 生成算法,它能够保证不同进程主键的不重复性,以及相同进程主键的有序性。它是通过时间位实现单调递增,且各个服务器如果都做了时间同步,那么生成的 ID 可以认为是总体有序的。
Leaf
Leaf
最早期需求是各个业务线的订单 ID 生成需求。在美团早期,有的业务直接通过数据库自增的方式生成 ID,有的业务通过 Redis 缓存来生成 ID,也有的业务直接用 UUID 这种方式来生成 ID。以上的方式各自有各自的问题,因此决定实现一套分布式 ID 生成服务来满足需求。
Leaf-segment
Leaf-segment
数据库方案,在使用数据库的方案上,做了以下改变:
- 原方案每次获取 ID 都得读写一次数据库,造成数据库压力大。改为利用 proxy server 批量获取一个 segment 号段,用完之后再去数据库获取新的号段,大大减轻数据库的压力。
- 各个业务不同的发号需求用 biz_tag 字段来区分,每个 big_tag 的 ID 获取相互隔离互不影响。
优点:
- Leaf 服务可以很方便的线性扩展,性能完全能够支撑大多数业务场景。
- ID 是趋势递增的 8 字节的 64 位数字,满足数据库存储的主键要求。
- 容灾性高:Leaf 服务内部有号段缓存,即使数据库宕机,短时间内仍能可以正常对外提供服务。
- 可以自定义 max_id 大小。
缺点:
- ID 不够随机,能够泄露发号数量的信息,不安全。
- 数据库宕机可能会造成整个系统不可用。
Leaf-snowflake
该方案完全沿用 snowflake
方案设计。对于 workerID
的分配,当服务器集群数量较小的情况下,完全可以手动配置。服务规模较大时,动手配置成本太高,所以使用 Zookeeper
持久顺序节点的特性自动对 snowflake
节点配置。
启动步骤如下:
- 启动
Leaf-snowflake
服务,连接Zookeeper
,在leaf_forever
父节点下检查自己是否已经注册过。 - 如果有注册过直接取回自己的
workerID
,启动服务。 - 如果没有注册过,就在该父节点下面创建一个持久顺序节点,创建成功后取回顺序号当做自己的
workerID
号。
使用 Docker Compose 部署 Leaf
克隆项目
$ git clone https://github.com/antoniopeng/leaf.git
$ cd leaf
$ mvn clean install -DskipTests
构建
$ cd leaf-docker
$ chmod +x build.sh
$ ./build.sh
启动
$ docker-compose up -d
测试
生成地址:http://localhost:8080/api/snowflake/get/test
$ curl http://localhost:8080/api/snowflake/get/test
文章作者:彭超
版权声明:本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 彭超 | Blog!
最新文章
- php 封装 知识点
- Web编程基础--HTML、CSS、JavaScript 学习之课程作业“仿360极速浏览器新标签页”
- Win7 64位命令行编译cuda及设置Windows显卡响应时间
- 使用CSS设置行间距,字间距.
- JavaScipt 源码解析 css选择器
- [Asp.net]c#中的斜杠和反斜杠
- LaTex学习笔记
- java性能优化策略
- MVC4,MVC3,VS2012+ entity framework Migration from Sqlserver to Mysql
- 文件系统与linux相关知识点
- [C#]使用Redis来存储键值对(Key-Value Pair)
- Hexo+Github博客最简教程-Dockerfile自动搭建
- java 8 双冒号运算符
- HDU 3308 LCIS (经典区间合并)【线段树】
- Elasticsearch 快速入门教程
- Unity3d粒子系统详解
- Python 变量比较
- 利用原生js的Dom操作实现简单的ToDoList的效果
- AndroidStudio2.2.2 打开ddms快捷键
- HTML5--(2)属性选择器+结构性伪类+伪类
热门文章
- SqlServer2016 startengine错误的解决方式整理
- python黑帽子之tcp服务端
- script写在head与写在body中的区别
- 使用Xmanager连接linux,操作“xhost +”时出现类似“xhost: unable to open display ";192.168.1.1811:1.0"; ”问题的解决
- return zero,or ,return non-zero
- Linux 文件类型 ,文件权限
- Flutter 中渐变的高级用法
- 这些 CSS 命名规范将省下你大把调试时间
- centos-docker安装及基本使用
- Python爬虫教程(16行代码爬百度)