分库分表按照某种规则将数据的集合拆分成多个子集合,数据的完整性被打破,因此在某种场景下会产生多种问题。
扩容与迁移
在分库分表后,如果涉及的分片已经达到了承载数据的最大值,就需要对集群进行扩容。扩容是很麻烦的,一般会成倍地扩容。
通用的扩容方法包括如下5个步骤。
按照新旧分片规则,对新旧数据库进行双写。
将双写前按照旧分片规则写入的历史数据,根据新分片规则迁移写入 新的数据库。
将按照旧的分片规则查询改为按照新的分片规则查询。
将双写数据库逻辑从代码中下线,只按照新的分片规则写入数据。
删除按照旧分片规则写入的历史数据。
这里,在第2步迁移历史数据时,由于数据量很大,通常会导致不一致,因此,先清洗旧的数据,洗完后再迁移到新规则的新数据库下,再做全量对比,对比后评估在迁移的过程中是否有数据的更新,如果有的话就再清洗、迁移,最后以对比没有差距为准。
如果是金融交易数据,则最好将动静数据分离,随着时间的流逝,某个时间点之前的数据是不会被更新的,我们就可以拉长双写的时间窗口,这样在足够长的时间流逝后,只需迁移那些不再被更新的历史数据即可,就不会在迁移的过程中由于历史数据被更新而导致代理不一致。
在数据量巨大时,如果数据迁移后没法进行全量对比,就需要进行抽样对比,在进行抽样对比时要根据业务的特点选取一些具有某类特征性的数据进行对比。
在迁移的过程中,数据的更新会导致不一致,可以在线上记录迁移过程中的更新操作的日志,迁移后根据更新日志与历史数据共同决定数据的最新状态,来达到迁移数据的最终一致性。
分库分表维度导致的查询问题
这里我们分析分库分表中的查询问题。
在分库分表以后,如果查询的标准是分片的主键,则可以通过分片规则再次路由并查询;但是对于其他主键的查询、范围查询、关联查询、查询结果排序等,并不是按照分库分表维度来查询的。
例如,用户购买了商品,需要将交易记录保存下来,那么如果按照买家的纬度分表,则每个买家的交易记录都被保存在同一表中,我们可以很快、很方便地查到某个买家的购买情况,但是某个商品被购买的交易数据很有可能分布在多张表中,查找起来比较麻烦。反之,按照商品维度分表,则可以很方便地查找到该商品的购买情况,但若要查找到买家的交易记录,则会比较麻烦。
所以常见的解决方式如下。
在多个分片表查询后合并数据集,这种方式的效率很低。
记录两份数据,一份按照买家纬度分表,一份按照商品维度分表。
通过搜索引擎解决,但如果实时性要求很高,就需要实现实时搜索。
实际上,在高并发的服务平台下,交易系统是专门做交易的,因为交易是核心服务,SLA的级别比较高,所以需要和查询系统分离,查询一般通过其他系统进行,数据也可能是冗余存储的。
这里再举个例子,在某电商交易平台下,可能有买家查询自己在某一时间段的订单,也可能有卖家查询自己在某一时间段的订单,如果使用了分库分表方案,则这两个需求是难以满足的。因此,通用的解决方案是,在交易生成时生成一份按照买家分片的数据副本和一份按照卖家分片的数据副本,查询时分别满足之前的两个需求,因此,查询的数据和交易的数据可能是分别存储的,并从不同的系统提供接口。
另外,在电商系统中,在一个交易订单生成后,一般需要引用到订单中交易的商品实体,如果简单地引用,若商品的金额等信息发生变化,则会导致原订单上的商品信息也会发生变化,这样买家会很疑惑。因此,通用的解决方案是在交易系统中存储商品的快照,在查询交易时使用交易的快照,因为快照是个静态数据,永远都不会更新,所以解决了这个问题。可见查询的问题最好在单独的系统中使用其他技术来解决,而不是在交易系统中实现各类查询功能;当然,也可以通过对商品的变更实施版本化,在交易订单中引用商品的版本信息,在版本更新时保留商品的旧版本,这也是一种不错的解决方案。
最后,关联的表有可能不在同一数据库中,所以基本不可能进行联合查询,需要借助大数据技术来实现,也就是上面所说的第3种方法,即通过大数据技术统一聚合和处理关系型数据库的数据,然后对外提供查询操作,请参考第5章的内容。
通过大数据方式来提供聚合查询的方式如图所示。
跨库事务难以实现
要避免在一个事务中同时修改数据库db0和数据库db1中的表,因为操作起来很复杂,对效率也会有一定的影响。请参考3.3.3节的内容。
同组数据跨库问题
要尽量把同一组数据放到同一台数据库服务器上,不但在某些场景下可以利用本地事务的强一致性,还可以使这组数据自治。以电商为例,我们的应用有两个数据库db0和db1,分库分表后,按照id维度,将卖家A的交易信息存放到db0中。当数据库db1挂掉时,卖家A的交易信息不受影响,依然可以正常使用。也就是说,要避免数据库中的数据依赖另一数据库中的数据。
END
领取专属 10元无门槛券
私享最新 技术干货