最新消息:我们是一群和平年代充满浮躁与抱怨的程序猿,心中充满抱负却无处撒野,明明是一匹野马,却找不到草原。

分库分表,你理解到什么程度呢?

技术经验 goomoon 1194浏览 0评论

随着互联网产业的蓬勃发展,在互联网应用上产生的数据也是与日俱增。产生大量的交易记录和行为记录,它们的存放和分析是我们需要面对的问题。例如:单表中出现了,动辄百万甚至千万级别的数据。“分表分库”就成为解决上述问题的有效工具。今天和大家一起看看,如何进行分表分库以及期间遇到的问题吧。

为什么会分表分库

数据库数据会随着业务的发展而不断增多,因此数据操作,如增删改查的开销也会越来越大。

再加上物理服务器的资源有限(CPU、磁盘、内存、IO 等)。最终数据库所能承载的数据量、数据处理能力都将遭遇瓶颈。

换句话说需要合理的数据库架构来存放不断增长的数据,这个就是分库分表的设计初衷。目的就是为了缓解数据库的压力,最大限度提高数据操作的效率。

数据分表

如果单表的数据量过大,例如千万级甚至更多,那么在操作表的时候就会加大系统的开销。

每次查询会消耗数据库大量资源,如果需要多表的联合查询,这种劣势就更加明显了。

以 MySQL 为例,在插入数据的时候,会对表进行加锁,分为表锁定和行锁定。

无论是哪种锁定方式,都意味着前面一条数据在操作表或者行的时候,后面的请求都在排队,当访问量增加的时候,都会影响数据库的效率。

那么既然一定要分表,那么每张表分配多大的数据量比较合适呢?这里建议根据业务场景和实际情况具体分析。

一般来说 MySQL 数据库单表记录最好控制在 500 万条(这是个经验数字)。既然需要将数据从一个表分别存放到多个表中,那么来看看下面两种分表方式吧。

垂直分表

根据业务把一个表中的字段(Field)分到不同的表中。这些被分出去的数据通常根据业务需要,例如分出去一些不是经常使用的字段,一些长度较长的字段。

一般被拆分的表的字段数比较多。主要是避免查询的时候出现因为数据量大而造成的“跨页”问题。

一般这种拆分在数据库设计之初就会考虑,尽量在系统上线之前考虑调整。已经上线的项目,做这种操作是要慎重考虑的。

水平分表

将一个表中的数据,按照关键字(例如:ID)(或取 Hash 之后)对一个具体的数字取模,得到的余数就是需要存放到的新表的位置。

1

用 ID 取模的分表方式分配记录

ID 分别为 01-04 的四条记录,如果分配到 3 个表中,那么对 3 取模得到的余数分别是:

  • ID:01 对 3 取模余数为 1 ,存到“表 1”。
  • ID:02 对 3 取模余数为 2 ,存到“表 2”。
  • ID:03 对 3 取模余数为 3 ,存到“表 3”。
  • ID:04 对 3 取模余数为 1 ,存到“表 1”。

当然这里只是一个例子,实际情况需要对 ID 做 Hash 之后再计算。同时还可以针对不同表所在的不同的数据库的资源来设置存储数据的多少。针对每个表所在的库的资源设置权值。

用这种方式存放数据以后,在访问具体数据的时候需要通过一个 Mapping Table 获取对应要响应的数据来自哪个数据表。目前比较流行的数据库中间件已经帮助我们实现了这部分的功能。

也就是说不用大家自己去建立这个 Mapping Table,在做查询的时候中间件帮助你实现了 Mapping Table 的功能。所以,我们这里只需要了解其实现原理就可以了。

2

水平拆分还有一种情况是根据数据产生的前后顺序来拆分存放。例如,主表只存放最近 2 个月的信息,其他比较老旧的信息拆分到其他的表中。通过时间来做数据区分。更有甚者是通过服务的地域来做数据区分的。

3

需要注意的是由于分表造成一系列记录级别的问题,例如 Join 和 ID 生成,事务处理,同时存在这些表需要跨数据库的可能性:

  • Join:需要做两次查询,把两次查询的结果在应用层做合并。这种做法是最简单的,在应用层设计的时候需要考虑。
  • ID:可以使用 UUID,或者用一张表来存放生成的 Sequence,不过效率都不算高。UUID 实现起来比较方便,但是占用的空间比较大。Sequence 表的方式节省了空间,但是所有的 ID 都依赖于单表。这里介绍一个大厂用的 Snowflake 的方式。

