分布式唯一ID的几种生成方案
前言
在互联网的业务系统中,涉及到各种各样的ID,如在支付系统中就会有支付ID、退款ID等。那一般生成ID都有哪些解决方案呢?特别是在复杂的分布式系统业务场景中,我们应该采用哪种适合自己的解决方案是十分重要的。下面我们一一来列举一下,不一定全部适合,这些解决方案仅供你参考,或许对你有用。
正文
分布式ID的特性
- 唯一性:确保生成的ID是全网唯一的。
- 有序递增性:确保生成的ID是对于某个用户或者业务是按一定的数字有序递增的。
- 高可用性:确保任何时候都能正确的生成ID。
- 带时间:ID里面包含时间,一眼扫过去就知道哪天的交易。
分布式ID的生成方案
1. UUID
算法的核心思想是结合机器的网卡、当地时间、一个随记数来生成UUID。
- 优点:本地生成,生成简单,性能好,没有高可用风险
- 缺点:长度过长,存储冗余,且无序不可读,查询效率低
2. 数据库自增ID
使用数据库的id自增策略,如 MySQL 的 auto_increment。并且可以使用两台数据库分别设置不同步长,生成不重复ID的策略来实现高可用。
- 优点:数据库生成的ID绝对有序,高可用实现方式简单
- 缺点:需要独立部署数据库实例,成本高,有性能瓶颈
3. 批量生成ID
一次按需批量生成多个ID,每次生成都需要访问数据库,将数据库修改为最大的ID值,并在内存中记录当前值及最大值。
- 优点:避免了每次生成ID都要访问数据库并带来压力,提高性能
- 缺点:属于本地生成策略,存在单点故障,服务重启造成ID不连续
4. Redis生成ID
Redis的所有命令操作都是单线程的,本身提供像 incr 和 increby 这样的自增原子命令,所以能保证生成的 ID 肯定是唯一有序的。
优点:不依赖于数据库,灵活方便,且性能优于数据库;数字ID天然排序,对分页或者需要排序的结果很有帮助。
缺点:如果系统中没有Redis,还需要引入新的组件,增加系统复杂度;需要编码和配置的工作量比较大。
考虑到单节点的性能瓶颈,可以使用 Redis 集群来获取更高的吞吐量。假如一个集群中有5台 Redis。可以初始化每台 Redis 的值分别是1, 2, 3, 4, 5,然后步长都是 5。各个 Redis 生成的 ID 为:
A:1, 6, 11, 16, 21
B:2, 7, 12, 17, 22
C:3, 8, 13, 18, 23
D:4, 9, 14, 19, 24
E:5, 10, 15, 20, 25
复制代码
随便负载到哪个机确定好,未来很难做修改。步长和初始值一定需要事先确定。使用 Redis 集群也可以方式单点故障的问题。
另外,比较适合使用 Redis 来生成每天从0开始的流水号。比如订单号 = 日期 + 当日自增长号。可以每天在 Redis 中生成一个 Key ,使用 INCR 进行累加。
5. Twitter的snowflake算法
Twitter 利用 zookeeper 实现了一个全局ID生成的服务 Snowflake:github.com/twitter/sno…
如上图的所示,Twitter 的 Snowflake 算法由下面几部分组成:
- 1位符号位:
由于 long 类型在 java 中带符号的,最高位为符号位,正数为 0,负数为 1,且实际系统中所使用的ID一般都是正数,所以最高位为 0。
- 41位时间戳(毫秒级):
需要注意的是此处的 41 位时间戳并非存储当前时间的时间戳,而是存储时间戳的差值(当前时间戳 - 起始时间戳),这里的起始时间戳一般是ID生成器开始使用的时间戳,由程序来指定,所以41位毫秒时间戳最多可以使用 (1 << 41) / (1000x60x60x24x365) = 69年。
- 10位数据机器位:
包括5位数据标识位和5位机器标识位,这10位决定了分布式系统中最多可以部署 1 << 10 = 1024 s个节点。超过这个数量,生成的ID就有可能会冲突。
- 12位毫秒内的序列:
这 12 位计数支持每个节点每毫秒(同一台机器,同一时刻)最多生成 1 << 12 = 4096个ID
加起来刚好64位,为一个Long型。
- 优点:高性能,低延迟,按时间有序,一般不会造成ID碰撞
- 缺点:需要独立的开发和部署,依赖于机器的时钟
简单实现
public class IdWorker {
/**
* 起始时间戳 2017-04-01
*/
private final long epoch = 1491004800000L;
/**
* 机器ID所占的位数
*/
private final long workerIdBits = 5L;
/**
* 数据标识ID所占的位数
*/
private final long dataCenterIdBits = 5L;
/**
* 支持的最大机器ID,结果是31
*/
private final long maxWorkerId = ~(-1L << workerIdBits);
/**
* 支持的最大数据标识ID,结果是31
*/
private final long maxDataCenterId = ~(-1 << dataCenterIdBits);
/**
* 毫秒内序列在id中所占的位数
*/
private final long sequenceBits = 12L;
/**
* 机器ID向左移12位
*/
private final long workerIdShift = sequenceBits;
/**
* 数据标识ID向左移17(12+5)位
*/
private final long dataCenterIdShift = sequenceBits + workerIdBits;
/**
* 时间戳向左移22(12+5+5)位
*/
private final long timestampShift = sequenceBits + workerIdBits + dataCenterIdBits;
/**
* 生成序列的掩码,这里为4095 (0b111111111111=0xfff=4095)
*/
private final long sequenceMask = ~(-1L << sequenceBits);
/**
* 数据标识ID(0~31)
*/
private long dataCenterId;
/**
* 机器ID(0~31)
*/
private long workerId;
/**
* 毫秒内序列(0~4095)
*/
private long sequence;
/**
* 上次生成ID的时间戳
*/
private long lastTimestamp = -1L;
public IdWorker(long dataCenterId, long workerId) {
if (dataCenterId > maxDataCenterId || dataCenterId < 0) {
throw new IllegalArgumentException(String.format("dataCenterId can't be greater than %d or less than 0", maxDataCenterId));
}
if (workerId > maxWorkerId || workerId < 0) {
throw new IllegalArgumentException(String.format("worker Id can't be greater than %d or less than 0", maxWorkerId));
}
this.dataCenterId = dataCenterId;
this.workerId = workerId;
}
/**
* 获得下一个ID (该方法是线程安全的)
* @return snowflakeId
*/
public synchronized long nextId() {
long timestamp = timeGen();
//如果当前时间小于上一次ID生成的时间戳,说明系统时钟回退过,这个时候应当抛出异常
if (timestamp < lastTimestamp) {
throw new RuntimeException(String.format("Clock moved backwards. Refusing to generate id for %d milliseconds", lastTimestamp - timestamp));
}
//如果是同一时间生成的,则进行毫秒内序列
if (timestamp == lastTimestamp) {
sequence = (sequence + 1) & sequenceMask;
//毫秒内序列溢出
if (sequence == 0) {
//阻塞到下一个毫秒,获得新的时间戳
timestamp = nextMillis(lastTimestamp);
}
} else {//时间戳改变,毫秒内序列重置
sequence = 0L;
}
lastTimestamp = timestamp;
//移位并通过按位或运算拼到一起组成64位的ID
return ((timestamp - epoch) << timestampShift) |
(dataCenterId << dataCenterIdShift) |
(workerId << workerIdShift) |
sequence;
}
/**
* 返回以毫秒为单位的当前时间
* @return 当前时间(毫秒)
*/
protected long timeGen() {
return System.currentTimeMillis();
}
/**
* 阻塞到下一个毫秒,直到获得新的时间戳
* @param lastTimestamp 上次生成ID的时间截
* @return 当前时间戳
*/
protected long nextMillis(long lastTimestamp) {
long timestamp = timeGen();
while (timestamp <= lastTimestamp) {
timestamp = lastTimestamp;
}
return timestamp;
}
}
6. 百度UidGenerator
UidGenerator是百度开源的分布式ID生成器,基于于snowflake算法的实现,看起来感觉还行。不过,国内开源的项目维护性真是担忧。
具体可以参考官网说明:github.com/baidu/uid-g…
7. 美团Leaf
Leaf 是美团开源的分布式ID生成器,能保证全局唯一性、趋势递增、单调递增、信息安全,里面也提到了几种分布式方案的对比,但也需要依赖关系数据库、Zookeeper等中间件。
具体可以参考官网说明:tech.meituan.com/MT_Leaf.htm…
小结
这篇文章和大家分享了全局id生成服务的几种常用方案,同时对比了各自的优缺点和适用场景。在实际工作中,大家可以结合自身业务和系统架构体系进行合理选型。
作者:陌上轩客
链接:https://juejin.im/post/5b3a23746fb9a024e15cad79
来源:掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
分布式唯一ID的几种生成方案的更多相关文章
- 分布式全局ID的几种生成方案
前言 在互联网的业务系统中,涉及到各种各样的ID,如在支付系统中就会有支付ID.退款ID等. 那一般生成ID都有哪些解决方案呢?特别是在复杂的分布式系统业务场景中,我们应该采用哪种适合自己的解决方案是 ...
- 分布式唯一ID生成方案是什么样的?(转)
一.前言 分布式系统中我们会对一些数据量大的业务进行分拆,如:用户表,订单表.因为数据量巨大一张表无法承接,就会对其进行分库分表. 但一旦涉及到分库分表,就会引申出分布式系统中唯一主键ID的生成问题, ...
- 一线大厂的分布式唯一ID生成方案是什么样的?
本人免费整理了Java高级资料,涵盖了Java.Redis.MongoDB.MySQL.Zookeeper.Spring Cloud.Dubbo高并发分布式等教程,一共30G,需要自己领取.传送门:h ...
- 分布式唯一ID的生成方案
分布式ID的特性 全局唯一 不能出现重复的ID,这是最基本的要求. 递增 有利于关系数据库索引性能. 高可用 既然是服务于分布式系统,为多个服务提供ID服务,访问压力一定很大,所以需要保证高可用. 信 ...
- 分布式唯一ID生成方案选型!详细解析雪花算法Snowflake
分布式唯一ID 使用RocketMQ时,需要使用到分布式唯一ID 消息可能会发生重复,所以要在消费端做幂等性,为了达到业务的幂等性,生产者必须要有一个唯一ID, 需要满足以下条件: 同一业务场景要全局 ...
- 分布式唯一ID生成算法-雪花算法
在我们的工作中,数据库某些表的字段会用到唯一的,趋势递增的订单编号,我们将介绍两种方法,一种是传统的采用随机数生成的方式,另外一种是采用当前比较流行的“分布式唯一ID生成算法-雪花算法”来实现. 一. ...
- 开源项目|Go 开发的一款分布式唯一 ID 生成系统
原文连接: 开源项目|Go 开发的一款分布式唯一 ID 生成系统 今天跟大家介绍一个开源项目:id-maker,主要功能是用来在分布式环境下生成唯一 ID.上周停更了一周,也是用来开发和测试这个项目的 ...
- php 生成唯一id的几种解决方法
php 生成唯一id的几种解决方法 网上查了下,有很多的方法 1.md5(time() . mt_rand(1,1000000)); 这种方法有一定的概率会出现重复 2.php内置函数uniqid ...
- 分布式唯一id:snowflake算法思考
匠心零度 转载请注明原创出处,谢谢! 缘起 为什么会突然谈到分布式唯一id呢?原因是最近在准备使用RocketMQ,看看官网介绍: 一句话,消息可能会重复,所以消费端需要做幂等.为什么消息会重复后续R ...
随机推荐
- STS的安装教程-鹏鹏
STS全称Spring Tools Suite. 简介:Spring Tools Suite (STS)其实就是一个被包装过的Eclipse,主要用于快速的开发Spring项目,我们不用再去编辑繁琐的 ...
- 解决文字和text-decoration:underline下划线重叠问题
一.text-decoration:underline下划线的问题 CSS text-decoration:underline可以给内联文本增加下划线,但是,如果对细节要求较高,就会发现,下划线经常会 ...
- C#(简单递归)和实现IComparable接口
递归: using System; using System.Collections.Generic; using System.Linq; using System.Text; using Syst ...
- html基础-a标签-img标签-绝对/相对路径(3)
美好的星期六,今天多写一点,争取早点写js这个有点小无聊. 一.先来讲点网页之间的跳转 (1).<a href=""></a> href="这里 ...
- 解决 login.live.com onedrive.live.com 等微软国外网站打不开问题
下面就分享一下通过更改HOSTS文件的方式打开onedrive网页版的方法. C:\Windows\System32\drivers\etc目录下的hosts文件把它复制到D盘,再复制一份放到桌面上. ...
- 从CVE-2018-1273看漏洞分析
漏洞分析的边界 漏洞分析最应该关注的是漏洞相关的代码,至于其余的代码可以通过关键位置下断点,来理解大概功能. 其中最关键的就是了解数据流,找到离漏洞位置最近的 原始数据 经过的位置,然后开始往下分析, ...
- 我的blog第一天
今天是2017年6月13号,一个很平凡的日子,但是对我来说意义非凡.这是我开通博客的第一天,这是我写的第一篇文章. 先自我介绍一下,本人邢卜,河北石家庄人,生于89年6月,说到这我马上就要过生日了!嘿 ...
- ES6高频面试题目整理
本篇文章是根据以下内容进行的总结 1.https://segmentfault.com/a/1190000011344301 2.http://www.bslxx.com/a/mianshiti/ti ...
- The directory '/home/stone/.cache/pip/http' or its parent directory is not owned by the current user and the cache has been disabled. Please check the permissions and owner of that directory. If execu
使用sudo pip install ......的时候出现下面一段黄色的代码: The directory '/home/stone/.cache/pip/http' or its parent d ...
- Paxos可容错的一致性协议
一致性问题要求多个process对一个值达成一致.基于消息传递的分布式系统中,在不考虑消息篡改等拜占庭错误的情况下,Paxos可以解决在进程退出,消息延迟,丢失,重复等异常发生的环境中对某个值达成一致 ...