1. id⾃增的劣势
问题:当数据量庞⼤时,在数据库分库分表后,数据库⾃增id不能满⾜唯⼀id来标识数据;因为每个表都按⾃⼰节奏⾃增,会造成id冲突,⽆法满⾜需求。
所以分布式id就有了一定规则
- 全局唯一:不能重复
- 递增性:MySql的InnoDB 使用的是聚簇索引,由于多数索引我们都是使用的B-tree的数据结构建立的索引,因此在主键上应该尽量选项有序的主键保证读写性能,保证下一个id大于上一个id。
- 安全性:既要保证安全又要有时间戳保证顺序性。
- 高性能高可用性:高并发情况下保证良好的生成id。
2. 分布式Id的实现方案有哪些呢?
2.1 数据库表
我们可以在某个数据库中专门地去维护一张数据表,然后每次无论是哪张数据表需要自增 id,都需要去查这张表的记录,然后再利用 for update 锁表,并将取到的值加一,再把新值记录到数据表中。显然因为每次我们都需要锁表,所以这仅对于并发量小的项目而言是可以接受的
优点:简单粗暴
缺点:严重依赖数据库
2.2 redis
r因为 Redis 是单线程的,所以我们可以在 Redis 中维护一个键值对,之后无论是哪张数据表需要自增 id,都需要直接先去 Redis 中取值并加一。显然这种方式和上面单独维护一张数据表的方式是一样的,对高并发的支持都有所不足
优点:灵活、不依赖数据库
缺点:性能不太好
2.3 ⚠️UUID
我们可以使用 UUID 来作为不重复的主键 id,但是 UUID 是无序的字符串,所以主键索引就会失效
优点:简单、方便、性能好、全球唯一
缺点:无序性、存储的是字符串、查询效率低、传输数据量大
2.4 ⚠️雪花算法
雪花算法是 Twitter 推出的针对分布式环境下的 id 生成算法,其结果是一个 Long 型的 64bit id。具体实现上使用 41bit 作为毫秒数,10bit 作为机器的 id(5bit 是数据中心,5bit 是机器 id),12bit 作为毫秒内的流水号(这意味着每个节点在每毫秒内可以产生 4096 个 id),最后还有一个符号位永远是 0
优点:不依赖数据库、完全在内存中生成 id、高性能高可用、容量大、每秒可生成数百万个 id、id 递增、后续插入数据库的索引时性能较高
缺点:严重依赖系统时钟,如果某台机器的系统时钟发生回拨,就有可能会造成 id 冲突甚至 id 乱序
3.自增Id和UUID效率对比
自增 id
自增的主键由于是顺序的,所以 InnoDB 会把每一条记录都存储在前一条记录的后面,当达到页的最大填充因子时(InnoDB 默认的最大填充因子是页大小的 15/16,它会预留出 1/16 的空间用作以后的数据修改),下一条记录就会写入到新的页中。
一旦数据按照这种顺序的方式进行加载,那么主键页就会近乎于顺序地填满,这将大大提高页的最大填充率,从而不会造成页的浪费。
此外,新插入的行一定会在原有的最大数据行的下一行,这对 MySQL 的定位和寻址很有帮助,MySQL 不必为计算出新行的位置而做出额外的消耗,并且能减少页分裂以及碎片的产生。
UUID
因为 UUID 是无序的,所以新行的值并不一定会比之前的主键值大,所以 InnoDB 无法做到总是把新行插入到索引的最后,而是需要为新行寻找到合适的位置,从而来分配新的空间(这个过程会需要做很多额外的工作,数据的毫无顺序会导致数据分布散乱)。
同时,写入的目标页很可能已经刷新到磁盘上并且已经从缓存中移除,甚至可能是还没有被加载到缓存中,以至于 InnoDB 在插入前不得不先在磁盘中读取目标页到内存中(这将伴随着大量的随机 I/O)。
又因为写入是乱序的,InnoDB 不得不频繁地做页分裂的操作,以便为新行分配空间。页分裂会导致需要移动大量的数据,一次插入操作最少需要修改三个页以上,而频繁地页分裂会导致页变得稀疏并且被不规则地填充,最终造成数据碎片。