Snowflake 是 Twitter 开源的分布式 ID 生成算法,结果是一个 long 型的 ID。

其核心思想是:使用 41bit 作为毫秒数,10bit 作为机器的 ID(5 个 bit 是数据中心,5 个 bit 的机器 ID),12bit 作为毫秒内的流水号(意味着每个节点在每毫秒可以产生 4096 个 ID),最后还有一个符号位,永远是 0。

4

排序/分页:数据分配到水平的几个表中的时候,做排序和分页或者一些集合操作是不容易的。

这里根据经验介绍两种方法。对分表的数据先进行排序/分页/聚合,再进行合并。对分表的数据先进行合并再做排序/分页/聚合。

事务:存在分布式事务的可能,需要考虑补偿事务或者用 TCC(Try Confirm Cancel)协助完成,这部分的内容我们下面会为大家介绍。

数据分库

说完了分表,再来谈谈分库。每个物理数据库支持数据都是有限的,每一次的数据库请求都会产生一次数据库链接,当一个库无法支持更多访问的时候,我们会把原来的单个数据库分成多个,帮助分担压力。

这里有几类分库的原则,可以根据具体场景进行选择:

  • 根据业务不同分库,这种情况都会把主营业务和其他功能分开。例如可以分为订单数据库,核算数据库,评论数据库。
  • 根据冷热数据进行分库,用数据访问频率来划分,例如:近一个月的交易数据属于高频数据,2-6 个月的交易数据属于中频数据,大于 6 个月的数据属于低频数据。
  • 根据访问数据的地域/时间范围进行分库。

5

通常数据分库之后,每一个数据库包含多个数据表,多个数据库会组成一个 Cluster/Group,提高了数据库的可用性,并且可以把读写做分离。

Master 库主要负责写操作,Slave 库主要负责读操作。在应用访问数据库的时候会通过一个负载均衡代理,通过判断读写操作把请求路由到对应的数据库。

如果是读操作,也会根据数据库设置的权重或者平均分配请求。另外,还有数据库健康监控机制,定时发送心跳检测数据库的健康状况。

如果 Slave 出现问题,会启动熔断机制停止对其的访问;如果 Master 出现问题,通过选举机制选择新的 Master 代替。

6

 

数据库扩容

分库之后的数据库会遇到数据扩容或者数据迁移的情况。这里推荐两种数据库扩容的方案。

主从数据库扩容

我们这里假设有两个数据库集群,每个集群分别有 M1 S1 和 M2 S2 互为主备。

7

 

由于 M1 和 S1 互为主备所以数据是一样的,M2 和 S2 同样。把原有的 ID %2 模式切换成 ID %4 模式,也就是把两个数据集群扩充到 4 个数据库集群。

负载均衡器直接把数据路由到原来两个 S1 和 S2 上面,同时 S1 和 S2 会停止与 M1 和 M2 的数据同步,单独作为主库(写操作)存在。

这些修改不需要重启数据库服务,只需要修改代理配置就可以完成。由于 M1 M2 S1 S2 中会存在一些冗余的数据,可以后台起服务将这些冗余数据删除,不会影响数据使用。

8

此时,再考虑数据库可用性,将扩展后的 4 个主库进行主备操作,针对每个主库都建立对应的从库,前者负责写操作,后者负责读操作。下次如果需要扩容也可以按照类似的操作进行。

9

双写数据库扩容

在没有数据库主从配置的情况下的扩容,假设有数据库 M1 M2 如下图:

10

需要对目前的两个数据库做扩容,扩容之后是 4 个库如下图。新增的库是 M3,M4 路由的方式分别是 ID%2=0 和 ID%2=1。

11

这个时候新的数据会同时进入 M1 M2 M3 M4 四个库中,而老数据的使用依旧从 M1 M2 中获取。

与此同时,后台服务对 M1 M3,M2 M4 做数据同步,建议先做全量同步再做数据校验。

12

当完成数据同步之后,四个库的数据保持一致了,修改负载均衡代理的配置为 ID%4 的模式。此时扩容就完成了,从原来的 2 个数据库扩展成 4 个数据库。

当然会存在部分的数据冗余,需要像上面一个方案一样通过后台服务删除这些冗余数据,删除的过程不会影响业务。

13

转载请注明:刘召考的博客 » 分库分表,你理解到什么程度呢?

发表我的评论
取消评论
表情

Hi,您需要填写昵称和邮箱!

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址