自增id
对于大多数系统来说,使用mysql的自增id当作主键再最合适不过了。在数据库层面就可以获取一个顺序的、唯一的、空间占用少的id。
自增id需要是 int、bigint这些整数类型,uint 支持 40 亿的数据量,bigint unsign(0 ~18,446,744,073,709,551,615.), 40 亿的数据量对于一个频繁增加删除的表来说并不是一个很大的数字,有用完的风险。因此在建表时需要考虑,是否使用bigint。
优点
- 简单、高效
- 唯一、递增
缺点
- 可拓展性差;所有的id生成都依赖于主库,难以拓展;当然,可以增加主库的数量,将数据水平切分保证每个库的Id不重复,但是会引入格外的复杂度。
每次id生成都会访问数据库,数据库压力大,所以很多系统想要自己生成id。
发号器服务
想要全局唯一的id,就只能访问一个单点服务。但是直接利用数据库,又会导致数据库压力大,而且每次生成id都会有一次远程访问;
我们可以写一个发号器的服务,专门用来生成唯一id;可以优化访问数据库的逻辑,比如 一次访问批量获取10条id到内存,并记录(持久化)最大的id=n+10,下次取号直接重内存中获取;
这样数据库的访问次数就减少了,并且还可以将 发号器服务和业务服务部署在一个pod里(sidecar),避免频繁的远程调用。
缺点
- 增加开发、运维成本
- 仍然存在远程调用,有一定延迟
接下来介绍两种常用的本地唯一id生成的方案
UUID
通过本地算法获取唯一字符串。
优点
- 本地生成,简单、低延迟
- 拓展性好,理论上无上限
缺点
- uuid类型时字符串,一般长度12+,占用空间较大
- uuid无序会导致页空间浪费以及频繁的页分裂,增加了系统开销
页分裂:如果是乱序的写入,每次在中间的某个叶子节点写入数据,后面的数据都需要往后挪。更糟糕的是,如果后挪时某一页记录已满,接着就会页分裂,部分的数据移动到新的页。
所以乱序的插入会导致频繁的页分裂,不仅耗时,还会导致空间利用率变低,后续不得不通过OPTIMIZE TABLE来重建表并优化填充页面。
图片来自 https://segmentfault.com/a/1190000039151324
snowflake
分布式ID生成算法
一个long型的ID,使用其中41bit
作为毫秒数,10bit
作为机器编号,12bit
作为毫秒内序列号。这个算法单机每秒内理论上最多可以生成1000 * (2^12-1) 或 1000 * ((1 << 12)- 1)
,也就是4096000个ID。
借鉴snowflake的思想,结合各公司的业务逻辑和并发量,可以实现自己的分布式ID生成算法。
优点
- 本地生成id,延迟低
- id整体趋势有序
- 可以根据自己的业务灵活调整
缺点
- 不是全局有序
Reference
https://segmentfault.com/a/1190000039151324
https://blog.csdn.net/weixin_39710991/article/details/113128951
https://changkaixin.cn/2015/12/04/gen-id-way/
https://z.itpub.net/article/detail/F49F834DAD1FDEE011F56A7CD7DA65C6
https://chai2010.cn/advanced-go-programming-book/ch6-cloud/ch6-01-dist-id.htm