好好的系统,为什么要分库分表?

本文为掘金社区首发签约文章,14天内禁止转载,14天后未获授权禁止转载,侵权必究!

大家好,我是小富~

说在前边

今天是《分库分表 ShardingSphere 原理与实战》系列的开篇文章,之前写过几篇关于分库分表的文章反响都还不错,到现在公众号:程序员小富后台不断的有人留言、咨询分库分表的问题,我也没想到大家对于分库分表的话题会这么感兴趣,可能很多人的工作内容业务量较小很难接触到这方面的技能。这个系列在我脑子里筹划了挺久的,奈何手说啥也不干活,就一直拖到了现在。

其实网上关于分库分表相关的文章很多,但我还是坚持出这个系列,主要是自己学习研究,顺便给分享,对于一个知识,不同的人从不同的角度理解的不尽相同。

网上的资料看似很多,不过值得学有价值的得仔细挑,很多时候在筛选甄别的过程中,逐渐的磨灭了本就不高的学习热情。搬运抄袭雷同的东西太多,而且知识点又都比较零碎,很少有细致的原理实战案例。对新手来说妥妥的从入门到放弃,即便有成体系的基本上几篇后就断更了(希望我不会吧!)。

我不太喜欢堆砌名词概念,熟悉我的朋友不难发现,我的文章从来都是讲完原理紧跟着来一波实战操作。学习技术原理必须配合实操巩固一下,不然三天半不到忘得干干净净,纯纯的经验之谈。

上图是我初步罗列的ShardingSphere提纲,在官网文档基础上补充了很多基础知识,这个系列会用几十篇文章,详细的梳理分库分表基础理论,手把手的实战ShardingSphere 5.X框架的功能和解读源码,以及开发中容易踩坑的点,每篇附带代码案例demo,旨在让新手也能看的懂,后续系列完结全部内容会整理成PDF分享给大家,期待一下吧!

话不多说,咱们这就进入正题~

不急于上手实战ShardingSphere框架,先来复习下分库分表的基础概念,技术名词大多晦涩难懂,不要死记硬背理解最重要,当你捅破那层窗户纸,发现其实它也就那么回事。

什么是分库分表

分库分表是在海量数据下,由于单库、表数据量过大,导致数据库性能持续下降的问题,演变出的技术方案。

分库分表是由分库分表这两个独立概念组成的,只不过通常分库与分表的操作会同时进行,以至于我们习惯性的将它们合在一起叫做分库分表。

通过一定的规则,将原本数据量大的数据库拆分成多个单独的数据库,将原本数据量大的表拆分成若干个数据表,使得单一的库、表性能达到最优的效果(响应速度快),以此提升整体数据库性能。

为什么分库分表

单机数据库的存储能力、连接数是有限的,它自身就很容易会成为系统的瓶颈。当单表数据量在百万以里时,我们还可以通过添加从库、优化索引提升性能。

一旦数据量朝着千万以上趋势增长,再怎么优化数据库,很多操作性能仍下降严重。为了减少数据库的负担,提升数据库响应速度,缩短查询时间,这时候就需要进行分库分表。

为什么需要分库?

容量

我们给数据库实例分配的磁盘容量是固定的,数据量持续的大幅增长,用不了多久单机的容量就会承载不了这么多数据,解决办法简单粗暴,加容量!

连接数

单机的容量可以随意扩展,但数据库的连接数却是有限的,在高并发场景下多个业务同时对一个数据库操作,很容易将连接数耗尽导致too many connections报错,导致后续数据库无法正常访问。

可以通过max_connections查看MySQL最大连接数。

show variables like '%max_connections%'
复制代码

将原本单数据库按不同业务拆分成订单库、物流库、积分库等不仅可以有效分摊数据库读写压力,也提高了系统容错性。

为什么需要分表?

做过报表业务的同学应该都体验过,一条SQL执行时间超过几十秒的场景。

