1.数据库使用阶段
1.1 单库单表
1.2 单库多表(目前阶段)
1.3 多库多表
2.什么情况下需要分库分表
数据库表中的数量达到了一定的量级
数据库吞吐量达到了瓶颈,需要增加数据库实例开分解大量数据库请求带来的系统压力
扩容时对应用层的配置改变最少,需要在每个数据库实例中预留足够的数据库数量
3.解决方案
- 3.1 拆分的方式:垂直拆分和水平拆分
垂直拆分是指,将一个属性较多,一行数据较大的表,将不同的属性拆分到不同的表中,以降低单库(表)大小,达到提升性能的目的的方法,垂直切分后,各个库(表)的特点是:
(1)每个库(表)的结构都不一样
(2)一般来说,每个库(表)的属性至少有一列交集,一般是主键
(3)所有库(表)的并集是全量数据
水平切分是指,以某个字段为依据(例如uid),按照一定规则(例如取模),将一个库(表)上的数据拆分到多个库(表)上,以降低单库(表)大小,达到提升性能的目的的方法,水平切分后,各个库(表)的特点是:
(1)每个库(表)的结构都一样
(2)每个库(表)的数据都不一样,没有交集
(3)所有库(表)的并集是全量数据
- 3.2 实现的方式:客户端分片,代理分片和支持实物的分布式数据库
- 3.2.1 客户端分片
具体实现方式:在应用层直接实现,通过制定JDBC协议实现,通过定制ORM框架实现
应用层直接实现
直接在应用层读取分片规则,然后解析分片规则,根据分片规则实现切分的路由逻辑,从应用层直接决定每次操作应该使用哪个数据库实例,数据库以及数据库表;这种实现方案虽然入侵了业务,但是实现起来比较简单,适合快速上线,而且切分逻辑由自己开发,如果在生产上产生了问题,比较容易解决
制定JDBC协议实现
在应用层直接实现会入侵应用的业务实现,对开发人员的能力要求较高,为了开发人员把精力集中在业务逻辑的开发上,可以通过制定jdbc协议来实现,保证业务层接口透明,Sharding JDBC便采用了这种方案
通过定制ORM框架实现
把分片规则实现放到ORM框架中或者通过ORM框架支持的机制来实现分库分表的逻辑,通用的做法是在Mybatis配置文件中增加表索引的参数来实现分片
- 3.2.2 代理分片
代理分片是在应用层和数据库层增加一个代理层,把分片的路由规则配置放在代理层,代理层对外提供与JDBC兼容的接口跟应用层,只需要关系业务逻辑实现,待业务逻辑实现后,在代理层配置路由规则即可,缺点是增加了代理层,尽管代理层是轻量级的转发协议,但是要实现JDBC协议的解析,并且通过分片的路由规则来路由请求,对每个数据库操作都增加了一层网络传输,对性能是有影响的,需要维护增加的代理层,也有硬件成本,还要有解决bug的技术专家,成本比价高,Cobar和MyCat是通过此种方式实现
- 3.2.3 支持事务的分布式数据库
市面上现有的产品如OceanBase,TiDB等都对外提供可伸缩的体系架构,并提供一定的分布式事务支持,将可伸缩的特点和分布式事务的实现包装到分布式数据库内部实现,对使用者透明,例如:TiDB对外提供JDBC接口,让应用层像使用MySQL一样来使用TiDB,而不用关注内部是如何伸缩,分片及处理分布式事务的,此类数据库适合非交易系统,例如大数据日志系统,统计系统,查询系统等
4.引起的问题
- 4.1 扩容与迁移
在分库分表后,如果涉及的分片已经达到了承载数据的最大值,就需要对集群进行扩容。扩容是很麻烦的,一般会成倍地扩容。
通用的扩容方法包括如下5个步骤。
(1)按照新旧分片规则,对新旧数据库进行双写。
(2)将双写前按照旧分片规则写入的历史数据,根据新分片规则迁移写入新的数据库。
(3)将按照旧的分片规则查询改为按照新的分片规则查询。
(4)将双写数据库逻辑从代码中下线,只按照新的分片规则写入数据。
(5)删除按照旧分片规则写入的历史数据。
这里,在第2步迁移历史数据时,由于数据量很大,通常会导致不一致,因此,先清洗旧的数据,洗完后再迁移到新规则的新数据库下,再做全量对比,对比后评估在迁移的过程中是否有数据的更新,如果有的话就再清洗、迁移,最后以对比没有差距为准。
如果是金融交易数据,则最好将动静数据分离,随着时间的流逝,某个时间点之前的数据是不会被更新的,我们就可以拉长双写的时间窗口,这样在足够长的时间流逝后,只需迁移那些不再被更新的历史数据即可,就不会在迁移的过程中由于历史数据被更新而导致代理不一致。
在数据量巨大时,如果数据迁移后没法进行全量对比,就需要进行抽样对比,在进行抽样对比时要根据业务的特点选取一些具有某类特征性的数据进行对比。
在迁移的过程中,数据的更新会导致不一致,可以在线上记录迁移过程中的更新操作的日志,迁移后根据更新日志与历史数据共同决定数据的最新状态,来达到迁移数据的最终一致性。
在分库分表以后,如果查询的标准是分片的主键,则可以通过分片规则再次路由并查询;但是对于其他主键的查询、范围查询、关联查询、查询结果排序等,并不是按照分库分表维度来查询的。
- 4.2 查询问题
例如,用户购买了商品,需要将交易记录保存下来,那么如果按照买家的纬度分表,则每个买家的交易记录都被保存在同一表中,我们可以很快、很方便地查到某个买家的购买情况,但是某个商品被购买的交易数据很有可能分布在多张表中,查找起来比较麻烦。反之,按照商品维度分表,则可以很方便地查找到该商品的购买情况,但若要查找到买家的交易记录,则会比较麻烦。
所以常见的解决方式如下。
(1)在多个分片表查询后合并数据集,这种方式的效率很低。
(2)记录两份数据,一份按照买家纬度分表,一份按照商品维度分表。
(3)通过搜索引擎解决,但如果实时性要求很高,就需要实现实时搜索。
实际上,在高并发的服务平台下,交易系统是专门做交易的,因为交易是核心服务,SLA的级别比较高,所以需要和查询系统分离,查询一般通过其他系统进行,数据也可能是冗余存储的。
这里再举个例子,在某电商交易平台下,可能有买家查询自己在某一时间段的订单,也可能有卖家查询自己在某一时间段的订单,如果使用了分库分表方案,则这两个需求是难以满足的。因此,通用的解决方案是,在交易生成时生成一份按照买家分片的数据副本和一份按照卖家分片的数据副本,查询时分别满足之前的两个需求,因此,查询的数据和交易的数据可能是分别存储的,并从不同的系统提供接口。
另外,在电商系统中,在一个交易订单生成后,一般需要引用到订单中交易的商品实体,如果简单地引用,若商品的金额等信息发生变化,则会导致原订单上的商品信息也会发生变化,这样买家会很疑惑。因此,通用的解决方案是在交易系统中存储商品的快照,在查询交易时使用交易的快照,因为快照是个静态数据,永远都不会更新,所以解决了这个问题。可见查询的问题最好在单独的系统中使用其他技术来解决,而不是在交易系统中实现各类查询功能;当然,也可以通过对商品的变更实施版本化,在交易订单中引用商品的版本信息,在版本更新时保留商品的旧版本,这也是一种不错的解决方案。
最后,关联的表有可能不在同一数据库中,所以基本不可能进行联合查询,需要借助大数据技术来实现,也就是上面所说的第3种方法,即通过大数据技术统一聚合和处理关系型数据库的数据,然后对外提供查询操作
通过大数据方式来提供聚合查询的方式
4.3 分布式事务
三种解决方案:两段式提交协议,最大努力保证模式和事务补偿机制
4.4 同组数据跨库
要尽量把同一组数据放到同一台数据库服务器上,不但在某些场景下可以利用本地事务的强一致性,还可以使这组数据自治。以电商为例,我们的应用有两个数据库db0和db1,分库分表后,按照id维度,将卖家A的交易信息存放到db0中。当数据库db1挂掉时,卖家A的交易信息不受影响,依然可以正常使用。也就是说,要避免数据库中的数据依赖另一数据库中的数据。