写点什么

面试没有分库分表经验,就看这篇

作者:三十而立
  • 2023-03-17
    湖南
  • 本文字数:3860 字

    阅读完需:约 13 分钟

分库分表的产生背景主要源于大规模互联网应用的需求。随着互联网应用的不断发展,数据量和访问量不断增加,单一的数据库往往无法满足应用的需求。传统的垂直扩展方式(增加更多的硬件资源)存在成本高、扩展能力受限等问题,因此水平扩展方式逐渐成为了主流。


同时,互联网应用的访问模式也日益复杂。传统的主从复制架构难以满足高并发、高可用、高扩展等需求。而分库分表可以通过将数据分散到多个数据库中,并通过一定的策略将请求路由到合适的数据库上,从而提高系统的性能和可用性。


因此,分库分表成为了互联网应用中常用的数据库扩展方案之一。


什么是分库分表


分库分表是一种数据库拆分技术,旨在通过将数据分散存储在多个数据库中,从而提高数据库的性能和扩展性。


分库:一个大型数据库被分成多个较小的数据库,每个数据库被称为“分库”。


分表:每个分库又被进一步分成多个表,每个表被称为“分表”。


这种拆分可以在多个物理服务器上进行,也可以在同一台服务器的不同实例上进行。分库分表的进化


现在我们详细说一下从单库的进化过程和好处。


最开始单数据库,但是随着业务量的提升,整体 QPS 不断增大。假设这时候的单机硬件资源无法再提高,那我们就可以考虑增加从库,将读压力转移到从库上。


但是随着业务进一步增长,主库的写压力也越来越大,这时候会出现两种情况:单表数据量过多,大表查询速度变慢;或者总体的 IOPS 压力已经很大。


对于单表查询压力过大的问题,我们可以判断查询的数据是否可以按照创建时间区分为冷热数据,如果可以优先考虑分区表。如果查询场景复杂我们可以考虑拆表,即使是在同一个数据库中,拆表也可以有效降低索引层级,加快查询速度。但是如果是 IOPS 压力大,则无法很好的缓解这个问题,证明单机性能已经到了瓶颈需要进行分库。


表分区(Table partitioning)和分区表(Partitioned table)是两个相关但不同的概念。
表分区是指将一个大表(Table)划分成更小的、可管理的部分,这些部分被称为分区(Partition)。每个分区都包含表的一部分数据,可以根据不同的分区策略进行划分,例如按时间、按地理位置等。
分区表是指已经进行了分区的表。在分区表中,每个分区都被视为一个独立的表,可以对每个分区进行单独的操作,例如查询、添加、删除等。分区表的分区结构使得数据访问更加高效,可以只查询或操作特定的分区,而不需要扫描整个表。
因此,表分区和分区表的区别在于,表分区是一种数据管理技术,用于将大表划分成更小的、可管理的部分,而分区表是指已经进行了分区的表,可以根据分区结构进行更加高效的数据访问和操作。
为什么一般不使用分区表?
1)分区表如果查询条件没有分区键,很容易出现全表锁 2)一旦数据量并发量上来,如果在分区表实施关联,就是一个灾难 3)自己分库分表,行为更可控
复制代码


一旦可以拆库,那复杂性就会大大提升。之前即使是分表至少写数据可以在一个数据库中进行,有本地事务保证数据的一致性。但是分成多个数据库之后,就失去了这种保证。并且可能因为不知道数据在哪些分库上,在查询数据的时候也有可能会造成访问多个数据库节点,这就需要我们采用合理的数据分配方式、


如果分库逐渐产生了性能问题,我们可以进一步拆表,或者继续拆库。这就需要我们的数据分片方案具有良好的扩展性,为未来的数据量增长提供预方案。当然,我们并不能无限制的去拆库,毕竟存储资源是有限的。


上面的演进思路主要是从性能上来考虑。其实我们还容易忽略一个问题就是可用性上。机器总是有可能发生意外的,尤其现在我们都采用云服务器,我们的数据安全不能依赖于服务商。


除了传统的部署架构层面提升可用性,比如主从结构、主主结构,我们还可以从数据的拆分上来考虑,毕竟鸡蛋不要放在一个篮子里。


如果数据拆成了 4 份,单个数据库发生故障,影响范围就是 25%,如果没有进行分库那就是 100%不可用。、


总的来说,分库分表大大提升了数据库的性能以及可用性。准备阶段判断是否需要分库分表


其实在上一段关于分库分表的进化历程,已经讲述了关于分库分表方案的选择思路。


一般来说,在数据库层我们至少都采用了主从的高可用架构,分区表又不常使用,所以这里更多考虑是采用分库还是分表,这需要根据我们当前系统的具体情况来做选择。


场景方案单表数据量过大,总体 QPS 不高分表单库 IOPS 过高、QPS 过高、数据库连接数不够用分库总体数据量过高、连接数不足分库分表确定分片方案


在我们确定了是选择分库还是分表之后就需要对具体的分片规模做选择,分多少库多少表,这直接关系到我们的资源情况以及数据切分方案。分表数量


如果选择分表方案,需要确定要分多少表,这需要根据我们的数据的增长速度、业务目标以及设计使用年限来做规划。


我们这里以订单系统作为业务背景,假设我们系统初始情况下单日有 1w 订单量,但是按照现有的业务发展速度,计划到年底增加到单日 10w 订单,系统设计目标是 5 年,那么我们就需要按照 10w 来计算。


