MySQL - 分库架构方案

181 阅读7分钟
  • 严格意义上来说分库分表应该是3种架构
    (1):分库不分表
    (2):分表不分库
    (3):既分库也分表

[此篇分析的是上述的第一种架构,既 "分库不分表",后续有时间我会在把剩下的两种架构思路也一起分享]

先来看看总体架构图

WX20220122-152251@2x.png

  • 项目在发展到一定规模后势必会面临数据库压力,刚开始做个读写分离数据分表虽然也可以在一定程度上缓解数据库性能压力,但是过段时间后问题依据会出现,因为上述两个方案都存在一个问题,所有的数据都直接或间接堆积在了一台服务器,压力并没有被真正意义上的被分摊,或许也可以考虑一下分库的方案!

  • 站在技术整体架构的角度来说,数据库是业务的核心,牵一发动全身,分库与分表不同,数据表不管是采用垂直拆字段或是水平分割表的方案依旧可以选择放在一台机器上,但分库是直接将不同的数据库按照类型部署到不同的机器上,性能可以得到肉眼可见的上升,存储压力也可以得到释放,但是选择分库方案时不得不考虑如下几个问题

    (1) 当业务有需要时如何进行跨库JOIN?
    (2) 如何进行跨库数据分页?
    (3) 跨库事务如何保证ACID特性?
    (4) 当某台机器因为已知/未知的原因暂时不可用,需要将数据迁移到其他服务器中时是不是所有开发人员都要一起改代码?
    (5) 使用中间件读写性能会不会有影响
    (6) 如何解决数据读写分离时产生的同步延迟
    (7) 如何最大限度的降低代码冗余
     # 也许还有其他的问题

分库衍生问题及解决方案

跨库join、跨库数据分页

   对于这个问题有些同学可能会硬气的说:我都分库了,还要啥join,先查这个在查这个不行吗?

这个回答看上去好像没什么问题,但是部署初期,架构五分钟,堆码两小时,还不包括测试,部署代码,时间上不一定能做到完美衔接。

更重要的一点,从技术角度来看数据库最直接用户是后台程序/服务,如果架构设计时不从全局考虑代码维护性和冗余那项目在之后的开发迭代中一定会变得臃肿进入推倒重构的死循环

当然,愿意堆屎山的当我没说哈[手动滑稽]

所以我的建议可以考虑用 mysql-federated 存储引擎将数据表映射至另外一台或多台的服务器,给程序读取数据

     !!!注意 !!!
    (1):虽然映射库也能执行写操作,但federated引擎不支持事务,所以操作映射的数据表只能用于读取,不要用于写操作【切记】
    (2):federated不支持表结构修改语句,所以更新映射表结构时需要删除它,并重新创建这张映射表 【不会影响数据】
    (3):由于映射数据库只会存储查询结果缓存,真实的数据还是需要从数据源中读取,所以交互存在网络传递的过程,实际架构部署时最好将映射库和存储库放置在一个内网空间
    (4):使用count语句时不要用*最为统计对象,最好直接count主键,性能在federated引擎体现会比较明显
    (5):数据源表修改结构后映射表表并不随之改变,所以修改时需要重新创建并定义映射表
     其他事项可以参考官方文档[https://dev.mysql.com/doc/refman/8.0/en/federated-usagenotes.html]

跨库事务如何保证ACID特性

    这个是分库架构中最重要的问题,如果事务不能解决分库将变得毫无意义

(1)考虑用 sharding-proxy 搭建一个代理数据库,程序的写入请求引导至此,sharding-proxy支持LOCAL, XA, BASE三种分布式事务方式,参考 sharding-proxy数据分片

(2)后端用 mysql-XA事务,方式最直接,架构也不用在调整,但是代码冗余率会很高

(3)后端采用微服务架构,不同的业务代码包去解决不同的问题,代码之间使用RPC或者API方式通讯,这虽然也能解决,但调整周期较长

(这个不是我们今天需要关注的重点,后面单独说)

当某台机器暂时不可用,需要将数据迁移到其他服务器中时是不是所有开发人员都要一起改代码?

遇到这个问题时就体现出数据库中间件和代理层重要性了

架构图上我有说到分库时可以将程序的读写任务分配给sharding-proxy中间件和federated映射库,其实他们两个的角色都是调度SQL请求,不是真正去处理SQL语句,就像nginx和php的关系一样,当数据源出现问题时只需要把配置文件中的连接地址修改会新的地址即可,程序并不需要去改变任何东西

只有代理层和中间件出问题时开发才需要介入,但是这种概率很小

使用中间件是否会影响读写效率

这个问题,对于中间件的角色来说它的核心任务是判断SQL语句类型并将语句传递给对应的服务器做处理,影响中间件效率的主要因素是服务器硬件(CPU),传递过程的网络延迟,数据源对语句的处理效率

(并发压测时你就会看到中间件服务器的CPU会很快占满,但是内存消耗并不大)

数据读写分离时产生的同步延迟

数据源的读写分离延迟问题,我在之前分享的文章里也提到过可以通过升级硬件,降低单条写语句对数据的影响行数,使用多线程复制来相对降低复制延迟

当然,如果业务对延迟完全不能容忍,也可以在配置读写分离时选择 全量同步 的方式来解决,但是这种方式在某个数据源机器少的情况下还好,机器一多时你就会明显感觉到写语句执行效率很慢,因为一条写语句需要等所有master和slave全执行完成才会返回结果给客户端

至少在目前的mysql版本中还没有能做到两全其美的方案

而中间件和代理层对读写的影响就主要来自于硬件和网络了,因为我们不生产数据,我们只是数据的搬运工

如何最大限度的降低代码冗余

使用中间件和代理层的一个很重要的原因就是出于对程序代码冗余的考虑,开发不需要在配置文件中写一大堆的连接信息,在逻辑代码或数据模型中去指定数据库表用哪套连接信息,依然可以像单库一样去写代码,把注意力放在业务逻辑和代码性能上即可。

另外,还有一个重要的原因就是出于安全原因,配置信息最小化暴露是对架构最起码的要求