1.背景
数据库在使用过程中通常会受到网络资源, CPU, 内存,磁盘等资源的制约。在以上资源确定的前提下,如果上层系统对数据库的使用诉求超出了它的承载能力,比如并发量太高,存储的数据量太大。那么就会导致请求超时、系统响应慢或者数据存储能力不足等问题。
2.方法
为了解决如上问题就需要考虑数据库的扩展,或者表的拆分,期望通过这些方式来降低数据库的负载,提升系统的处理能力。通常的处理方式包括两种,分别是主从复制,分库分表。
3.作用
以上讲述的方法分别解决的问题也是不同的。主从复制解决读多写少,单机读取压力大的问题。分库分表则是解决单机并发大引起的写入能力不足、数据存储量大造成的查询性能低下的问题。本文只讨论分库分表相关问题。
分库分表
通过数据库资源的横向扩展,提升系统对外的整体处理能力、存储能力
降低单表数据量,提升单表查询性能
分散请求,降低单机请求负载
4.方式
分库分表方式总体上分为垂直拆分和水平拆分。垂直拆分包括 垂直分表,垂直分库;水平拆分包括水平分表,水平分库分表;通常的拆分顺序是先垂直分表,再垂直分库,然后水平分表,最后水平分库分表。因为从拆分的难道,以及拆分后系统的复杂度,运维的复杂度来讲,其程度是逐渐递增的。系统复杂度从中间件的引入,分布式事务的处理等方面产生影响;运维复杂度则是从中间件的维护成本上带来影响。几种方式体现了AKF 扩展立方体(《架构即未来》提出,这里不再单独介绍)Y轴扩展(按服务,功能拆分)和Z轴扩展(数据分区)。
垂直拆分:
1.垂直分表
即大表拆小表,体现在单表字段数太多,或者冗余字段数太多的情况。将使用不频繁,数据冗余,字段长度太长(如text类型)的字段拆到扩展表里面。
优点:通过冗余字段和其余字段数量上的减少降低单表的存储占用
缺点:业务处理会存在多次查询或者Join 查询的问题
2.垂直分库
就是将一个库内的表拆分到多个数据库,包括物理拆分和逻辑拆分。物理拆分是拆分到不同机器上,逻辑拆分是分到不同Schema(此种方式不能解决资源限制引起的问题,因此不纳入垂直分库考虑的范围。通常指的垂直分库是指第一种方式)。常用的拆分方式是根据业务模块做拆分,比如用户相关的表拆到用户库,订单相关的表拆到订单库,商品相关的表分到商品库。
为什么这样拆分?
可以想象一下如下场景:一个电商系统,如果订单量激增,那么原本除了需要存储订单,还需存储用户、商品、库存的数据库,就无法存储订单外的数据了,没准订单都存不了,怎么办。 如果下单相关的并发量太大,那么用户、商品、库存相关的请求就无法正常处理,同理也可能订单相关的请求也无法处理,怎么办。通过垂直分库就能解决如上问题
优点:通过资源横向扩展,提升整体存储能力,通过请求分散提升系统整体处理能力
缺点:出现分布式事务问题
水平拆分:
1.水平分表
针对数据量很大的表(如订单表),进行单表横向扩展。通常的方式有范围分片(Range), 哈希分片(Hash),时间分片。
优点:解决单表数据量大导致的查询性能低的问题
缺点:不能解决单表数据量大导致的单机存储空间不足的问题
2.水平分库分表
针对数据量大的表进行水平拆分的同时,将表拆分到不同的物理库。
优点:解决单表数据量大,单机存储不足的问题;并分散请求,解决单机负载高的问题
缺点:单表数据分散到不同数据库,导致数据分散。查询,统计,排序,Join 处理的复杂度增大
分片规则
1.范围分片(Range)
按照ID 的范围进行分片,比如 ID 1 ~ 5000 一个库,ID 5001 ~ 10000 一个库
优点:天然分片,扩展不需要数据迁移
缺点:数据集中,易引起单机负载过大的问题
2.哈希分片(Hash)
先将分片字段Hash ,然后再根据机器数量取模进行分片
优点:数据分散均匀
缺点:不易扩展,扩展需要迁移数据(使用一致性Hash 算法可解决此问题)
3.时间分片
根据时间做分片,比如分成某一年1 ~ 3月,某一年4 ~ 6月两个分片
优点:天然分片,易扩展,也方便做历史数据迁移。适合订单这类跟时间顺序关联强的数据。
缺点:数据集中,易引起单机负载过大的问题。并且单库数据量无法准确确定。
5.场景
垂直拆分:
垂直分库在传统的单体系统中,当数据库在并发出现瓶颈,单库存储资源不足等情况下比较适用
垂直分表在传统的单体系统、现在的微服务架构系统,中台架构系统里。当表设计不合理(字段过多,冗余字段太多,存在长度太长字段)的情况下适用
水平拆分:
水平分表在传统单体系统、现在微服务架构系统、中台架构系统中,出现单表数据量太大,单机数据存储不足,单机请求负载太大的情况下适用。但通常作为主库数据来讲,数据量大和请求量大是成正比的。因此这种场景通常不在考虑范围内。
水平分库分表在传统的单体系统中,如今的微服务架构或者中台架构场景中。当单机并发量大,单表数据量大,单机存储资源不足的情况下适用
6.现状
垂直分表对于现如今的微服务架构或者中台架构来讲。数据库本身已经根据业务模块进行了拆分,所以需要考虑垂直分库的场景很少;数据库表在设计之初已经考虑表字段的个数多,冗余字段多,长度太长字段的问题,并进行了处理,所以需要垂直分表的场景也很少。
至于水平分表,如上面场景所分析,需要考虑的场景也是很少。
反观现如今系统出现的并发量大,存储资源不足,数据量大等问题。水平分库分表才是亟待解决的问题,如下着重分析一下水平分库分表。
7.什么场景下做分库分表
分库分表是通过分库解决了单机存储限制的问题,以及通过分散请求解决了单机请求压力大的问题。然而分表为分库扩展单机存储能力提供了条件的同时,分表最重要的解决了单表数据量太大的问题。
那么单表数据量大会带来什么问题呢?
对于 MySQL 来讲通常存储用的是InnoDB作为存储引擎。那么它的查询效率受索引B+ 树数据结构的影响。对于众多数据结构来讲,B+树从算法的层面讲被DB来使用是无可挑剔的。通过控制树的高度,减少IO 操作次数来提升查询的效率。但话说回来,算法的优势不在本文讨论的范围内。对于使用B+树作为索引的InnoDB 来讲,查询效率还受Buffer Size的影响。Buffer Size 受内存资源的影响。内存不足的情况下,如果数据量太大就会造成磁盘检索次数增多,造成IO操作变多,那么查询效率就低了。
那么数据量达多大需要进行分库分表?
从行业经验来讲通常对于MySQL数据量达500W或者存储达2G 的表,或者未来三年会达到这个量级的表就需要做分库分表。当然这也是行业跟常用的系统资源做出的一个初略判断准则。具体量级受系统资源限制。不过这个准则可以作为我们系统架构的一个判断标准,为系统的未来业务承载量做好准备
8.举例
本文以电商领域比较常见的订单(Order)场景分析。
8.1 分片规则:
订单在电商领域通常数据量是很大的。并且有个特性是通常会以时间维度检索近段时间内的数据。那么采用时间分片的策略对表进行分库分库。
8.2 中间件选择:
8.2.1 中间件介绍
1. Sharing - JDBC 是当当开源的轻量级的分库分表中间件,在Java 的JDBC 层提供额外服务
优点:轻量级,无须单独部署服务,不存在单点问题
缺点: 应用感知分库分表,对代码有侵入性
2. MyCat 是一个实现了MySQL协议的分库分表数据库代理中间件
优点:分库分表对应用透明,对业务无侵入性
缺点: 存在单点问题,需要考虑高可用架构;多一层增大网络开销
8.2.2 选择结果:
本着中间件尽量不对代码造成侵入,让应用对分库分表无感知的原则。这里选择MyCat作为分库分表中间件。
8.3 问题
随着数据库的分库分表,一些问题也随之而来,常见的问题有如下几种
-
分布式全局唯一ID
数据拆分后,原有的数据库自增主键已无法保证自增主键的全局唯一性,因此MyCat 提供了全局唯一ID生成机制。全局唯一ID 生成的方式如下:
-
本地文件方式:此方式 MyCat 将 sequence 配置到文件中,当使用到 sequence 中的配置后,MyCat 会更新classpath 中 sequence_conf.properties 文件里 sequence 的当前值。(不推荐)
优点:本地加载,读取速度较快。
缺点:当 MyCat 重新发布后,配置文件中的 sequence 会恢复到初始值。
-
数据库方式:在数据库中建立一张表,存放 sequence 名称(name),sequence 当前值(current_value),步长(increment 是int 类型,表示每次读取多少个 sequence,假设为 K)等信息;
优点:能够持久化序列号
缺点:依赖数据库,数据库的稳定性影响生成序列号生成。如果读取序列号之后,MyCat 服务重启了,会造成序列号不连续
-
本地时间戳: 64 位二进制 (42(毫秒)+5(机器 ID)+5(业务编码)+12(重复累加) 类似雪花算法
优点:不需要依赖外部系统,能支持高并发高场景ID 的生成
缺点:高可用时需要同步全局时钟,否则可能因为时间回溯导致ID重复
-
-
分布式事务
数据拆分后会面临分布式事务问题,因此需要分布式的处理方案。
- MyCat支持XA分布式事务(配合MySQL 版本5.7及以上)
-
跨库Join
Join 是关系型数据库中最常用的一个特性,然而在分布式环境中, 跨分片的 Join 却是最复杂的,最难解决的一 个问题。因此建议能不用Join尽量不用。同时MyCat 也提供了几种Join处理方案
全局表:业务系统中通常存在一些字典表。这些字典表存在变动频率低,数据量小(不超过10万)等特点。MyCat 将这些表定义为全局表。将字典表或者符合字典表特性的一些表定义为全局表。通过此方式,很好的解决了数据 Join 的难题。以全局表 + 基于 E-R 关系的分片策略,MyCat可以满足 80%以上的企业应用开发。
ER Join:MyCat 借鉴了 NewSQL 领域的新秀 Foundation DB 的设计思路,其将子表的存储位置依赖于主表,并且物理上紧邻存放,因此彻底解决了 Join 的效率和性能问题,根据这一思路,提出了基于 E-R 关系的数据分片策略,子表的记录与所关联的父表记录存放在同一个数据分片上。例如订单作为父表,订单明细作为子表。存储明细时讲数据存到对应订单主表的分片上。
Share Join:Share Join 是一个简单的跨分片 Join。目前支持 2 个表的 Join, 原理就是解析 SQL 语句,拆分成单表的 SQL 语句执行,然后把各个节点的数据汇集。
Spark/Storm 对 Join 扩展: MyCat 后续的功能会引入 Spark 和 Storm 来做跨分片的 join。大致流程是这样的: MyCat 调用 Spark, Storm的 API, 先把数据传送到 Spark, Storm中; 然后在 Spark, Storm 里面进行 Join; 之后把数据传回 MyCat ; 最后MyCat 再返回给客户端。
-
跨库 分页、group by、order by、sum、count、max
MyCat 支持数据的多片自动路由与聚合,支持sum, count, max等常用的聚合函数, 支持跨库分页
分页: 对于limit n,limit m,n 查询均有处理方案。对limit n 的形式 MyCat 会返回最先回复结果的分片数据,这样可能会引起每次返回的结果不一样。因此这种查询需要加上排序。 对于limit m,n,MyCat会改写sql ,在每个节点执行limit m+n ,再在MyCat 内部做最小堆运算。这种情况下需要开启非堆内存,否则很容易造成OOM。
group by、order by、sum、count、max:均会在每个分片内执行,然后在Mycat 内部进行处理
9. 结语
追本溯源分库分表是一项实现难度高,维护成本大的技术。它的设计初衷是为了解决大型系统数据库遇到的瓶颈问题。不到万不得已不要轻易使用。
任何技术选型都不能脱离实际问题去思考。所有技术都是围绕它要解决的问题而诞生的,因此技术的选型应该明确当前的场景是什么,要解决的问题是什么,此种技术是否能解决这个问题,有没有更简单、更易维护、更易上手的技术解决此问题。
没有万能的技术,只有解决对应问题的技术。任何一项高大上的技术用在了错误的场景,结果就是添油加醋,得不偿失
参考文档
MyCat 权威指南: http://www.mycat.org.cn/document/mycat-definitive-guide.pdf