估算数据量:10w\*365\*5 = 18250w 5年后我们将拥有18250w数据,假设单表500w数据。计算分表数:18250/500=36.5考虑到未来业务的增长,那我们就按照 64 张表来拆分
复制代码


分库数量


如果选择分库,思路跟分表有一些不同,除了考虑数据量的拆分,我们还需要考虑到一些高并发场景,比如搞一些营销活动或者遇到节日。这种情况下业务的 QPS 会大量增加,我们需要根据高峰 QPS 估算业务资源配置情况,适当增加资源。


比如日常峰值 300QPS,但是节日高峰时候会达到 20 倍也就是 6k,如果每个连接的平均查询耗时是 0.2 秒,则每个数据库连接的最大并发数是 5,则平均连接数为 6000/5 = 1200。如果单个数据库最大连接数是 500,那么我们需要至 1200/500=2.4,所以取近似 4 个数据库。


对于数据库最大连接数的配置,我们需要根据库服务器的硬件配置以及结合业务压测来确认。切分维度选择


一般来说,我们对于数据的切分有两个维度,水平切分和垂直切分,当然他们也可以结合使用水平切分


水平切分(Horizontal Sharding)是将一个大型数据库表按照某个规则(如数据范围、哈希值、轮询等)分成多个较小的表,分布在多个数据库节点上。例如,将一个用户表按照用户 ID 分成多个小表,每个小表存储一部分用户数据,分布在不同的数据库节点上。水平切分可以解决单个数据库性能瓶颈和单点故障问题,提高数据库的可扩展性和可用性。


简单来说,水平切分就是按照一定规则把一个表不同行拆分到不同表中。


垂直切分


垂直切分(Vertical Sharding)是将一个大型数据库表按照业务功能(如用户信息、订单信息、商品信息等)拆分成多个表,每个表存储不同的字段和数据。例如,将一个包含用户信息、订单信息和商品信息的大型表拆分成三个小表,每个小表只存储对应的信息。垂直切分可以将数据库的读写压力分散到不同的表和数据库节点上,提高数据库的性能和可维护性。


除了数据量角度,也可以从频繁写入的角度来看。如果一张表有 20 个字段,其中只有 5 个字段需要频繁修改,那么可以考虑把这 5 个字段拆分到子表。避免在修改这 5 个数据时,影响到其余 15 个字段的查询行锁定。


简单来说,垂直切分就是把一张表不同字段拆分到不同的表里。


Sharding 方案


除了在同一个数据库去垂直拆分表,其他方案都逃不过一个问题:选择哪个列作为拆分表的依据?sharding 的算法是什么?选择拆分的列


选择一个合适的列作为分库分表的依据非常重要,我们可以称其为 Sharding Key。他的选择直接影响到了我们的数据分布均匀程度以及以后的查询效率。


通常来说互联网 C 端应用都是直接面对客户,用户在 App 端的操作很多都是在查询自己的数据,所以所以比如用户系统的表做切分,我们可以选择用户 id 这个列来做 Sharding Key。并且用户 id 通常来说都是有序自增的,如果用户量很大的情况下,在不同分片中的数据分布也会较为均匀。


如果是 B 端系统有些场景可能会不一样些。比如现在有一个 ERP 系统,每天会产生大量的计划单数据,但是这些数据主要是给工厂和运营人员去使用的,并没有那么高的并发量。而且这种数据有个特点,只关注近期的数据,这种情况下我们就可以按照创建时间去分片。不同维度数据怎么查询


分库分表一个狠大的弊端就是查询数据非常不方便。


如果是订单系统可能就会更复杂一些,当我们同样使用用户 id 做切分后,但是如果需要按照订单 id 查询呢?那就不知道这个订单属于哪个用户,该从哪个分片去查询,那就只能把每个分片都去查询一遍,可向这个效率有多低。


既然这样,我不用用户 id 作为 sharding key 了,换成订单 id 可以吧。虽然解决了订单 id 的查询问题,但是现在只有用户 id 又怎么查询呢?这显然是行不通的。


首先可以肯定一件事情,每一条订单数据一定包含一个列就是用户 id,我们的问题在于知道订单 id 却不知道在哪个分片,那我们在订单生成规则里附加一下对应用户的 id 信息不就可以了嘛。比如下面的订单 id 生成规则:


6 位日期+2 位版本号+4 位用户 id 后四位+8 位订单号


总结一下:我们的解决思路是把其他维度数据的生成规则中带有 Sharding Key 的一部分,这样就知道了分片信息。


但是如果条件不止一个呢?我还想按照商户维度去查询,希望看到每个商家售卖出的商品数据,或者年度的报表数据。或者按照发货的快递号去查询,快递号都是快递公司生成的,你总不能按照自己的规则去改写把。


这种情况下,可以采用空间换时间的思维去解决。


可以把需要查询的数据同时同步到一个按照商家id分片的读库里,专门用作查询目的。或者可以将数据同步给数仓,数据部门去处理这些信息,然后生成对应的报表信息。或者专门做一个快递单号和订单号的映射表,需要根据快递号查询可以先去这个映射表中找到对应id,然后再找到对应的分库。
复制代码


至于选择什么分片算法,我们下篇继续,请继续关注!

用户头像

三十而立

关注

还未添加个人签名 2023-02-06 加入

还未添加个人简介

评论

发布
暂无评论
面试没有分库分表经验,就看这篇_Java_三十而立_InfoQ写作社区