分布式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的几种生成方案相关推荐

  1. idgenerator 会重复吗_终极版:分布式唯一ID的几种生成方案

    在业务开发中,大量场景需要唯一ID来进行标识:用户需要唯一身份标识.商品需要唯一标识.消息需要唯一标识.事件需要唯一标识等,都需要全局唯一ID,尤其是复杂的分布式业务场景中全局唯一ID更为重要. 那么 ...

  2. java 生成objectid_【Java】唯一ID的几种生成方案

    在互联网的业务系统中,涉及到各种各样的ID,订单id,支付id,退款id,下面我一一来列举一下,不一定全部适合,这些解决方案仅供你参考,或许对你有用. 方案: 1.UUID 算法的核心思想是结合机器的 ...

  3. 分布式「唯一ID生成器」的几种生成方案

    前言 在互联网的业务系统中,涉及到各种各样的ID,如在支付系统中就会有支付ID.退款ID等.那一般生成ID都有哪些解决方案呢?特别是在复杂的分布式系统业务场景中,我们应该采用哪种适合自己的解决方案是十 ...

  4. 一线大厂的分布式唯一ID生成方案是什么样的?

    点击上方"方志朋",选择"设为星标" 回复"666"获取新整理的面试文章 一.前言 分布式系统中我们会对一些数据量大的业务进行分拆,如:用户 ...

  5. 一起学习下一线大厂的分布式唯一ID生成方案!

    来源 | http://www.toutiao.com/i6682672464708764174 一.前言 分布式系统中我们会对一些数据量大的业务进行分拆,如:用户表,订单表.因为数据量巨大一张表无法 ...

  6. 分布式唯一ID生成企业级方案(含时钟回拨生产级解决)

    目录 分布式唯一ID要求 常见的几种方案 一. 数据库自增主键 二. UUID 三. SnowFlow算法 四. Redis自增机制 五. flickr 雅虎公司方案 六. flickr方案的高并发优 ...

  7. Java架构直通车——分布式唯一 ID生成方案

    文章目录 分布式ID的几种生成方案 UUID MySQL主键自增 数据库自增ID改进方案 雪花算法(SnowFlake) 雪花算法的优化 Redis自增id Zookeeper有序节点 最近要做区块链 ...

  8. 分布式唯一ID几种生成方案

    一.分布式唯一ID的需求产生的背景 在分布式集群环境环境中,大量的业务场景需要使用到唯一ID的情况,如用户需要唯一身份标识.商品需要唯一标识.消息需要唯一标识.事件需要唯一标识等,都需要全局唯一ID, ...

  9. asp按时间自动递增编号_Java秒杀系统实战系列-分布式唯一ID生成订单编号

    本文是"Java秒杀系统实战系列文章"的第七篇,在本文中我们将重点介绍 "在高并发,如秒杀的业务场景下如何生成全局唯一.趋势递增的订单编号",我们将介绍两种方法 ...

最新文章

  1. JAVA基础11-继承(2)
  2. spring bean xml 调用方法_Spring通过Xml方式注册Bean的几处关键实现点
  3. numpy数组ndarray如何对每个元素取绝对值,然后生成原数组的绝对值数组
  4. Linux中的configure、pkg-config、pkg_config_path和安装中的PKG_CONFIG_PATH问题 pkgconfig
  5. 教你打造一个Android组件化开发框架
  6. 白话Elasticsearch60-数据建模实战_Join datatype 父子关系数据建模
  7. ITK:观察事件 Observe An Event
  8. 快速理解网络通信协议
  9. Inside Dynamics Axapta源代码赏析(四)
  10. [剑指offer][JAVA]面试题第[29]题[顺时针打印矩阵][数组]
  11. Centos 7 Linux系统修改网卡名称为ethx
  12. 关键字 标识符 数据类型
  13. count(1),count(*)与count(列名)到底有什么区别?
  14. 4款Windows必装的软件,免费又实用,让你的电脑无所不能
  15. 大三计算机写学术论文,学院大三本科生在高水平国际会议发表学术论文
  16. 国际贸易术语解释通则(DDU 未完税交货(……指定目的港))
  17. 从0到1:开启商业与未来的秘密
  18. Android不同屏幕适配的大神解决方案(转)
  19. linux命令行 随机排列,有趣的Linux命令行:随机输出唐诗宋词
  20. 在上海,有哪些牛逼的互联网公司?

热门文章

  1. dz去掉/forum.php_discuz如何去除url的forum.php
  2. 10打开没有反应_118个遇水反应化学品清单及高压反应釜操作经验
  3. 计算机过去和现在的变化英语作文,自己过去和现在的变化英语作文带翻译
  4. django框架 day08
  5. struts2 过滤器和拦截器的区别和使用
  6. 使用React的static方法实现同构以及同构的常见问题
  7. Javascript原型理解图
  8. [转]Spring数据库读写分离
  9. 对抗攻击之SMI-FGSM:北航提出用空间动量提高对抗迁移性
  10. 图本检索的Zero-Shot超过CLIP模型!FILIP用细粒度的后期交互获得更好的预训练效率。...