分库分表笔记
在系统建立之初,通常是单体的形式,随着业务的发展,数据量逐渐增大,业务逐渐复杂,这时单体应用不能有效应对业务的发展和业务量,就需要进行应用的架构调整。目前最流行的的是将一个单体应用按照业务划分为多个服务,这样每个服务相对原来的应用体量更小,更加独立,可以独自发展。
伴随着应用服务的划分,数据库也应该对应进行划分。如果这时仍旧将数据库放在一个库中,数据库将很容易成为瓶颈,很难获得服务划分带来的全部好处,同时也容易导致服务之间的数据耦合。
垂直分
垂直分库
垂直分库指按照服务或者模块的划分,将服务或者模块涉及的数据划分至独立的数据库。这样实现了服务之间的数据解耦,从而可以让各个服务独立发展。同时,相比单体应用,多个服务对应多个库,从资源上看也更加充裕(相似的配置情况下),从而可以支撑更大的业务量和访问量。
垂直分表
垂直分表与服务拆分没有关系。通常体现的是应用的设计优化,例如,在数据库中将 TEXT/BLOB 类型的字段与其他数据拆分存储,从而降低因为大量 IO 而带来的性能瓶颈。又例如,在数据库中将低频访问的字段与高频访问的字段拆分存储,这样可以更好的利用缓存空间,提高缓存使用效率。等等。
水平分
水平分表
在垂直分的基础上,随着业务的发展,单个服务可能也会面临巨量的访问量和数据量。例如,对于用户直接相关的服务(如用户中心,如订单中心)来说,用户量的增加直接导致数据量的增加。这个时候单表的数据量很容易会触发单表容量的上限,在典型的实践中,常用的 MySQL 数据库,当单表数据量达到数百万至千万时,如果数据库操作不能有效利用索引,将会出现显著的性能问题。
此时,可以进行水平分表,即将大表中的数据,按照一定的规则切分,将他们放到一系列相同结构的表之中,每个表存储整个数据集中的一部分。
这样,通过拆分成多个相同数据结构的数据表,避免的单表过大可能导致的性能问题。
但与此同时,也引入了新的问题:
数据访问路由:需要额外的能力实现数据访问的路由功能,确保数据能够被存储至正确的分表中,同时能够准确的被访问的到。访问路由可以通过自己编写路由函数简单实现,也可以通过引入中间件实现。
非分表键查询:进行分表时,分表所依据的字段称为分表键。完成分表之后,对于以非分表键作为条件的查询(以及更新)将无法有效应对。为此,可以:
在分表时考虑所有可能的查询情况,分表设计涵盖所有的查询条件。例如,以用户 ID 作为分表键的同时,订单 ID 中最后若干位设置为用户 ID,这样根据订单 ID 也可以找到分表编号。
采用异构索引。所谓异构索引,即额外存储一份其他查询条件与分表键的映射关系,通过冗余数据实现数据准确定位。例如,以用户 ID 作为分表键,存储订单 ID 与用户 ID 的映射关系,这样可以根据订单 ID 直接定位到用户 ID,然后执行接下来的查询便可以利用分表键实现定位。需要注意的是,异构索引如果同时存储在数据库中,那么可能同时需要考虑异构索引的分表。如果存储在其他存储中,如 Redis 等,则需要考虑与数据库中数据的一致性问题,其本质是分布式数据的一致性问题。
水平分库分表
进行水平分表,还是存在瓶颈,即单库所能利用的硬件资源终归有限,不可能无限扩展。此时如果业务继续发展,则可能需要继续进行调整。
此时,可以在分表的基础上,再进行更粗粒度的划分,将分表按照一定规则划分到若干个库中,这时即为分库分表。
完成分库分表之后,不同的库部署在硬件资源上,这样可以成倍地提升数据库层面可以提供的访问量支撑。
但同时,分库也带来了更进一步的问题:
跨库数据访问。一个应用中很难做到所有业务数据都是按照同一个分表键存储,可能存在其他无关的数据,例如不常变化的参数类数据。同时查询中分库分表的数据可能要与这些数据进行关联查询,这时就需要进行相应的调整。
一种办法是,加入所有其他的数据数据量都不大,并且变化都非常低频,那么,可以将这些数据在每一个分库中都冗余存储一份,这样就可以实现本地关联查询。
另一种办法是,将这些数据放到一个固定的库中,同时调整业务逻辑,对于原先的关联查询等,改为在应用层进行聚合。这种做法潜在的问题是,如果原先的关联查询比较复杂(例如,涉及较多过滤条件,涉及分页、排序等),那么应用层的聚合将非常复杂,甚至不可实现。
分布式数据一致性问题。在分库之前,所有的数据都存储在单个数据库中,此时,可以利用数据库的事务能力实现事务控制,保证数据的一致性。而在分库之后,数据存储在物理隔离的不同数据库中,这时对于设计多张表,同时这些表又是非主键的 DML 操作,将会演化为分布式一致性问题。为此,将不得不引入分布式事务的解决方案。
版权声明: 本文为 InfoQ 作者【luojiahu】的原创文章。
原文链接:【http://xie.infoq.cn/article/c63d460a12e8ffecbcb78a6f8】。文章转载请联系作者。
评论