数据库读写分离详解( 四 )


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

数据库读写分离详解

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

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

数据库读写分离详解

文章插图
 
  1. 我们从上图中可以看出snowflake算法的第二部分的41位时间戳,大概可以支撑2^41/1000/60/60/24/365 年,也就是大约有69年 。我们设计一个系统用69年应该是足够了吧 。
  2. 10位的机器ID我们可以怎么使用呢?我们可以划分成大概2到3位IDC,也就是可以支撑4到8个IDC机房;然后划分7到 8 位的机器ID,即可以支撑128~256台机器 。
  3. 12位的序号,就代表每个节点每毫秒可以生成4096个ID序号 。


    推荐阅读