数据库分库分表后,我们怎么保证ID全局唯一

上两篇讲到了我们的系统在面临大并发读取的时候 , 采用了读写分离主从复制(数据库读写分离方案 , 实现高性能数据库集群)的方案去应对 , 后来又面临了大并发写入的时候 , 系统数据库采用了分库分表的方案(数据库分库分表方案 , 优化大量并发写入所带来的性能问题) , 通过垂直拆分以及水平拆分的方式 , 将数据分到多个库和多个表中去应对的 , 即现在是这样的一套分布式存储结构 。

数据库分库分表后,我们怎么保证ID全局唯一

文章插图
 
数据库分库分表那篇也讲到了 , 使用了分库分表势必会带来和我们之前使用不大相同的问题 。今天 , 我将其中一个和我们开发息息相关的问题提出来进行讲解 , 也就是我们开发中所使用的的主键的问题 。我们知道 , 以前我们单库的时候 , 主键唯一ID是自增的 , 现在好了 , 我们的数据被分到多个库的多个表里面了 , 如果我们还是使用之前的主键自增策略 , 那么这样就会出现两个数据插入到了两个不同的表会出现相同的ID值 , 这时我们该怎么去使用呢?
对于什么是主键 , 主键该怎么选 , 今天不做讲解 , 我相信大家可能比我还精通 , 我们今天主要是讲唯一主键ID在分布式存储系统下怎么生成 , 保证ID的唯一性且符合我们业务需要 , 才是我们开发人员最关心的实战 。
UUID这个时候 , 你可能会说 , 自增用不了 , 那我就是用UUID嘛 , 这个UUID生成出来的就是唯一的 。的确 , 在我以前在一个公司中的确接触到是使用UUID来生成唯一主键ID的 , 而且性能还可以 。但是 , 我想提一点的就是 , 当这个ID和我们业务交集不相关的时候是可以使用UUID生成主键的 。比如 , 一般我们业务是需要用来做查询的 , 而且最好是单调递增的 , 这样我们的UUID就很不适合了 。
主键ID单调递增有什么好处呢?1 , 就拿我们用户关注航班这个模块来说 , 我们查看某个航班关注用户按照时间的先后进行排序 。因为现在的ID是时间上有序的 , 所以现在我们就可以按照ID来进行排序了 , 同时这样对于有些并不是要存储时间的业务来说 , 会减少不少的存储空间 。
2 , 有序的ID可以提升数据写入的性能
我们知道主键其实在数据库中就是一种索引 , 而索引在MySQL数据库的B+数据结构中是顺序存储的 , 所以每次插入的时候就是递增排序的 , 直接追加到后面就行 。如果是无序的话 , 则每次插入数据之前还得查找它应该所在的位置 , 这无疑就会增加数据的异动等相关的开销 , 如下图:
数据库分库分表后,我们怎么保证ID全局唯一

文章插图
 
如上图所示 , 如果我们生成的ID是有序的 , 那这个 50 就直接插在尾部就行了 , 如果是无序的话 , 突然生成了一个 26 , 我们还得先找到 26 需要存放的位置 , 然后还要对其后面数据进行挪位置 。
3 , UUID不具备业务相关性
我们现在开发的项目都是依据公司业务开展的 , 而我们的唯一ID一般都是和业务有关系的 , 比如 , 有些订单ID中带上了时间的维度、机房的维度以及业务类型等维度 。也就是为了我方便进行定位是那种业务的订单 , 才会这么设计的 , 是不是 。
而UUID是由32位的16进制数字组成的字符串 , 不仅在存储空间上造成浪费 , 更不具备我们业务相关性 。那我们该怎么解决呢?其实twitter提出来的Snowflake 算法就能很好满足我们现在的要求 , 满足了主键ID的全局唯一性、单调递增性 , 也可以满足我们的业务相关 。所以 , 我们现在使用的唯一ID生成方式就是使用Snowflake算法 , 这个算法其实很简单 。下面我们来对其进行讲解 , 并对其相应改造使其能用到我们的开发业务中来 。
Snowflake 算法原理Snowflake 是由 64 比特bit二进制数字组成的 , 一共分为4大部分:
  • 1位默认不使用