微服务入门系列(四):数据库的服务化切分【转载】
1. 什么是“分库分表”?
随着大数据时代的到来,业务系统的数据量日益增大,数据存储能力逐渐成为影响系统性能的瓶颈。目前主流的关系型数据库单表存储上限为1000万条记录,而这一存储能力显然已经无法满足大数据背景下的业务系统存储要求了。随着微服务架构、分布式存储等概念的出现,数据存储问题也渐渐迎来了转机。而数据分片是目前解决海量数据持久化存储与高效查询的一种重要手段。数据分库分表的过程在系统设计阶段完成,要求系统设计人员根据系统预期的业务量,将未来可能出现瓶颈的数据库、数据表按照一定规则拆分成多个库、多张表。这些数据库和数据表需要部署在不同的服务器上,从而将数据读写压力分摊至集群中的各个节点,提升数据库整体处理能力,避免出现读写瓶颈的现象。
目前数据分片的方式一共有两种:离散分片和连续分片。
离散分片是按照数据的某一字段哈希取模后进行分片存储。只要哈希算法选择得当,数据就会均匀地分布在不同的分片中,从而将读写压力平均分配给所有分片,整体上提升数据的读写能力。然而,离散存储要求数据之间有较强的独立性,但实际业务系统并非如此,不同分片之间的数据往往存在一定的关联性,因此在某些场景下需要跨分片连接查询。由于目前所有的关系型数据库出于安全性考虑,均不支持跨库连接。因此,跨库操作需要由数据分库分表中间件来完成,这极大影响数据的查询效率。此外,当数据存储能力出现瓶颈需要扩容时,离散分片规则需要将所有数据重新进行哈希取模运算,这无疑成为限制系统可扩展性的一个重要因素。虽然,一致性哈希能在一定程度上减少系统扩容时的数据迁移,但数据迁移问题仍然不可避免。对于一个已经上线运行的系统而言,系统停止对外服务进行数据迁移的代价太大。
第二种数据分片的方式即为连续分片,它能解决系统扩容时产生的数据迁移问题。这种方式要求数据按照时间或连续自增主键连续存储。从而一段时间内的数据或相邻主键的数据会被存储在同一个分片中。当需要增加分片时,不会影响现有的分片。因此,连续分片能解决扩容所带来的数据迁移问题。但是,数据的存储时间和读写频率往往呈正比,也就是大量的读写往往都集中在最新存储的那一部分数据,这就会导致热点问题,并不能起到分摊读写压力的初衷。
2. 数据库扩展的几种方式
数据库扩展一共有四种分配方式,分别是:垂直分库、垂直分表、水平分表、水平数据分片。每一种策略都有各自的适用场景。
1、垂直分库
垂直分库即是将一个完整的数据库根据业务功能拆分成多个独立的数据库,这些数据库可以运行在不同的服务器上,从而提升数据库整体的数据读写性能。这种方式在微服务架构中非常常用。微服务架构的核心思想是将一个完整的应用按照业务功能拆分成多个可独立运行的子系统,这些子系统称为“微服务”,各个服务之间通过RPC接口通信,这样的结构使得系统耦合度更低、更易于扩展。垂直分库的理念与微服务的理念不谋而合,可以将原本完整的数据按照微服务拆分系统的方式,拆分成多个独立的数据库,使得每个微服务系统都有各自独立的数据库,从而可以避免单个数据库节点压力过大,影响系统的整体性能,如下图所示。
2、垂直分表
垂直分表如果一张表的字段非常多,那么很有可能会引起数据的跨页存储,这会造成数据库额外的性能开销,而垂直分表可以解决这个问题。垂直分表就是将一张表中不常用的字段拆分到另一张表中,从而保证第一章表中的字段较少,避免出现数据库跨页存储的问题,从而提升查询效率。而另一张表中的数据通过外键与第一张表进行关联,如下图所示。
3、水平分表
如果一张表中的记录数过多(超过1000万条记录),那么会对数据库的读写性能产生较大的影响,虽然此时仍然能够正确地读写,但读写的速度已经到了业务无法忍受的地步,此时就需要使用水平分表来解决这个问题。水平分表是将一张含有很多记录数的表水平切分,拆分成几张结构相同的表。举个例子,假设一张订单表目前存储了2000万条订单的数据,导致数据读写效率极低。此时可以采用水平分表的方式,将订单表拆分成100张结构相同的订单表,分别叫做order_1、order_2……、order_100。然后可以根据订单所属用户的id进行哈希取模后均匀地存储在这100张表中,从而每张表中只存储了20万条订单记录,极大提升了订单的读写效率,如下图所示。
当然,如果拆分出来的表都存储在同一个数据库节点上,那么当请求量过大的时候,毕竟单台服务器的处理能力是有限的,数据库仍然会成为系统的瓶颈,所以为了解决这个问题,就出现了水平数据分片的解决方案。
4、水平分库分表
水平数据分片与数据分片区别在于:水平数据分片首先将数据表进行水平拆分,然后按照某一分片规则存储在多台数据库服务器上。从而将单库的压力分摊到了多库上,从而避免因为数据库硬件资源有限导致的数据库性能瓶颈,如下图所示。
分库分表的几种方式
目前常用的数据分片策略有两种,分别是连续分片和离散分片。
1、离散分片
离散分片是指将数据打散之后均匀地存储在逻辑表的各个分片中,从而使的对同一张逻辑表的数据读取操作均匀地落在不同库的不同表上,从而提高读写速度。离散分片一般以哈希取模的方式实现。比如:一张逻辑表有4个分片,那么在读写数据的时候,中间件首先会取得分片字段的哈希值,然后再模以4,从而计算出该条记录所在的分片。在这种方法中,只要哈希算法选的好,那么数据分片将会比较均匀,从而数据读写就会比较均匀地落在各个分片上,从而就有较高的读写效率。但是,这种方式也存在一个最大的缺陷——数据库扩容成本较高。采用这种方式,如果需要再增加分片,原先的分片算法将失效,并且所有记录都需要重新计算所在分片的位置。对于一个已经上线的系统来说,行级别的数据迁移成本相当高,而且由于数据迁移期间系统仍在运行,仍有新数据产生,从而无法保证迁移过程数据的一致性。如果为了避免这个问题而停机迁移,那必然会对业务造成巨大影响。当然,如果为了避免数据迁移,在一开始的时候就分片较多的分片,那需要承担较高的费用,这对于中小公司来说是无法承受的。
2、连续分片
连续分片指的是按照某一种分片规则,将某一个区间内的数据存储在同一个分片上。比如按照时间分片,每个月生成一张物理表。那么在读写数据时,直接根据当前时间就可以找到数据所在的分片。再比如可以按照记录ID分片,这种分片方式要求ID需要连续递增。由于Mysql数据库单表支持最大的记录数约为1000万,因此我们可以根据记录的ID,使得每个分片存储1000万条记录,当目前的记录数即将到达存储上限时,我们只需增加分片即可,原有的数据无需迁移。连续分片的一个最大好处就是方便扩容,因为它不需要任何的数据迁移。但是,连续分片有个最大的缺点就是热点问题。连续分片使得新插入的数据集中在同一个分片上,而往往新插入的数据读写频率较高,因此,读写操作都会集中在最新的分片上,从而无法体现数据分片的优势。
引入分库分表中间件后面临的问题
1、跨库操作
在关系型数据库中,多张表之间往往存在关联,我们在开发过程中需要使用JOIN操作进行多表连接。但是当我们使用了分库分表模式后,由于数据库厂商处于安全考虑,不允许跨库JOIN操作,从而如果需要连接的两张表被分到不同的库中后,就无法使用SQL提供的JOIN关键字来实现表连接,我们可能需要在业务系统层面,通过多次SQL查询,完成数据的组装和拼接。这一方面会增加业务系统的复杂度,另一方面会增加业务系统的负载。
因此,当我们使用分库分表模式时,需要根据具体的业务场景,合理地设置分片策略、设置分片字段,这将会在本文的后续章节中介绍。
2、分布式事务
我们知道,数据库提供了事务的功能,以保证数据一致性。然而,这种事务只是针对单数据库而言的,数据库厂商并未提供跨库事务。因此,当我们使用了分库分表之后,就需要我们在业务系统层面实现分布式事务。关于分布式事务的详细内容,可以参考笔者的另一篇文章《常用的分布式事务解决方案》。
现有分库分表中间件的横向对比
- Cobar实现数据库的透明分库,让开发人员能够在无感知的情况下操纵数据库集群,从而简化数据库的编程模型。然而Cobar仅实现了分库功能,并未实现分表功能。分库可以解决单库IO、CPU、内存的瓶颈,但无法解决单表数据量过大的问题。此外,Cobar是一个独立运行的系统,它处在应用系统与数据库系统之间,因此增加了额外的部署复杂度,增加了运维成本。
- 为了解决上述问题,Cobar还推出了一个Cobar-Client项目,它只是一个安装在应用程序的Jar包,并不是一个独立运行的系统,一定程度上降低了系统的复杂度。但和Cobar一样,仍然只支持分库,并不支持分表,也不支持读写分离。
- MyCat是基于Cobar二次开发的数据库中间件,和Cobar相比,它增加了读写分离的功能,并修复了Cobar的一些bug。但是,MyCat和Cobar一样,都是一套需要独立部署的系统,因此会增加部署的复杂度,提高了后期系统运维的成本。