分布式ID生成器

版权声明:本文为博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/u013467442/article/details/91644613

0.背景

公司产品线最初为了快速上线、快速迭代,所使用的ID采用把JDK原生的32位(去掉四个-)或者36位的原始UUID(Universally Unique Identifier)缩短为19位,且不丢失精度的方式。

例如:E3MGMM6SQwsaZqHfcIs

但是UUID太长而且人类不可读(由大写、小写、数字随机组成),且对数据库性能有一定的影响(短且数字递增的最优),所以希望开发出一种全局唯一性、高性能、纯数字、较短、趋势递增的分布式ID生成器。

1.需求/设计

  • ID不重复、全局唯一
  • 长度简短:长度尽量保持短(最好在10位以内),方便用户转述、记录、投诉
  • 能通过ID区分业务类型,例如A开头的表示国内机票
  • 推荐使用纯数字
  • 性能要高
  • 适应分布式环境
  • 趋势递增

业界常用的ID如下所示:(作为参考)
在这里插入图片描述
暂定参考设计为:
在这里插入图片描述

2.UUID

2.1概念

UUID 含义是通用唯一识别码 (Universally Unique Identifier),这是一个软件建构的标准。
也是被开源软件基金会 (Open Software Foundation, OSF) 的组织应用在分布式计算环境领域的一部分。
UUID 的目的,是让分布式系统中的所有元素,都能有唯一的辨识资讯,而不需要透过中央控制端来做辨识资讯的指定。

UUID保证对在同一时空中的所有机器都是唯一的。通常平台会提供生成的API。
按照开放软件基金会(OSF)制定的标准计算,用到了以太网卡地址、纳秒级时间、芯片ID码和许多可能的数字

2.2优缺点

  • 优点:
    UUID的好处是生成和使用简单、性能好、本地生成、没有高可用风险、而且在全球范围内任意分布式系统中都不会重复,不用考虑数据库主键的冲突等。
  • 缺点:
    缺陷在于生成的结果串会比较长;由大写、小写、数字随机组成,人类完全不可读,不可记;在进行ID转述过程中特别容易出错;查询效率低,数据库的主键若使用数字的性能要高于字符串。

2.3一个生成19位UUID的算法

JDK自带的UUID类中toString方法其实是把128位字节转换为16进制数值,若使用62进制,既0-9a-zA-Z,这样就能缩短UUID到19位。为此,专门编写了一个UUID字符串生成法。
详见:超短的19位UUID,性能几乎翻倍提升

3.数据库自增ID

使用数据库的id自增策略,如 MySQL 的 auto_increment。并且可以使用两台数据库分别设置不同步长,生成不重复ID的策略来实现高可用。
能够保证严格有序,但是有严重的性能瓶颈。项目组最早就是使用这种方式,性能低下、吃了不少苦头。

为了解决性能低的问题,可以采用批量生成ID的方式缓解。
主要思想为:一次按需批量生成多个ID,每次生成都需要访问数据库,将数据库修改为最大的ID值,并在内存中记录当前值及最大值。

4.类snowflake方案

在这里插入图片描述

  • 1bit:一般是符号位,不做处理
  • 41bit:用来记录时间戳,这里可以记录69年,如果设置好起始时间比如今年是2018年,那么可以用到2089年,到时候怎么办?要是这个系统能用69年,我相信这个系统早都重构了好多次了。
  • 10bit:10bit用来记录机器ID,总共可以记录1024台机器,一般用前5位代表数据中心,后面5位是某个数据中心的机器ID
  • 12bit:循环位,用来对同一个毫秒之内产生不同的ID,12位可以最多记录4095个,也就是在同一个机器同一毫秒最多记录4095个,多余的需要进行等待下毫秒。

优点:
毫秒数在高位,自增序列在低位,整个ID都是趋势递增的。
生成ID的性能也是非常高的。
可以根据自身业务特性分配bit位,非常灵活。

缺点:
强依赖机器时钟,如果机器上时钟回拨,会导致发号重复或者服务会处于不可用状态。
上面只是方案和思路,需要落地实现

详见:ID 生成器 雪花算法

5.Leaf——美团点评分布式ID生成系统

