一、背景
唯一 ID,每个人都不陌生,因为在很多场景下我们都会接触到,比如:
— 用户唯一标识
— 订单唯一标识
— 商品唯一标示
唯一 ID 的生成方式是有很多的,而且在不同的业务场景下,会有不同的生成需求,即没有任何一种 ID 的生成方式是最适合所有业务场景的。
所以说,脱离业务来谈的技术,都是耍流氓。
下文会分析一些分布式 ID 的生成方式以供参考。
</p>
二、分布式 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、性能并不低、无序等。
自己实现的简单的雪花算法:
1 | public class IdWorker{ |