背景
主键(Primary Key),用于唯一标识表中的每一条数据。所以,一个合格的主键的最基本要求应该是唯一性。
那怎么保证唯一呢?相信绝大部分开发者在刚入行的时候选择的都是数据库的自增id,因为这是一种非常简单的方式,数据库里配置下就行了。但自增主键优缺点都很明显。
优点如下:
- 无需编码,数据库自动生成,速度快,按序存放。
- 数字格式,占用空间小。
缺点如下:
- 有数量限制。存在用完的风险。
- 导入旧数据时,可能会存在id重复,或id被重置的问题。
- 分库分表场景处理过于麻烦。
GUID
GUID,全局唯一标识符,是一种有算法生成的二进制长度为128位的数字标识符,在理想情况下,任何计算机和计算机集群都不会生成两个相同的GUID,所以可以保证唯一性。但也是有优缺点的。分别如下:
优点如下:
- 分布式场景唯一。
- 跨合并服务器数据合并方便。
缺点如下:
- 存储空间占用较大。
- 无序,涉及到排序的场景下性能较差。
GUID最大的缺点是无序,因为数据库主键默认是聚集索引,无序的数据将导致涉及到排序场景时性能降低。虽然可以根据算法生成有序的GUID,但对应的存储空间占用还是比较大的。
概念介绍
所以,本文的重点来了。如果能优化自增和GUID的缺点,是不是就算是一个更好的选择呢。一个好的主键需要具备如下特性:
- 唯一性。
- 递增有序性。
- 存储空间占用尽量小。
- 分布式支持。
经过优化后的雪花算法可以完美支持以上特性。
下图是雪花算法的构成图:
雪花id组成由1位符号位+41位时间戳+10位工作机器id+12位自增序号组成,总共64比特组成的long类型。
1位符号位 :因为long的最高位是符号位,正数为0,负数为1,咱们要求生成的id都是正数,所以符号位值设置0。
41位时间戳 :41位能表示的最大的时间戳为2199023255552(1L<<41),则可使用的时间为2199023255552/(1000606024365)≈69年。到这里可能会有人百思不得姐,时间戳2199023255552对应的时间应该是2039-09-07 23:47:35,距离现在只有不到20年的时间,为什么笔者算出来的是69年呢?
其实时间戳的算法是1970年1月1日到指点时间所经过的毫秒或秒数,那咱们把开始时间从2020年开始,就可以延长41位时间戳能表达的最大时间。
10位工作机器id :这个表示的是分布式场景中,集群中的每个机器对应的id,所以咱们需要给每个机器编号。10位的二进制最大支持1024个机器节点。
12位序列号 :自增值,毫秒级最大支持4096个id,也就是每秒最大可生成4096000个id。说个题外话,如果用雪花id当成订单号,淘宝的双十一的每秒的订单量有这个多吗?
到这里,雪花id算法的结构已经介绍完了,那怎么根据这个算法封装成可以使用的组件呢?
开发方案
作为一个程序员,根据算法逻辑写代码这属于基础操作,但写之前,还需要把算法里可能存在的坑想清楚,咱们再来一起来过一遍雪花id的结构。
首先,41位的时间戳部分没有特别需要注意的,起始时间你用1970也是可以的,反正也够用十几二十年(二十年之后的事,关我屁事)。或者,你觉得你的系统可以会运行半个世纪以上,那就把当前离你最近的时间作为起始时间吧。
其次,10位的工作机器id,你可以每个机器编个号,0-1023随便选,但人工搞这件事好像有点傻,如果就两三台机器,人工配