一、背景
唯一 ID,每个人都不陌生,因为在很多场景下我们都会接触到,比如:-- 用户唯一标识
-- 订单唯一标识
-- 商品唯一标示 唯一 ID 的生成方式是有很多的,而且在不同的业务场景下,会有不同的生成需求,即没有任何一种 ID 的生成方式是最适合所有业务场景的。 所以说,脱离业务来谈的技术,都是耍流氓。
下文会分析一些分布式 ID 的生成方式以供参考。
二、分布式 ID —— UUID
UUID(Universally Unique Identifier)是通用的唯一识别码,开放软件基金会(OSF)规范定义了包括网卡MAC地址、时间戳、名字空间(Namespace)、随机或伪随机数、时序等元素。利用这些元素来生成UUID。 UUID 是由128位二进制组成,一般转换成十六进制,然后用 String 表示。 在 java 中有个 UUID 类,在他的注释中我们看见这里有 4 种不同的UUID的生成策略: ① 基于时间的; ② 基于 DCE 安全的; ③ 基于名称空间的; ④ 基于随机数的一般我们默认使用第四种,
另外,我们如果想研究更多的 UUID 的算法,附上传送门:http://www.ietf.org/rfc/rfc4122.txt
- 优点: 1、无序,即生成规则是无序的 2、本地生成,性能较高
缺点: 1、无序,因为无序带来的弊端是不能生成连续的数字 2、32 位的十六进制,只能使用 String 来存储,空间占用相对会多一点
三、分布式 ID —— 数据库主键自增
数据库主键自增比较简单,使用其实很方便,场景也相对比较广。 使用时只需要将字段设置为主键即可。- 优点: 1、简单,方便 2、排序和分页都很方便
缺点: 1、并发性不好,受限于数据库性能 2、稳定性不高,受限于数据库的服务,需担心数据库宕机 3、分库分表时会有问题,需要定制化开发区解决 4、业务量太明显,因为是自增的数字,所以业务量很容易被研究
所以,对于一些简单的业务场景,比如内部系统、to-B 的系统等,业务量并不高,使用数据库主键自增的方案为最佳。
四、分布式 ID —— redis
redis 是单线程的,可以充分保证原子性redis 中的俩命令一用便会:Incr 和 IncrBy
- 优点: 1、性能比数据库好 2、能满足自增的序列号
缺点: 1、由于是基于内存的数据库,所以有可能会存在数据丢失的情况,会导致 ID 重复 2、稳定性需担心,因为完全依赖于 redis
五、分布式 ID —— 雪花算法(Snowflake)
Snowflake 是 Twitter 提出来的一个算法,其目的是生成一个 64bit 的整数,如下图: 说明:1、1bit:一般是符号位,不做处理 2、41bit:用来记录时间戳,但是最多只可以记录69年(如果这个系统能用69年,估计已经被重构好多次了) 3、10bit:10bit用来记录机器ID,总共可以记录1024台机器,一般用前5位代表数据中心,后面5位是某个数据中心的机器ID 4、12bit:循环位,用来对同一个毫秒之内产生不同的ID,12位可以最多记录4095个,也就是在同一个机器同一毫秒最多记录4095个,多余的需要进行等待下毫秒。 当然,上面的说明只是一个最常规的使用说明,在我么实际使用的时候,需要根据业务来进行酌情调整,但是总长度不变(64bit),比如:
案例一:当我们的业务服务器位于北、上、广、深四地,则可以将 10bit 的工作机器 id 调整为 3+7 模式,即 3 位(最多看标识8)用于标识机房位置,7位(最多标识128)用于标识机器 id。
案例二:当我们的业务量没那么大的时候,则可以将最后的 12bit 的序列号再进行划分,调整为 10 + 2 模式,即 10 位(最多标识1024)用于标识序列号,2 位用于扩展。 雪花算法生成的 ID 有点多多,满足了 long 类型的 ID、性能并不低、无序等。
自己实现的简单的雪花算法:
public class IdWorker{ private long workerId; private long datacenterId; private long sequence = 0; /** * 2018/10/21日,从此时开始计算,可以用到2087年 */ private long twepoch = 1540126254592L; private long workerIdBits = 5L; private long datacenterIdBits = 5L; private long sequenceBits = 12L; private long workerIdShift = sequenceBits; private long datacenterIdShift = sequenceBits + workerIdBits; private long timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits; // 得到0000000000000000000000000000000000000000000000000000111111111111 private long sequenceMask = ~(-1L << sequenceBits); private long lastTimestamp = -1L; public IdWorker(long workerId, long datacenterId){ this.workerId = workerId; this.datacenterId = datacenterId; } public synchronized long nextId() { long timestamp = timeGen(); // 时间回拨,抛出异常 if (timestamp < lastTimestamp) { System.err.printf("clock is moving backwards. Rejecting requests until %d.", lastTimestamp); throw new RuntimeException(String.format("Clock moved backwards. Refusing to generate id for %d milliseconds", lastTimestamp - timestamp)); } // 时间回拨 // 伪代码 /*if ( timestamp < lastTimestamp) { if (lastTimestamp - timestamp <= 5) { // 时间在 5ms 以内,则直接等待 5ms,让其追上 LockSupport.parkNanos(TimeUnit.MICROSECONDS.toNanos(5)); timestamp = timeGen(); // 如果还小,则利用扩展字段(2位扩展字段最多支持3次回拨) if (timestamp < lastTimestamp) { // 扩展字段 + 1 extension += 1; if (extension > maxExtension) { throw new RuntimeException(String.format("Clock moved backwards. Refusing to generate id for %d milliseconds", lastTimestamp - timestamp)); } } } else { // 扩展字段 + 1 extension += 1; if (extension > maxExtension) { throw new RuntimeException(String.format("Clock moved backwards. Refusing to generate id for %d milliseconds", lastTimestamp - timestamp)); } } }*/ if (lastTimestamp == timestamp) { sequence = (sequence + 1) & sequenceMask; if (sequence == 0) { timestamp = tilNextMillis(lastTimestamp); } } else { sequence = 0; } lastTimestamp = timestamp; return ((timestamp - twepoch) << timestampLeftShift) | (datacenterId << datacenterIdShift) | (workerId << workerIdShift) | sequence; } /** * 当前ms已经满了 * @param lastTimestamp * @return */ private long tilNextMillis(long lastTimestamp) { long timestamp = timeGen(); while (timestamp <= lastTimestamp) { timestamp = timeGen(); } return timestamp; } private long timeGen(){ return System.currentTimeMillis(); } public static void main(String[] args) { IdWorker worker = new IdWorker(1,1); for (int i = 0; i < 30; i++) { System.out.println(worker.nextId()); } } }
文章评论