Leaf是美团开源的分布式ID生成器,能保证全局唯一性、趋势递增、单调递增、信息安全,里面也提到了几种分布式方案的对比,但也需要依赖关系数据库、Zookeeper等中间件。
详见:Leaf——美团点评分布式ID生成系统

6.百度UidGenerator(项目中最终使用该方案)

UidGenerator是Java实现的, 基于Snowflake算法的唯一ID生成器。
UidGenerator通过借用未来时间来解决sequence天然存在的并发限制; 采用RingBuffer来缓存已生成的UID, 并行化UID的生产和消费, 同时对CacheLine补齐,避免了由RingBuffer带来的硬件级「伪共享」问题. 最终单机QPS可达600万
依赖版本:Java8及以上版本, MySQL(内置WorkerID分配器, 启动阶段通过DB进行分配; 如自定义实现, 则DB非必选依赖)
详见:baidu/uid-generator

7. Redis生成ID

Redis的所有命令操作都是单线程的,本身提供像 incr 和 increby 这样的自增原子命令,所以能保证生成的 ID 肯定是唯一有序的
类似数据库自增ID,性能优于数组库自增

8.参考文献

展开阅读全文

分布式唯一ID生成器Twitter 的 Snowflake idworker有谁在用么

05-06

Snowflake idworker在分布式部署到不同IDC服务器的时候,是否需要修改参数或配置才能达到产生的ID才会不重复?rn[code="java"]rnrnimport org.slf4j.Logger;rnimport org.slf4j.LoggerFactory;rnrn/**rn * @author zhujuanrn * From: https://github.com/twitter/snowflakern * An object that generates IDs.rn * This is broken into a separate class in casern * we ever want to support multiple worker threadsrn * per processrn */rnpublic class IdWorker rn rn protected static final Logger LOG = LoggerFactory.getLogger(IdWorker.class);rn rn private long workerId;rn private long datacenterId;rn private long sequence = 0L;rnrn private long twepoch = 1288834974657L;rnrn private long workerIdBits = 5L;rn private long datacenterIdBits = 5L;rn private long maxWorkerId = -1L ^ (-1L << workerIdBits);rn private long maxDatacenterId = -1L ^ (-1L << datacenterIdBits);rn private long sequenceBits = 12L;rnrn private long workerIdShift = sequenceBits;rn private long datacenterIdShift = sequenceBits + workerIdBits;rn private long timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits;rn private long sequenceMask = -1L ^ (-1L << sequenceBits);rnrn private long lastTimestamp = -1L;rnrn public IdWorker(long workerId, long datacenterId) rn // sanity check for workerIdrn if (workerId > maxWorkerId || workerId < 0) rn throw new IllegalArgumentException(String.format("worker Id can't be greater than %d or less than 0", maxWorkerId));rn rn if (datacenterId > maxDatacenterId || datacenterId < 0) rn throw new IllegalArgumentException(String.format("datacenter Id can't be greater than %d or less than 0", maxDatacenterId));rn rn this.workerId = workerId;rn this.datacenterId = datacenterId;rn LOG.info(String.format("worker starting. timestamp left shift %d, datacenter id bits %d, worker id bits %d, sequence bits %d, workerid %d", timestampLeftShift, datacenterIdBits, workerIdBits, sequenceBits, workerId));rn rnrn public synchronized long nextId() rn long timestamp = timeGen();rnrn if (timestamp < lastTimestamp) rn LOG.error(String.format("clock is moving backwards. Rejecting requests until %d.", lastTimestamp));rn throw new RuntimeException(String.format("Clock moved backwards. Refusing to generate id for %d milliseconds", lastTimestamp - timestamp));rn rnrn if (lastTimestamp == timestamp) rn sequence = (sequence + 1) & sequenceMask;rn if (sequence == 0) rn timestamp = tilNextMillis(lastTimestamp);rn rn else rn sequence = 0L;rn rnrn lastTimestamp = timestamp;rnrn return ((timestamp - twepoch) << timestampLeftShift) | (datacenterId << datacenterIdShift) | (workerId << workerIdShift) | sequence;rn rnrn protected long tilNextMillis(long lastTimestamp) rn long timestamp = timeGen();rn while (timestamp <= lastTimestamp) rn timestamp = timeGen();rn rn return timestamp;rn rnrn protected long timeGen() rn return System.currentTimeMillis();rn rnrn[/code] 问答

没有更多推荐了,返回首页