导致数据库查询慢的原因有很多,SQL没命中索引、like扫全表、用了函数计算,这些都可以通过优化手段解决,可唯独数据量大是MySQL无法通过自身优化解决的。慢的根本原因是InnoDB存储引擎,聚簇索引结构的 B+tree 层级变高,磁盘IO变多查询性能变慢,详细原理自行查找一下,这里不用过多篇幅说明。

阿里的开发手册中有条建议,单表行数超500万行或者单表容量超过2GB,就推荐分库分表,然而理想和实现总是有差距的,阿里这种体量的公司不差钱当然可以这么用,实际上很多公司单表数据几千万、亿级别仍然不选择分库分表。

什么时候分库分表

技术群里经常会有小伙伴问,到底什么情况下会用分库分表呢?

分库分表要解决的是现存海量数据访问的性能瓶颈,对持续激增的数据量所做出的架构预见性。

是否分库分表的关键指标是数据量,我们以fire100.top这个网站的资源表 t_resource为例,系统在运行初始的时候,每天只有可怜的几十个资源上传,这时使用单库、单表的方式足以支持系统的存储,数据量小几乎没什么数据库性能瓶颈。

但某天开始一股神秘的流量进入,系统每日产生的资源数据量暴增至十万甚至上百万级别,这时资源表数据量到达千万级,查询响应变得缓慢,数据库的性能瓶颈逐渐显现。

以MySQL数据库为例,单表的数据量在达到亿条级别,通过加索引、SQL调优等传统优化策略,性能提升依旧微乎其微时,就可以考虑做分库分表了。

既然MySQL存储海量数据时会出现性能瓶颈,那么我们是不是可以考虑用其他方案替代它?比如高性能的非关系型数据库MongoDB

可以,但要看存储的数据类型!

现在互联网上大部分公司的核心数据几乎是存储在关系型数据库(MySQL、Oracle等),因为它们有着NoSQL如法比拟的稳定性和可靠性,产品成熟生态系统完善,还有核心的事务功能特性,也是其他存储工具不具备的,而评论、点赞这些非核心数据还是可以考虑用MongoDB的。

如何分库分表

分库分表的核心就是对数据的分片(Sharding)并相对均匀的路由在不同的库、表中,以及分片后对数据的快速定位与检索结果的整合。

分库与分表可以从:垂直(纵向)和 水平(横向)两种纬度进行拆分。下边我们以经典的订单业务举例,看看如何拆分。

垂直拆分

1、垂直分库

垂直分库一般来说按照业务和功能的维度进行拆分,将不同业务数据分别放到不同的数据库中,核心理念 专库专用

按业务类型对数据分离,剥离为多个数据库,像订单、支付、会员、积分相关等表放在对应的订单库、支付库、会员库、积分库。不同业务禁止跨库直连,获取对方业务数据一律通过API接口交互,这也是微服务拆分的一个重要依据。

垂直分库很大程度上取决于业务的划分,但有时候业务间的划分并不是那么清晰,比如:电商中订单数据的拆分,其他很多业务都依赖于订单数据,有时候界线不是很好划分。

垂直分库把一个库的压力分摊到多个库,提升了一些数据库性能,但并没有解决由于单表数据量过大导致的性能问题,所以就需要配合后边的分表来解决。

2、垂直分表

垂直分表针对业务上字段比较多的大表进行的,一般是把业务宽表中比较独立的字段,或者不常用的字段拆分到单独的数据表中,是一种大表拆小表的模式。

例如:一张t_order订单表上有几十个字段,其中订单金额相关字段计算频繁,为了不影响订单表t_order的性能,就可以把订单金额相关字段拆出来单独维护一个t_order_price_expansion扩展表,这样每张表只存储原表的一部分字段,通过订单号order_no做关联,再将拆分出来的表路由到不同的库中。

数据库它是以行为单位将数据加载到内存中,这样拆分以后核心表大多是访问频率较高的字段,而且字段长度也都较短,因而可以加载更多数据到内存中,减少磁盘IO,增加索引查询的命中率,进一步提升数据库性能。

水平拆分

