上篇文章介绍了数据库优化,以及分库分表的一个概念,这篇主要是介绍分库分表需要解决的问题
1、ID问题
当我们进行分库分表的拆分之后,如果继续使用原来的表自增的方式显然最终查询出来的结果会出现id重复问题,处理分库分表下的id,一般有两种方法
1、1 UUID
使用uuid作为主键是最简单的方案,缺点也很明显,uuid长度太长,占据的空间比较大,作为索引并且基于索引的查询性能上面会有一定的影响。
1、2 Sequence表
创建一个Sequence表来维护表明和id的关系,
CREATE TABLE `SEQUENCE` (
`table_name` varchar(18) NOT NULL,
`nextid` bigint(20) NOT NULL,
PRIMARY KEY (`table_name`)
) ENGINE=InnoDB
每当需要为某个表生成id时,就从Sequence表当中取出nextId,并将nextId加一后存入表中,当然缺点也很明显:所有插入数据库当中的数据,都要访问这张表,这张表很容易成为系统瓶颈。
1、3 Twitter的分布式id算法Snowflake
10---0000000000 0000000000 0000000000 0000000000 0 --- 00000 ---00000 ---000000000000
其主要算法的核心就是生成long型的ID,共64位,第一位未使用,41bit的毫秒级时间,5bitdatacenter标志位,5bit机器码,12bit作为毫秒内序列号。该算法每秒能产生26万个id
1、4 取当前毫秒数
采用这种方法最大的问题就是一秒内并发产生的只有1000个主键id,显然是不够的。
2、分布式事务管理问题
分布式事务简单来说就是一个大的操作由多个小的操作组成,这些小的操作分布在不同的服务器上,属于不同的应用,分布式事务要保证这些小的操作要么成功,要么不成功,简单来说就是分布式事务是为了保证数据库当中的数据一致性。
常见的分布式事务解决方案
2、1 基于XA协议的两阶段提交
XA是一种分布式协议,由两部分组成:事务管理器和本地资源管理器。本地资源管理器往往是由数据库实现,像Oracle等这些商用数据库都实现了XA接口,而事务管理器作为全局的调度者,负责各个本地资源的提交和回滚。XA实现分布式原理如下:
第一阶段:事务管理器给每个参与的本地资源库发送预备信息,每个参与者要么直接返回失败,要么在本地执行事务信息,在本地执行redo和undo日志,但是不提交,参与者会在准备阶段完成几乎所有正式提交的动作,只保留了最后一步耗时非常短的提交动作,留在第二阶段执行
第二阶段:如果事务管理器收到了本地资源管理器的失败的信息,那么会对每个资源管理器发送回滚信息,否则发送commit信息,
总结:XA协议比较简单,一旦商业数据库实现了XA协议,使用分布式事务的成本就比较低,XA的缺点就是性能不理想,特别是在交易下单链路,往往并发量很高,XA无法实现高并发场景。XA在MySQL当中的支持不是特别理想,MySQL的XA实现,没有记录prepare日志,并且对于NoSQL当中没有支持XA接口,所以XA的实现会比较狭隘点。
2、2 消息事务+最终一致性
消息事务就是基于消息中间件的两阶段提交,其实是对消息中间件的特殊使用,是将本地资源和发送信息放在了一个分布式事务里,保证本地操作成功并且对外发送消息成功,要么两者都失败。
对于上述操作的执行流程
1、A系统向消息中间件发送预备消息(如果失败,整个事务失败,不会执行A的本地操作)
2、消息中间件保存预备消息并返回(如果失败,整个事务失败,不会执行A的本地操作)
3、A系统执行本地事务(如果失败,需要回滚预备消息,这个时候需要A系统实现中间件的回调接口,消息中间件会不断的执行回调接口,检查事务A是否执行失败,如果失败回滚预备消息)
4、A系统执行完本地事务后发送提交消息到消息中间件(如果这个步骤失败,这个时候系统A是已经执行过本地事务了,中间件通过回调接口,可以判断到事务A执行成功了,不需要事务A再进行提交了,消息中间件自己对消息进行提交,完成整个事务)
5、消息中间件收到消息后发送到系统B
6、系统B执行失败的话,中间件一直向系统B发送消息,直到执行成功并返回
对于这种方式采用消息事务的方式,缺点在于如果B系统一直执行失败,就会造成一致性收到影响。
2、3 TCC编程模式
TCC编程模式是阶段编程的一个变种。TCC采用了一个编程框架,将整个业务逻辑分成三部分:Try、Confirm、Cancle。比如以下单为例:Try阶段尝试去减少库存,Confirm阶段更新订单状态,如果更新订单失败,进入Cancle阶段,会去恢复库存。整个过程是通过代码来实现两阶段,所以在开发时使用的并不普遍。
3、跨节点的join, count,order by ,group by 及聚合函数
3、1 跨节点的join问题
只要是进行切分,节点的join问题是不可避免的,普遍解决办法是通过两次查询,在第一次查询到的结果集当中找出关联id,然后再根据id发起二次查询。
3、2跨节点的count, order by, group by 这类查询都是基于全部数据进行处理的,所以需要对每个节点都进行数据查询,然后再进行汇总处理,但是我们可以使用多线程的方式,对每个节点的数据进行处理,然后再进行汇总,这样也比单表一次性全部查询速度要快,尤其当数据量很大的时候。
4、跨分片的分页排序
对于跨表之间的分页问题,当我们想要访问的是前几页数据时,可以对各个节点进行分页查询,然后再对结果集进行汇总处理,尤其需要注意,如果想要查询第100页的数据,这时候,不是单纯的去查询每个表当中第100页开始,10条数据,然后进行汇总,而是需要把每个表当中从0开始到100页的数据全部查询出来,然后再此基础上进行汇总查询出来第100页的数据。
5、分库策略
分库策略主要是来决定,每当添加一条数据时,存放在哪张表当中
5、1 根据数值范围 0-100000放在表一,100001到200000放在表二依次类推
这种方式,出现的问题就是无法解决单表写入数据的瓶颈问题,优点就是,单表长度可控,扩展方便
5、2 主键id 对分库数量进行取模运算
取模方式进行分库,相对来说会比较均匀,在进行扩展的时候,一般是2的幂次方扩展库的数量,这样可以降低数据移动的数量,只有一半数据需要移动到新表当中,实际开发当中这种方式使用的也比较多。
6、分库数量
分库数量的设定首先要基于业务场景,现有的数据量有多大,每天或者每月的增量是多少,现在需要分多少个库,分完之后可以支撑多久。
如果分库数量太少,就达不到分散存储和减轻DB压力的目的。如果分库的数量过多,每个库数据存储量少,单库的访问性能比较好,但是对于跨表访问,如果是并发模式下,可能会降低程序的性能。