架构系列十五(分库分表设计实现方案思考)

108 阅读9分钟

1.引子

今天应用系统的建设,很多时候单库难以再满足业务诉求了,主要在于业务形态的变化,业务体量倍增,不管是B端系统,还是C端系统都需要去应对高并发、海量数据处理等业务诉求。

于是我们看到了,技术圈的小伙伴们对云原生、容器化、DDD、微服务、中台等词语都能耳熟能详。这些都是为了赶时髦吗?不,我们理解为是业务诉求的需要,造就了技术生态趋势的发展!正是因为这样,原来在一些大厂才有机会去实践的技术解决方案,今天我们都有机会去实践了,这是好事儿!

那么,接下来我想我们一起来探讨关于数据持久层的解决方案:分库分表设计实现方案。关于分库分表,很多小伙伴都知道,没做过也听过,对吧!我们主要从以下几个方面来探讨

  • 为什么要分库分表
  • 有哪些分库分表方案、以及策略
  • 分库分表成本收益问题

2.案例

2.1.为什么要分库分表

我们来假设一个问题:为什么要分库分表?如果说有一个商城系统,是一个All in one的系统,它的架构如下图

image.png

我们看到整个应用架构中,用户、商品、订单、库存等,都共用一个数据库,这即是单体应用的典型架构。

这在系统初期,事实上是合理的,因为早期用户量、业务量都不大,一个单一的数据库完全可以支撑起整个应用,且这个时候整个应用架构足够简单,因此我们说它是合理的。

但是随着业务的发展,直到某一天我们发现数据库服务器资源压力比较大,比如说通过监控观察到CPU负载、内存、磁盘IO都非常高。我们知道,数据库服务器发生资源瓶颈了。

此时应用端的表现是响应延迟高,甚至有时候服务不可用,用户开始投诉等。

这个时候,我们就需要考虑实施分库分表方案了。通过上述顺带回答了为什么要分库分表?分库分表是因为系统压力过大,数据库服务器资源出现瓶颈,我们需要通过分库分表,通过增加系统资源的方式,分担系统压力,从而解决系统性能问题

但是这里需要注意,我们通常都说分库分表,具体到分库、分表其实是两个事情。即需要区分

  • 什么时候需要分库?
  • 什么时候需要分表?

通常意义系统的压力主要来源于两个层面

  • 用户量、业务量大,导致系统并发压力大
  • 数据量大,导致业务处理耗时多(响应延迟大)

那么,我们说当面对系统并发压力大,要解决并发压力的问题,我们通过分库方案,即分库解决的系统绝对并发压力的问题

但是,如果仅仅是数据量大,导致处理单表业务耗时久,响应延迟大的问题,我们通过分表方案,即分表解决的是单表数据量大的问题

你看分库、分表本来是两个事,为什么我们经常都放到一起说:分库分表呢?这是因为系统通常情况下,既有并发压力大的问题,还有单表数据量大的问题。自然就不分家了,这没办法!

搞清楚了什么要分库分表?以及具体分库、分表方案解决的问题域。下面我们来看一个分库分表以后的架构图(以当下主流微服务化的架构呈现)

image.png

上图我们看到,将商城系统拆分为一系列的子服务系统:用户、商品、订单等。其中每个子服务系统,都拥有独立的数据库,这是垂直分库方案。关于更多分库、分表方案,我们在下一个小节来探讨!

2.2.分库分表方案、策略

2.2.1.分库分表各种姿势

上一小节,我们探讨了为什么要分库分表?是为了提升系统服务能力,解决系统性能问题

其中分库是为了应对系统并发问题;分表是为了应对单表数据量过大问题。

这一小节,我们来探讨都有哪些分库分表的方案、以及策略?首先来看分库分表方案

  • 垂直分库
  • 水平分库
  • 垂直分表
  • 水平分表

垂直分库,它首要考虑的是业务归属,比如我们前边的商城系统,按照业务归属划分出一系列子系统:用户子系统、商品子系统、订单子系统等。于是我们看到垂直分库,它的特点每个库的结构、以及数据都是不一样的

水平分库,当系统按照业务归属拆分以后,比如说用户子系统,并发压力还是非常大。这个时候我们通过水平分库,将不同的用户存储在不同的数据库中,从而实现分担用户请求压力,解决并发压力大问题。于是我们看到水平分库,它的特点是每个库的结构都一样,但是存储的数据都不一样

