分布式唯一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位的IDreturn ((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地址:github.com/baidu/uid-g…
7. 美团Leaf
Leaf 是美团开源的分布式ID生成器,能保证全局唯一性、趋势递增、单调递增、信息安全,里面也提到了几种分布式方案的对比,但也需要依赖关系数据库、Zookeeper等中间件。
GITHUB地址:https://github.com/Meituan-Dianping/Leaf
在线说明文档:https://tech.meituan.com/2017/04/21/mt-leaf.html
小结
这篇文章和大家分享了全局id生成服务的几种常用方案,同时对比了各自的优缺点和适用场景。在实际工作中,大家可以结合自身业务和系统架构体系进行合理选型。
分布式唯一ID的几种生成方案相关推荐
- idgenerator 会重复吗_终极版:分布式唯一ID的几种生成方案
在业务开发中,大量场景需要唯一ID来进行标识:用户需要唯一身份标识.商品需要唯一标识.消息需要唯一标识.事件需要唯一标识等,都需要全局唯一ID,尤其是复杂的分布式业务场景中全局唯一ID更为重要. 那么 ...
- java 生成objectid_【Java】唯一ID的几种生成方案
在互联网的业务系统中,涉及到各种各样的ID,订单id,支付id,退款id,下面我一一来列举一下,不一定全部适合,这些解决方案仅供你参考,或许对你有用. 方案: 1.UUID 算法的核心思想是结合机器的 ...
- 分布式「唯一ID生成器」的几种生成方案
前言 在互联网的业务系统中,涉及到各种各样的ID,如在支付系统中就会有支付ID.退款ID等.那一般生成ID都有哪些解决方案呢?特别是在复杂的分布式系统业务场景中,我们应该采用哪种适合自己的解决方案是十 ...
- 一线大厂的分布式唯一ID生成方案是什么样的?
点击上方"方志朋",选择"设为星标" 回复"666"获取新整理的面试文章 一.前言 分布式系统中我们会对一些数据量大的业务进行分拆,如:用户 ...
- 一起学习下一线大厂的分布式唯一ID生成方案!
来源 | http://www.toutiao.com/i6682672464708764174 一.前言 分布式系统中我们会对一些数据量大的业务进行分拆,如:用户表,订单表.因为数据量巨大一张表无法 ...
- 分布式唯一ID生成企业级方案(含时钟回拨生产级解决)
目录 分布式唯一ID要求 常见的几种方案 一. 数据库自增主键 二. UUID 三. SnowFlow算法 四. Redis自增机制 五. flickr 雅虎公司方案 六. flickr方案的高并发优 ...
- Java架构直通车——分布式唯一 ID生成方案
文章目录 分布式ID的几种生成方案 UUID MySQL主键自增 数据库自增ID改进方案 雪花算法(SnowFlake) 雪花算法的优化 Redis自增id Zookeeper有序节点 最近要做区块链 ...
- 分布式唯一ID几种生成方案
一.分布式唯一ID的需求产生的背景 在分布式集群环境环境中,大量的业务场景需要使用到唯一ID的情况,如用户需要唯一身份标识.商品需要唯一标识.消息需要唯一标识.事件需要唯一标识等,都需要全局唯一ID, ...
- asp按时间自动递增编号_Java秒杀系统实战系列-分布式唯一ID生成订单编号
本文是"Java秒杀系统实战系列文章"的第七篇,在本文中我们将重点介绍 "在高并发,如秒杀的业务场景下如何生成全局唯一.趋势递增的订单编号",我们将介绍两种方法 ...
最新文章
- JAVA基础11-继承(2)
- spring bean xml 调用方法_Spring通过Xml方式注册Bean的几处关键实现点
- numpy数组ndarray如何对每个元素取绝对值,然后生成原数组的绝对值数组
- Linux中的configure、pkg-config、pkg_config_path和安装中的PKG_CONFIG_PATH问题 pkgconfig
- 教你打造一个Android组件化开发框架
- 白话Elasticsearch60-数据建模实战_Join datatype 父子关系数据建模
- ITK:观察事件 Observe An Event
- 快速理解网络通信协议
- Inside Dynamics Axapta源代码赏析(四)
- [剑指offer][JAVA]面试题第[29]题[顺时针打印矩阵][数组]
- Centos 7 Linux系统修改网卡名称为ethx
- 关键字 标识符 数据类型
- count(1),count(*)与count(列名)到底有什么区别?
- 4款Windows必装的软件,免费又实用,让你的电脑无所不能
- 大三计算机写学术论文,学院大三本科生在高水平国际会议发表学术论文
- 国际贸易术语解释通则(DDU 未完税交货(……指定目的港))
- 从0到1:开启商业与未来的秘密
- Android不同屏幕适配的大神解决方案(转)
- linux命令行 随机排列,有趣的Linux命令行:随机输出唐诗宋词
- 在上海,有哪些牛逼的互联网公司?
热门文章
- dz去掉/forum.php_discuz如何去除url的forum.php
- 10打开没有反应_118个遇水反应化学品清单及高压反应釜操作经验
- 计算机过去和现在的变化英语作文,自己过去和现在的变化英语作文带翻译
- django框架 day08
- struts2 过滤器和拦截器的区别和使用
- 使用React的static方法实现同构以及同构的常见问题
- Javascript原型理解图
- [转]Spring数据库读写分离
- 对抗攻击之SMI-FGSM:北航提出用空间动量提高对抗迁移性
- 图本检索的Zero-Shot超过CLIP模型!FILIP用细粒度的后期交互获得更好的预训练效率。...