上边垂直分库、垂直分表后还是会存在单库、表数据量过大的问题,当我们的应用已经无法在细粒度的垂直切分时,依旧存在单库读写、存储性能瓶颈,这时就要配合水平分库、水平分表一起了。

1、水平分库

水平分库是把同一个表按一定规则拆分到不同的数据库中,每个库可以位于不同的服务器上,以此实现水平扩展,是一种常见的提升数据库性能的方式。

例如:db_orde_1db_order_2两个数据库内有完全相同的t_order表,我们在访问某一笔订单时可以通过对订单的订单编号取模的方式 订单编号 mod 2 (数据库实例数) ,指定该订单应该在哪个数据库中操作。

这种方案往往能解决单库存储量及性能瓶颈问题,但由于同一个表被分配在不同的数据库中,数据的访问需要额外的路由工作,因此系统的复杂度也被提升了。

2、水平分表

水平分表是在同一个数据库内,把一张大数据量的表按一定规则,切分成多个结构完全相同表,而每个表只存原表的一部分数据。

例如:一张t_order订单表有900万数据,经过水平拆分出来三个表,t_order_1t_order_2t_order_3,每张表存有数据300万,以此类推。

水平分表尽管拆分了表,但子表都还是在同一个数据库实例中,只是解决了单一表数据量过大的问题,并没有将拆分后的表分散到不同的机器上,还在竞争同一个物理机的CPU、内存、网络IO等。要想进一步提升性能,就需要将拆分后的表分散到不同的数据库中,达到分布式的效果。

数据存在哪个库的表

分库分表以后会出现一个问题,一张表会出现在多个数据库里,到底该往哪个库的哪个表里存呢?

上边我们多次提到过一定规则 ,其实这个规则它是一种路由算法,决定了一条数据具体应该存在哪个数据库的哪张表里。

常见的有 取模算法 、范围限定算法范围+取模算法 、预定义算法

1、取模算法

关键字段取模(对hash结果取余数 hash(XXX) mod N),N为数据库实例数或子表数量)是最为常见的一种路由方式。

t_order订单表为例,先给数据库从 0 到 N-1进行编号,对 t_order订单表中order_no订单编号字段进行取模hash(order_no) mod N,得到余数ii=0存第一个库,i=1存第二个库,i=2存第三个库,以此类推。

同一笔订单数据会落在同一个库、表里,查询时用相同的规则,用t_order订单编号作为查询条件,就能快速的定位到数据。

优点

实现简单,数据分布相对比较均匀,不易出现请求都打到一个库上的情况。

缺点

取模算法对集群的伸缩支持不太友好,集群中有N个数据库实·hash(user_id) mod N,当某一台机器宕机,本应该落在该数据库的请求就无法得到处理,这时宕掉的实例会被踢出集群。

此时机器数减少算法发生变化hash(user_id) mod N-1,同一用户数据落在了在不同数据库中,等这台机器恢复,用user_id作为条件查询用户数据就会少一部分。

2、范围限定算法

范围限定算法以某些范围字段,如时间ID区拆分。

用户表t_user被拆分成t_user_1t_user_2t_user_3三张表,后续将user_id范围为1 ~ 1000w的用户数据放入t_user_1,1000~ 2000w放入t_user_2,2000~3000w放入t_user_3,以此类推。按日期范围划分同理。

优点

  • 单表数据量是可控的
  • 水平扩展简单只需增加节点即可,无需对其他分片的数据进行迁移

缺点

  • 由于连续分片可能存在数据热点,比如按时间字段分片时,如果某一段时间(双11等大促)订单骤增,存11月数据的表可能会被频繁的读写,其他分片表存储的历史数据则很少被查询,导致数据倾斜,数据库压力分摊不均匀。

3、范围 + 取模算法

为了避免热点数据的问题,我们可以对上范围算法优化一下

这次我们先通过范围算法定义每个库的用户表t_user只存1000w数据,第一个db_order_1库存放userId从1 ~ 1000w,第二个库10002000w,第三个库20003000w,以此类推。

每个库里再把用户表t_user拆分成t_user_1t_user_2t_user_3等,对userd进行取模路由到对应的表中。