垂直分表,它的思路其实跟垂直分库类似,比如说在一张商品表中,存储着商品的全部属性(商品Id、名称、库存数量、图像地址、商品描述等)。

  • 我们发现在实际业务中,商品Id、名称、库存、图像是业务操作的高频属性
  • 商品描述,是只在查看商品详情的时候需要的业务信息,且商品描述信息通常是一段大文本

这个时候,我们通过垂直分表的方式,将商品Id、名称、库存、图像;与商品描述拆分成两个表,分开存储。带来的收益是80%商品相关业务操作性能的提升,因为避免了每次数据库操作,解析商品描述大文本属性。于是我们看到垂直分表,它的特点是每个表的结构都不一样、以及数据不一样

水平分表,水平分表是解决当单个表的数据量过大,导致每次业务请求响应延迟大的问题。比如说用户数据量过大,我们将单个用户表,拆分成多个用户表,每个表存储一部分用户的数据,从而解决单表数据量过大的问题。于是我们看到水平分表,它的特点是每个表的结构都一样,但是存储的数据都不一样

2.2.2.分库分表策略

理清楚了分库分表的不同姿势,我们再来探讨一下有哪些分库分表的策略呢?

首先我们要探讨为什么需要考虑分库分表策略呢? 举个例子,当我们把用户库,从一个单库,水平拆分成多个库以后,必然面临一个问题:用户请求进来以后,怎么知道当前用户存储在哪一个库?即数据路由的问题

于是我们说,在分库分表方案实施中,数据分片、分区key的选择非常重要!因为后续业务请求,需要通过分区key实现数据路由

这里,项目实践中应用比较多的两种策略是

  • 哈希分片策略
  • 范围分片策略

什么是哈希策略?举个例子,比如说实施用户库水平分库,选择用户Id作为数据分片的key。那么首先将用户Id,通过哈希算法计算出一个值,决定当前用户数据存储在哪一个数据库中,进而实现数据路由。

文字描述有些抽象,看一个图你就明白了

image.png

上图我们选择的哈希算法是求余

  • 选择分区key为:userId
  • 用户库分为2个库:用户库0,用户库1
  • 将userId与2取模求余,余数为0,存储到0号数据库
  • 余数为1,存储到1号数据库

你看哈希策略理解起来,其实并不难,那么哈希策略它的特点是什么呢?

  • 通过哈希算法,实现数据分布均匀,这是哈希策略的优点
  • 扩容、缩容的时候,需要重新计算哈希,进行数据迁移,这是哈希策略的缺点

理解了哈希策略,我们再来看范围分区策略

范围分片策略,它的思想非常简单。比如说,还是用户库实施水平分库,选择用户Id作为数据分片的key。根据key的取值范围

  • 将0到1千万的用户,存储到用户库0
  • 将千万到2千万的用户,存储到用户库1
  • 以此类推......

你看这就是范围数据分片的核心思想,非常简单!那么与哈希分片比较,范围策略的特点是什么呢?

  • 按照范围数据分片,在扩容、缩容的时候,不需要进行数据迁移,这是范围策略的优点
  • 数据分布不均匀,没有人能保证0到1千万的用户更活跃呢?还是1千万到2千万的用户更活跃。这是范围策略的缺点

到这里,相信你应该理解了不同的分库分表策略。那么具体在项目实践中,我们该如何选择呢?我的建议是,结合业务与不同的分片策略,尽量扬长避短

2.3.分库分表成本收益问题

现在,我们理解了这么几个问题

  • 为什么要分库分表?
  • 分库分表都有哪些姿势?
  • 分库分表都有哪些策略?

最后,还有一个问题需要探讨。实施分库分表方案中,在获取了服务能力的提升,以及解决了系统性能问题的同时;

又带来了哪些成本问题呢?这个是我们需要去考虑,任何架构设计方案,最终都是在成本、收益权衡下的最优解

这里我简单抛出需要考虑的一些问题,考虑到探讨解决方案内容太多,就留给小伙伴们去思考了

  • 增加了整体系统架构复杂度,需要考虑数据路由(不管是通过代理层proxy,还是在应用中直接数据源路由),应用架构复杂度都增加了
  • 带来了分布式系统问题,分库分表以后(如何保证业务标识全局唯一,分布式Id的问题;还有分布式事务的问题)
  • 增加了业务实现复杂度,分库分表以后(如何解决跨库join、分页、排序等问题)