全局唯一ID设计
在分布式系统中,经常需要使用全局唯一ID查找对应的数据。产生这种ID需要保证系统全局唯一,而且要高性能以及占用相对较少的空间。
全局唯一ID在数据库中一般会被设成主键,这样为了保证数据插入时索引的快速建立,还需要保持一个有序的趋势。
这样全局唯一ID就需要保证这两个需求:
- 全局唯一
- 趋势有序
全局ID产生的几种方式
数据库自增
当服务使用的数据库只有单库单表时,可以利用数据库的auto_increment来生成全局唯一递增ID.
优势:
- 简单,无需程序任何附加操作
- 保持定长的增量
- 在单表中能保持唯一性
劣势:
- 高并发下性能不佳,主键产生的性能上限是数据库服务器单机的上限。
- 水平扩展困难,在分布式数据库环境下,无法保证唯一性。
UUID
一般的语言中会自带UUID的实现,比如Java中UUID方式UUID.randomUUID().toString(),可以通过服务程序本地产生,ID的生成不依赖数据库的实现。
优势:
- 本地生成ID,不需要进行远程调用。
- 全局唯一不重复。
- 水平扩展能力非常好。
劣势:
- ID有128 bits,占用的空间较大,需要存成字符串类型,索引效率极低。
- 生成的ID中没有带Timestamp,无法保证趋势递增
Twitter Snowflake
snowflake是twitter开源的分布式ID生成算法,其核心思想是:产生一个long型的ID,使用其中41bit作为毫秒数,10bit作为机器编号,12bit作为毫秒内序列号。这个算法单机每秒内理论上最多可以生成1000*(2^12)个,也就是大约400W的ID,完全能满足业务的需求。
根据snowflake算法的思想,我们可以根据自己的业务场景,产生自己的全局唯一ID。因为Java中long类型的长度是64bits,所以我们设计的ID需要控制在64bits。
比如我们设计的ID包含以下信息:
| 41 bits: Timestamp | 3 bits: 区域 | 10 bits: 机器编号 | 10 bits: 序列号 |
产生唯一ID的Java代码:
import java.security.SecureRandom;
/**
* 自定义 ID 生成器
* ID 生成规则: ID长达 64 bits
*
* | 41 bits: Timestamp (毫秒) | 3 bits: 区域(机房) | 10 bits: 机器编号 | 10 bits: 序列号 |
*/
public class CustomUUID {
// 基准时间
private long twepoch = 1288834974657L; //Thu, 04 Nov 2010 01:42:54 GMT
// 区域标志位数
private final static long regionIdBits = 3L;
// 机器标识位数
private final static long workerIdBits = 10L;
// 序列号识位数
private final static long sequenceBits = 10L;
// 区域标志ID最大值
private final static long maxRegionId = -1L ^ (-1L << regionIdBits);
// 机器ID最大值
private final static long maxWorkerId = -1L ^ (-1L << workerIdBits);
// 序列号ID最大值
private final static long sequenceMask = -1L ^ (-1L << sequenceBits);
// 机器ID偏左移10位
private final static long workerIdShift = sequenceBits;
// 业务ID偏左移20位
private final static long regionIdShift = sequenceBits + workerIdBits;
// 时间毫秒左移23位
private final static long timestampLeftShift = sequenceBits + workerIdBits + regionIdBits;
private static long lastTimestamp = -1L;
private long sequence = 0L;
private final long workerId;
private final long regionId;
public CustomUUID(long workerId, long regionId) {
// 如果超出范围就抛出异常
if (workerId > maxWorkerId || workerId < 0) {
throw new IllegalArgumentException("worker Id can't be greater than %d or less than 0");
}
if (regionId > maxRegionId || regionId < 0) {
throw new IllegalArgumentException("datacenter Id can't be greater than %d or less than 0");
}
this.workerId = workerId;
this.regionId = regionId;
}
public CustomUUID(long workerId) {
// 如果超出范围就抛出异常
if (workerId > maxWorkerId || workerId < 0) {
throw new IllegalArgumentException("worker Id can't be greater than %d or less than 0");
}
this.workerId = workerId;
this.regionId = 0;
}
public long generate() {
return this.nextId(false, 0);
}
/**
* 实际产生代码的
*
* @param isPadding
* @param busId
* @return
*/
private synchronized long nextId(boolean isPadding, long busId) {
long timestamp = timeGen();
long paddingnum = regionId;
if (isPadding) {
paddingnum = busId;
}
if (timestamp < lastTimestamp) {
try {
throw new Exception("Clock moved backwards. Refusing to generate id for " + (lastTimestamp - timestamp) + " milliseconds");
} catch (Exception e) {
e.printStackTrace();
}
}
//如果上次生成时间和当前时间相同,在同一毫秒内
if (lastTimestamp == timestamp) {
//sequence自增,因为sequence只有10bit,所以和sequenceMask相与一下,去掉高位
sequence = (sequence + 1) & sequenceMask;
//判断是否溢出,也就是每毫秒内超过1024,当为1024时,与sequenceMask相与,sequence就等于0
if (sequence == 0) {
//自旋等待到下一毫秒
timestamp = tailNextMillis(lastTimestamp);
}
} else {
// 如果和上次生成时间不同,重置sequence,就是下一毫秒开始,sequence计数重新从0开始累加,
// 为了保证尾数随机性更大一些,最后一位设置一个随机数
sequence = new SecureRandom().nextInt(10);
}
lastTimestamp = timestamp;
return ((timestamp - twepoch) << timestampLeftShift) | (paddingnum << regionIdShift) | (workerId << workerIdShift) | sequence;
}
// 防止产生的时间比之前的时间还要小(由于NTP回拨等问题),保持增量的趋势.
private long tailNextMillis(final long lastTimestamp) {
long timestamp = this.timeGen();
while (timestamp <= lastTimestamp) {
timestamp = this.timeGen();
}
return timestamp;
}
// 获取当前的时间戳
protected long timeGen() {
return System.currentTimeMillis();
}
}
使用自定义的这种方法需要注意的几点:
- 为了保持增长的趋势,要避免有些服务器的时间早,有些服务器的时间晚,需要控制好所有服务器的时间,而且要避免
NTP时间服务器回拨服务器的时间。 - 在跨毫秒时,序列号总是归0,会使得序列号为0的ID比较多,导致生成的ID取模后不均匀,所以序列号不是每次都归0,而是归一个0到9的随机数。
- 使用这个
CustomUUID类,最好在一个系统中能保持单例模式运行。
全局唯一ID设计的更多相关文章
- 如何在高并发分布式系统中生成全局唯一Id
月整理出来,有兴趣的园友可以关注下我的博客. 分享原由,最近公司用到,并且在找最合适的方案,希望大家多参与讨论和提出新方案.我和我的小伙伴们也讨论了这个主题,我受益匪浅啊…… 博文示例: 1. ...
- 高并发分布式系统中生成全局唯一Id汇总
数据在分片时,典型的是分库分表,就有一个全局ID生成的问题.单纯的生成全局ID并不是什么难题,但是生成的ID通常要满足分片的一些要求: 1 不能有单点故障. 2 以时间为序,或者ID里包含时间 ...
- 如何在高并发分布式系统中生成全局唯一Id(转)
http://www.cnblogs.com/heyuquan/p/global-guid-identity-maxId.html 又一个多月没冒泡了,其实最近学了些东西,但是没有安排时间整理成博文, ...
- 游戏服务器生成全局唯一ID的几种方法
在服务器系统开发时,为了适应数据大并发的请求,我们往往需要对数据进行异步存储,特别是在做分布式系统时,这个时候就不能等待插入数据库返回了取自动id了,而是需要在插入数据库之前生成一个全局的唯一id,使 ...
- (转)如何在高并发分布式系统中生成全局唯一Id
又一个多月没冒泡了,其实最近学了些东西,但是没有安排时间整理成博文,后续再奉上.最近还写了一个发邮件的组件以及性能测试请看 <NET开发邮件发送功能的全面教程(含邮件组件源码)> ,还弄了 ...
- 全局唯一ID发号器的几个思路
标识(ID / Identifier)是无处不在的,生成标识的主体是人,那么它就是一个命名过程,如果是计算机,那么它就是一个生成过程.如何保证分布式系统下,并行生成标识的唯一与标识的命名空间有着密不可 ...
- 高并发分布式环境中获取全局唯一ID[分布式数据库全局唯一主键生成]
需求说明 在过去单机系统中,生成唯一ID比较简单,可以使用MySQL的自增主键或者Oracle中的sequence, 在现在的大型高并发分布式系统中,以上策略就会有问题了,因为不同的数据库会部署到不同 ...
- 框架篇:分布式全局唯一ID
前言 每一次HTTP请求,数据库的事务的执行,我们追踪代码执行的过程中,需要一个唯一值和这些业务操作相关联,对于单机的系统,可以用数据库的自增ID或者时间戳加一个在本机递增值,即可实现唯一值.但在分布 ...
- 关于全局唯一ID生成方法
引:最近业务开发过程中需要涉及到全局唯一ID生成.之前零零总总的收集过一些相关资料,特此整理以便后用 本博客已经迁移至:http://cenalulu.github.io/ 本篇博文已经迁移,阅读全文 ...
随机推荐
- nohup程序后台执行
Linux常用命令,用于不挂断的执行程序. nohup命令:如果你正在运行一个进程,而且你觉得在退出帐户时该进程还不会结束,那么可以使用nohup命令.该命令可以在你退出帐户/关闭终端之后继续运行相应 ...
- Shell特殊变量
$ 表示当前Shell进程的ID,即pid $echo $$ 运行结果 特殊变量列表 变量 含义 $0 当前脚本的文件名 $n 传递给脚本或函数的参数.n 是一个数字,表示第几个参数.例如,第一个参数 ...
- 如何一步一步用DDD设计一个电商网站(二)—— 项目架构
阅读目录 前言 六边形架构 终于开始建项目了 DDD中的3个臭皮匠 CQRS(Command Query Responsibility Segregation) 结语 一.前言 上一篇我们讲了DDD的 ...
- 【原】实时渲染中常用的几种Rendering Path
[原]实时渲染中常用的几种Rendering Path 本文转载请注明出处 —— polobymulberry-博客园 本文为我的图形学大作业的论文部分,介绍了一些Rendering Path,比较简 ...
- HTTP常用状态码分析
不管是面试还是工作中,经常会碰到需要通过HTTP状态码去判断问题的情况,比如对于后台RD,给到前端FE的一个接口,出现502或者504 error错误,FE就会说接口存在问题,如果没有知识储备,那就只 ...
- scrapy 知乎用户信息爬虫
zhihu_spider 此项目的功能是爬取知乎用户信息以及人际拓扑关系,爬虫框架使用scrapy,数据存储使用mongo,下载这些数据感觉也没什么用,就当为大家学习scrapy提供一个例子吧.代码地 ...
- Java 8 的 Nashorn 脚本引擎教程
本文为了解所有关于 Nashorn JavaScript 引擎易于理解的代码例子. Nashorn JavaScript 引擎是Java SE 8的一部分,它与其它像Google V8 (它是Goog ...
- ASP.NET Core应用的错误处理[1]:三种呈现错误页面的方式
由于ASP.NET Core应用是一个同时处理多个请求的服务器应用,所以在处理某个请求过程中抛出的异常并不会导致整个应用的终止.出于安全方面的考量,为了避免敏感信息的外泄,客户端在默认的情况下并不会得 ...
- VS2015常用快捷键总结
生成解决方案 F6,生成项目Shift+F6 调试执行F5,终止调试执行Shift+F5 执行调试Ctrl+F5 查找下一个F3,查找上一个Shift+F3 附加到进程Ctrl+Alt+P,逐过程F1 ...
- win10上部署Hadoop-2.7.3——非Cygwin、非虚拟机
开始接触Hadoop,听人说一般都是在Lunix下部署Hadoop,但是本人Lunix不是很了解,所以Google以下如何在Win10下安装Hadoop(之后再在Lunix下弄),找到不少文章,以下是 ...