有效的避免数据分布不均匀的问题,数据库水平扩展也简单,直接添加实例无需迁移历史数据。

4、地理位置分片

地理位置分片其实是一个更大的范围,按城市或者地域划分,比如华东、华北数据放在不同的分片库、表。

5、预定义算法

预定义算法是事先已经明确知道分库和分表的数量,可以直接将某类数据路由到指定库或表中,查询的时候亦是如此。

分库分表出来的问题

了解了上边分库分表的拆分方式不难发现,相比于拆分前的单库单表,系统的数据存储架构演变到现在已经变得非常复杂。看几个具有代表性的问题,比如:

分页、排序、跨节点联合查询

分页、排序、联合查询,这些看似普通,开发中使用频率较高的操作,在分库分表后却是让人非常头疼的问题。把分散在不同库中表的数据查询出来,再将所有结果进行汇总合并整理后提供给用户。

比如:我们要查询11、12月的订单数据,如果两个月的数据是分散到了不同的数据库实例,则要查询两个数据库相关的数据,在对数据合并排序、分页,过程繁琐复杂。

事务一致性

分库分表后由于表分布在不同库中,不可避免会带来跨库事务问题。后续会分别以阿里的Seata和MySQL的XA协议实现分布式事务,用来比较各自的优势与不足。

全局唯一的主键

分库分表后数据库表的主键ID业务意义就不大了,因为无法在标识唯一一条记录,例如:多张表t_order_1t_order_2的主键ID全部从1开始会重复,此时我们需要主动为一条记录分配一个ID,这个全局唯一的ID就叫分布式ID,发放这个ID的系统通常被叫发号器。

多数据库高效治理

对多个数据库以及库内大量分片表的高效治理,是非常有必要,因为像某宝这种大厂一次大促下来,订单表可能会被拆分成成千上万个t_order_n表,如果没有高效的管理方案,手动建表、排查问题是一件很恐怖的事。

历史数据迁移

分库分表架构落地以后,首要的问题就是如何平滑的迁移历史数据,增量数据和全量数据迁移,这又是一个比较麻烦的事情,后边详细讲。

分库分表架构模式

分库分表架构主要有两种模式:client客户端模式和proxy代理模式

客户模式

client模式指分库分表的逻辑都在你的系统应用内部进行控制,应用会将拆分后的SQL直连多个数据库进行操作,然后本地进行数据的合并汇总等操作。

代理模式

proxy代理模式将应用程序与MySQL数据库隔离,业务方的应用不在需要直连数据库,而是连接proxy代理服务,代理服务实现了MySQL的协议,对业务方来说代理服务就是数据库,它会将SQL分发到具体的数据库进行执行,并返回结果。该服务内有分库分表的配置,根据配置自动创建分片表。

如何抉择

如何选择client模式和proxy模式,我们可以从以下几个方面来简单做下比较。

1、性能

性能方面client模式表现的稍好一些,它是直接连接MySQL执行命令; proxy代理服务则将整个执行链路延长了,应用->代理服务->MySQL,可能导致性能有一些损耗,但两者差距并不是非常大。

2、复杂度

client模式在开发使用通常引入一个jar可以; proxy代理模式则需要搭建单独的服务,有一定的维护成本,既然是服务那么就要考虑高可用,毕竟应用的所有SQL都要通过它转发至MySQL。

3、升级

client模式分库分表一般是依赖基础架构团队的Jar包,一旦有版本升级或者Bug修改,所有应用到的项目都要跟着升级。小规模的团队服务少升级问题不大,如果是大公司服务规模大,且涉及到跨多部门,那么升级一次成本就比较高;

proxy模式在升级方面优势很明显,发布新功能或者修复Bug,只要重新部署代理服务集群即可,业务方是无感知的,但要保证发布过程中服务的可用性。

4、治理、监控

client模式由于是内嵌在应用内,应用集群部署不太方便统一处理;proxy模式在对SQL限流、读写权限控制、监控、告警等服务治理方面更优雅一些。

<think>好的,用户现在问的是“为什么要做分库分表?怎么做?”,之前他们已经问过分布式ID生成方式,现在这个问题可能是在进一步探讨分布式系统中的数据存储问题。我需要先明确用户的需求,他们可能是在设计一个高并发的系统,遇到了数据性能瓶颈,或者想要提前了解分库分表的知识来规划架构。 首先,回答“为什么要做分库分表”时,应该从性能瓶颈、数据量过大、高并发访问、可用性等方面入手。用户可能已经遇到了单单表的性能问题,或者预期数据增长很快,需要提前规划。这里需要强调单单表的限制,比如连接数不够、磁盘IO压力大、查询速度变慢等。 然后是“怎么做”,这部分需要结构清晰,分步骤讲解。可能需要包括分库分表的方式(垂直和水平拆分)、分片策略(范围、哈希、一致性哈希等)、中间件选择(如ShardingSphere、MyCat)、数据迁移和扩容问题,以及带来的挑战比如分布式事务、跨查询等。 用户可能还关心实际操作中的注意事项,比如如何选择分片键,如何避免热点问题,扩容时如何平滑迁移数据。此外,他们可能想知道有没有什么工具或框架可以简化分库分表的工作,或者是否有成功案例可以参考。 需要注意的是,用户之前的问题是关于分布式ID生成的,所以分库分表后ID生成可能会更复杂,可能需要结合之前提到的Snowflake或号段模式来确保ID的唯一性,这点可以适当联系起来,帮助用户形成完整的知识体系。 另外,用户可能对分库分表的优缺点不太清楚,需要指出虽然分库分表能解决扩展性问题,但也会带来复杂性,比如跨join困难,事务管理复杂,维护成本增加等。需要权衡利弊,是否真的有必要进行分库分表,或者是否有其他优化手段如读写分离、缓存等可以先尝试。 最后,可能需要给出一些实际的应用场景,比如电商平台的订单表分库分表,用户量大的社交平台分库分表,帮助用户更好地理解何时以及如何实施分库分表。</think>以下是关于分库分表的详细介绍,从核心原因到实施方法,结合场景和案例进行说明: --- ### **一、为什么要做分库分表?** #### **1. 解决单机数据的性能瓶颈** - **存储瓶颈**:单机磁盘容量有限(如MySQL单表数据量超过5000万行,性能明显下降)。 - **计算瓶颈**:高并发查询导致CPU过载(如电商大促时每秒数十万请求)。 - **连接数瓶颈**:单机数据最大连接数有限(如MySQL默认151连接,超出后拒绝服务)。 #### **2. 提升系统扩展性** - 单机数据无法通过无限升级硬件来应对数据量增长。 - 分库分表允许通过增加节点横向扩展,支持业务持续增长。 #### **3. 提高可用性与容灾能力** - 单机故障会导致全不可用,分库分表后故障影响范围缩小(如仅部分分片受影响)。 - 天然支持多机房部署,实现异地容灾。 #### **4. 优化数据管理效率** - 按业务拆分后,不同表可独立维护(如订单和用户分开备份、升级)。 --- ### **二、分库分表的核心策略** #### **1. 垂直拆分** - **分**:按业务模块拆分(如订单、用户、商品)。 **场景**:不同业务团队独立管理数据,减少跨事务。 - **分表**:将宽表拆分为多个字段较少的表(如订单表拆分为订单基础表 + 订单详情表)。 **场景**:减少单行数据大小,提升查询效率。 #### **2. 水平拆分** - **分库分表**:将同一表的数据按规则分散到多个/表中。 **分片键选择**: - **用户ID**:适合用户维度查询(如用户订单)。 - **订单ID**:适合全局唯一ID查询(如订单详情页)。 - **时间**:适合按时间范围查询(如日志表按月分片)。 --- ### **三、分库分表的具体实现方法** #### **1. 分片策略** | **策略** | **原理** | **优缺点** | **场景** | |----------------|-----------------------------------------|----------------------------------------------|----------------------------| | **范围分片** | 按分片键范围划分(如1~100万在DB1,100万+在DB2) | 易扩容,但数据分布不均(可能热点集中在最新分片) | 时间序列数据(日志、订单按年分片) | | **哈希取模** | 对分片键哈希取模(如 `user_id % 4`) | 数据分布均匀,但扩容需迁移大量数据 | 用户ID、订单ID等离散值分片 | | **一致性哈希** | 哈希环动态分配节点,减少扩容时数据迁移量 | 扩容灵活,但实现复杂 | 动态调整分片数量的场景 | #### **2. 分库分表中间件** - **客户端SDK**: - **ShardingSphere**(Apache开源):通过JDBC驱动或Proxy代理实现分片。 - **TDDL**(阿里开源):基于JDBC的轻量级分库分表框架。 - **代理层**: - **MyCat**:通过独立代理服务路由SQL(需维护中间件高可用)。 - **Vitess**(YouTube开源):针对MySQL的集群管理方案,支持Kubernetes部署。 #### **3. 分库分表示例(以订单表为例)** - **分片规则**:按 `order_id` 哈希取模分4个,每个分8张表。 - **路由计算**: ```python db_index = (hash(order_id) % 4) # 确定编号(DB0~DB3) table_index = (hash(order_id) // 4) % 8 # 确定表编号(table0~table7) ``` - **真实表名**:`order_db_{db_index}.order_table_{table_index}` --- ### **四、分库分表的挑战与解决方案** #### **1. 分布式事务** - **问题**:跨操作无法保证ACID(如扣存和生成订单需跨)。 - **解决方案**: - **最终一致性**:通过消息队列异步补偿(如存服务扣减后发MQ通知订单服务)。 - **Seata**(阿里开源):基于两阶段提交(2PC)的分布式事务框架。 #### **2. 跨分片查询** - **问题**:分页、排序、聚合计算需合并多个分片结果(如查询“用户最近3月订单”)。 - **解决方案**: - **冗余数据**:建立全局二级索引表(如ES维护用户ID与订单ID的映射)。 - **并行查询**:中间件向所有分片广播查询请求,内存中合并结果(性能较差,慎用)。 #### **3. 扩容与数据迁移** - **扩容流程**: 1. 双写旧和新,确保新旧数据同步。 2. 迁移历史数据到新(使用工具如`pt-online-schema-change`)。 3. 切换流量到新,停用旧。 - **工具支持**: - **ShardingSphere-Scaling**:自动化分片扩容工具。 - **数据管道**:通过Kafka同步增量数据。 #### **4. 全局唯一ID** - **问题**:分库分表后无法依赖数据自增ID。 - **解决方案**:使用分布式ID生成方案(如Snowflake、Leaf)。 --- ### **五、分库分表的适用场景** | **场景** | **拆分方式** | **示例** | |---------------------------|---------------------|---------------------------------------| | 单表数据量超过5000万行 | 水平分表 | 用户表按 `user_id` 哈希分64张表 | | 业务模块耦合度高 | 垂直分 | 电商系统拆分为订单、商品、支付 | | 高并发读写 | 分分担连接数压力 | 社交App用户动态表按 `user_id` 分16个 | | 数据冷热分离 | 按时间范围分库分表 | 日志表按月分片,旧数据归档到廉价存储 | --- ### **六、分库分表的最佳实践** 1. **避免过度拆分**: - 单不超过500张表,单表数据量控制在千万级。 - 拆分前优先优化索引、缓存、SQL性能。 2. **分片键设计原则**: - 选择高频查询字段(如`user_id`)。 - 避免热点问题(如按时间分片导致新压力大)。 3. **灰度与监控**: - 逐步迁移数据,观察慢查询和连接数变化。 - 监控分片负载均衡(如Prometheus + Grafana)。 --- ### **总结** 分库分表是应对海量数据和高并发的终极手段,但其复杂度极高。**建议优先尝试读写分离、缓存优化、数据参数调优等方案**,仅在单机无法支撑时再考虑分库分表。拆分后需配套解决分布式ID、事务、查询等问题,可借助成熟中间件降低开发成本。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值