数据库中间件

数据库中间件

1 主流数据库中间件设计方案

https://www.sohu.com/a/336811296_505827

我们首先回顾一下在单个库的情况下,应用的架构,可以用下图进行描述:

img

1.1 设计方案

img

可以看到不论是proxy还是smart-client,底层都操作了多个数据库实例。不论是分库分表,还是读写分离,都是在数据库中间件层面对业务开发同学进行屏蔽

1.1.1 proxy模式

我们独立部署一个代理服务,这个代理服务背后管理多个数据库实例。而在应用中,我们通过一个普通的数据源(c3p0、druid、dbcp等)与代理服务器建立连接,所有的sql操作语句都是发送给这个代理,由这个代理去操作底层数据库,得到结果并返回给应用。在这种方案下,分库分表和读写分离的逻辑对开发人员是完全透明的。

优点:

1 多语言支持。也就是说,不论你用的php、java或是其他语言,都可以支持。以mysql数据库为例,如果proxy本身实现了mysql的通信协议,那么你可以就将其看成一个mysql 服务器。mysql官方团队为不同语言提供了不同的客户端却动,如java语言的mysql-connector-java,python语言的mysql-connector-python等等。因此不同语言的开发者都可以使用mysql官方提供的对应的驱动来与这个代理服务器建通信。

2 对业务开发同学透明。由于可以把proxy当成mysql服务器,理论上业务同学不需要进行太多代码改造,既可以完成接入。

缺点:

1 实现复杂。因为proxy需要实现被代理的数据库server端的通信协议,实现难度较大。通常我们看到一些proxy模式的数据库中间件,实际上只能代理某一种数据库,如mysql。几乎没有数据库中间件,可以同时代理多种数据库(sqlserver、PostgreSQL、Oracle)。

2 proxy本身需要保证高可用。由于应用本来是直接访问数据库,现在改成了访问proxy,意味着proxy必须保证高可用。否则,数据库没有宕机,proxy挂了,导致数据库无法正常访问,就尴尬了。

3 租户隔离。可能有多个应用访问proxy代理的底层数据库,必然会对proxy自身的内存、网络、cpu等产生资源竞争,proxy需要需要具备隔离的能力。

1.1.2 smart-client模式

业务代码需要进行一些改造,引入支持读写分离或者分库分表的功能的sdk,这个就是我们的smart-client。通常smart-client是在连接池或者driver的基础上进行了一层封装,smart-client内部与不同的库建立连接。应用程序产生的sql交给smart-client进行处理,其内部对sql进行必要的操作,例如在读写分离情况下,选择走从库还是主库;在分库分表的情况下,进行sql解析、sql改写等操作,然后路由到不同的分库,将得到的结果进行合并,返回给应用。

优点:

1 实现简单。proxy需要实现数据库的服务端协议,但是smart-client不需要实现客户端通信协议。原因在于,大多数据数据库厂商已经针对不同的语言提供了相应的数据库驱动driver,例如mysql针对java语言提供了mysql-connector-java驱动,针对python提供了mysql-connector-python驱动,客户端的通信协议已经在driver层面做过了。因此smart-client模式的中间件,通常只需要在此基础上进行封装即可。

2 天然去中心化。smart-client的方式,由于本身以sdk的方式,被应用直接引入,随着应用部署到不同的节点上,且直连数据库,中间不需要有代理层。因此相较于proxy而言,除了网络资源之外,基本上不存在任何其他资源的竞争,也不需要考虑高可用的问题。只要应用的节点没有全部宕机,就可以访问数据库。(这里的高可用是相比proxy而言,数据库本身的高可用还是需要保证的)

缺点:

1 通常仅支持某一种语言。例如tddl、zebra、sharding-jdbc都是使用java语言开发,因此对于使用其他语言的用户,就无法使用这些中间件。如果其他语言要使用,那么就要开发多语言客户端。

2 版本升级困难。因为应用使用数据源代理就是引入一个jar包的依赖,在有多个应用都对某个版本的jar包产生依赖时,一旦这个版本有bug,所有的应用都需要升级。而数据库代理升级则相对容易,因为服务是单独部署的,只要升级这个代理服务器,所有连接到这个代理的应用自然也就相当于都升级了。

1.2 业界产品

无论是proxy,还是smart-client,二者的作用都是类似的。以下列出了这两种方案目前已有的实现以及各自的优缺点:

img

proxy实现

目前的已有的实现方案有:

  • 阿里巴巴开源的cobar
  • 阿里云上的drds
  • mycat团队在cobar基础上开发的mycat
  • mysql官方提供的mysql-proxy
  • 奇虎360在mysql-proxy基础开发的atlas(只支持分表,不支持分库)
  • 当当网开源的sharing-sphere

目前除了mycat、sharing-sphere,其他几个开源项目基本已经没有维护,sharing-sphere前一段时间已经进去了Apache 软件基金会孵化器。

smart-client实现

目前的实现方案有:

  • 阿里巴巴开源的tddl,已很久没维护
  • 大众点评开源的zebra,大众点评的zebra开源版本代码已经很久没有更新,不过最近美团上市,重新开源大量内部新的功能特性,并计划长期维持。
  • 当当网开源的sharding-jdbc,目前算是做的比较好的,文档资料比较全。和sharding-sphere一起进入了Apache孵化器。
  • 蚂蚁金服的zal
  • 等等

1.3 读写分离核心要点

3.1 基本路由功能

基本路由路功能主要是解决,在读写分离的情况下,如何实现一些基本的路由功能,这个过程通常可以通过下图进行描述:

img

3.1.1 sql类型判断

主要是判断出来sql是读还是写sql,将读sql到从库上去执行,写sql去主库上执行

write语句:insert、update、delete、create、alter、truncate…

query语句:select、show、desc、explain…

3.1.2 强制走主库

有的时候,对于一些强一致性的场景,需要写入后,必须能读取到数据。由于主从同步存在延迟,可能会出现主库写入,而从库查不到的情况。这次时候,我们需要使用强制走主库的功能。具体实现上有2种方案:hint 或API

hint,就是开发人员在sql上做一些特殊的标记,数据库中间件识别到这个标记,就知道这个sql需要走主库,如:

/master/select* fromtable_xx

这里的/master/就是一个hint,表示需要走主库。不同的数据库中间件强制走主库的hint可能不同,例如zebra的hint为/zebra:w+/,hint到底是什么样是无所谓的,其作用仅仅就是一个标记而已。之所以将hint写在//中,是因为这是标准的sql注释语法。即使数据库中间件未能识别这个hint,也不会导致sql语法错误。

api:主要是通过代码的方式来添加sql走主库的标识,hint通常只能加在某个sql上。如果我们希望多个sql同时都走主库,也不希望加hint,则可以通过api的方式,其内部主要利用语言的thread local线程上下文特性,如:

ForceMasterHelper.forceMaster //…执行多条sqlForceMasterHelper.clear

在api标识范围内执行的sql,都会走主库。具体API到底应该是什么样,如何使用,也是由相应的数据库中间件来决定的。

特别的,对于一些特殊的sql,例如 select last_insert_id;或者select @@identity等,这类sql总是需要走主库。这些sql是要获得最后一个插入记录的id,插入操作只可能发生在主库上。

3.2 从库路由策略

通常在一个集群中,只会有一个master,但是有多个slave。当判断是一个读请求时,如何判断选择哪个slave呢?

一些简单的选择策略包括:

  • 随机选择(random)
  • 按照权重进行选择(weight)
  • 或者轮训(round-robin)

特别的,对于一些跨IDC(数据中心)部署的数据库集群,通常需要有就近路由的策略,如下图:

img

图中,在IDC2部署了一个master,在IDC1和IDC2各部署了一个slave,应用app部署在IDC1。显然当app接收到一个查询请求时,应该优先查询与其位于同一个数据中心的slave1,而不是跨数据中心去查询slave2,这就是就近路由的概念。

当然一个数据中心内,可能会部署多个slave,也需要进行选择,因此就近路由通常和一些基本的路由策略结合使用。另外,对于就近路由,通常也会有一个层级,例如同机房、同中心、同区域、跨区域等。

3.3 HA、Scalable相关

数据库中间件除了需要具备上述提到的读写分离功能来访问底层的数据库集群。也需要一套支持高可用、动态扩展的体系:

  • 从HA的角度来说,例如主库宕机了,那么应该从从库选择一个作为新的主库。开源的MHA可以帮助我们完成这个事;然而,MHA只能在主库宕机的情况下,完成主从切换,对于仅仅是一个从库宕机的情况下,MHA通常是无能为力的。因此,通常都会在MHA进行改造,使其支持更多的HA能力要求。
  • 从Scalable角度来说,例如读qps实在太高,需要加一些从库,来分担读流量。

事实上,无论是HA,还是Scalable,对于数据库中间件(不论是proxy或者smart-client)来说,只是配置信息发生了变更。

因此,通常我们会将所有的配置变更信息写到一个配置中心,然后配置心中监听这个配置的变更,例如主从切换,只需要把最新的主从信息设置到配置中心;增加从库,把新从库ip、port等信息放到配置中心。数据库中间件通过对这些配置信息变更进行监听,当配置发生变更时,实时的应用最新的配置信息即可。

因此,一个简化的数据库中间件的高可用架构通常如下所示:

img

监控服务对集群进行监控,当发生变更时,将变更的信息push到配置中心中,数据库中间件(proxy或smart-client)接收到配置变更,应用最新的配置。而整个过程,对于业务代码基本是无感知的。

对于配置中心的选择,有很多,例如百度的disconf、阿里的diamond、点评开源的lion、携程开源的apollo等,也可以使用etcd、consul。通常如果没有历史包袱的话,建议使用携程开源的apollo。

特别需要注意的一点是,通常监控服务监控到集群信息变更,推送到配置中心,再到数据库中间件,必然存在一些延迟。对于一些场景,例如主从切换,没有办法做到彻底的业务无感知。当然,对于多个从库中,某个从库宕机的情况下,是可以做到业务无感知的。例如,某个从库失败,数据库中间件,自动从其他正常的从库进行重试。

另外,上图中的HA方案强依赖于配置中心,如果某个数据库集群上建立了很多库,这个集群发生变更时,将会存在大量的配置信息需要推送。又或者,如果数据库集群是多机房部署的,在某个机房整体宕机的情况下(例如光纤被挖断了,或者机房宕机演练),也会存在大量的配置信息需要推送。如果配置中心,推送有延迟,业务会有非常明显的感知。

因此,通常我们会在客户端进行一些轻量级的HA保障。例如,根据数据库返回异常的sqlstate和vendor code,判断异常的严重级别,确定数据库实例能否正常提供服务,如果不能正常提供服务,则自动将其进行隔离,并启动异步线程进行检测数据库实例是否恢复。

最后,很多数据库中间件,也会提供一些限流和降级的功能,计算sql的唯一标识(有些称之为sql指纹),对于一些烂sql,导致数据库压力变大的情况,可以实时的进行拦截,直接抛出异常,不让这些sql打到后端数据库上去。

1.4 分库分表核心要点

从业务开发的角度来说,其不关心底层是否是分库分表了,其还是希望想操作单个数据库实例那样编写sql,那么数据库中间件就需要对其屏蔽所有底层的复杂逻辑。

下图演示了一个数据库表(user表)在分库分表情况下,数据库中间件内部是如何执行一个批量插入sql的:

img

数据库中间件主要对应用屏蔽了以下过程:

  • sql解析:首先对sql进行解析,得到抽象语法树,从语法树中得到一些关键sql信息
  • sql路由:sql路由包括库路由和表路由。库路由用于确定这条记录应该操作哪个分库,表路由用于确定这条记录应该操作哪个分表。
  • sql改写:将sql改写成正确的执行方式。例如,对于一个批量插入sql,同时插入4条记录。但实际上用户希望4个记录分表存储到一个分表中,那么就要对sql进行改写成4条sql,每个sql都只能插入1条记录。
  • sql执行:一条sql经过改写后可能变成了多条sql,为了提升效率应该并发的去执行,而不是按照顺序逐一执行
  • 结果集合并:每个sql执行之后,都会有一个执行结果,我们需要对分库分表的结果集进行合并,从而得到一个完整的结果。

1.4.1 SQL解析

用户执行只是一条sql,并传入相关参数。数据库中间件内部需要通过sql解析器,对sql进行解析。可以将sql解析,类比为xml解析,xml解析的最终结果是得到一个document对象,而sql解析最终得到一个抽象语法树(AST)。通过这个语法树,我们可以很简单的获取到sql的一些执行,例如当前执行的sql类型,查询了那些字段,数据库表名,where条件,sql的参数等一系列信息。

通常来说,对于sql解析,内部需要经过词法(lex)解析和语法(Syntax)解析两个阶段,最终得到一个语法树。

img

SQL解析器的内部实现原理对业务同学是屏蔽的,业务同学也感知不到。一些数据库中间件采用了第三方开源的sql解析器,也有一些自研sql解析器。例如mycat、zebra采用的都是druid解析器,shard-jdbc一开始也用的是druid解析器,后面自研了解析器。目前较为流行的sql解析器包括:

  • FoundationDB SQL Parser
  • Jsqlparser
  • Druid SQL Parser

其中,其中Fdbparser和jsqlparser都是基于javacc实现的。

mycat团队曾经做过一个性能测试,druid解析器的解析性能通常能达到基于javacc生成的sql解析器10~20倍。本人也进行过类似的测试,得出的结论基本一致。

如何对比不同的sql解析器的好坏呢?主要是考虑以下两点:

解析性能:druid最好。

druid采用的是预测分析法,它只需要从字符的第一个到最后一个遍历一遍,就同时完成了词法解析和语法解析,语法树也已经构造完成。

数据库方言:druid支持的最多。

SQL-92、SQL-99等都是标准SQL,mysql/oracle/pg/sqlserver/odps等都是方言,sql-parser需要针对不同的方言进行特别处理。Druid的sql parser是目前支持各种数据语法最完备的SQL Parser。

注:这里说的仅仅是基于Java实现的SQL解析器,druid是比较好的。大部分同学可能知道druid是一个为监控而生的连接池,事实上,druid另一大特性,就是它的SQL解析器。很多开源的数据库中间件,例如zebra、sharding-jdbc等,都使用了druid解析器。(sharding-jdbc后来自研了解析器)。虽然SQL解析是druid的一大亮点,不过github上也因为SQL解析的bug,收到了不少issue。

1.4.2 SQL路由

路由规则是分库分表的基础,其规定了数据应该按照怎样的规则路由到不同的分库分表中。对于一个数据库中间件来说,通常是支持用户自定义任何路由规则的。路由规则本质上是一个脚本表达式,数据库中间件通过内置的脚本引擎对表达式进行计算,确定最终要操作哪些分库、分表。常见的路由规则包括哈希取模,按照日期等。

下图展示了user表进行分库分表后(2个分库,每个分库2个分表),并如何根据id进行路由的规则:

img

路由分则分为:

  • 库规则:用于确定到哪一个分库
  • 表规则:用于确定到哪一个分表

在上例中,我们使用id来作为计算分表、分表,因此把id字段就称之为路由字段,或者分区字段。

需要注意的是,不管执行的是INSERT、UPDATE、DELETE、SELECT语句,SQL中都应该包含这个路由字段。否则,对于插入语句来说,就不知道插入到哪个分库或者分表;对于UPDATE、DELETE、SELECT语句而言,则更为严重,因为不知道操作哪个分库分表,意味着必须要对所有分表都进行操作。SELECT聚合所有分表的内容,极容易内存溢出,UPDATE、DELETE更新、删除所有的记录,非常容易误更新、删除数据。因此,一些数据库中间件,对于SQL可能有一些限制,例如UPDATE、DELETE必须要带上分区字段,或者指定过滤条件。

1.4.3 SQL改写

前面已经介绍过,如一个批量插入语句,如果记录要插入到不同的分库分表中,那么就需要对SQL进行改写。例如,将以下SQL

insertintouser(id,name) values(1,”tianshouzhi”),(2,”huhuamin”), (3,”wanghanao”),(4,”luyang”)

改写为:

insertintouser_1(id,name) values(1,”tianshouzhi”)insertintouser_2(id,name) values(2,”huhuamin”)insertintouser_3(id,name) values(3,”wanghanao”)insertintouser_0(id,name) values(4,”luyang”)

这里只是一个简单的案例,通常对于INSERT、UPDATE、DELETE等,改写相对简单。比较复杂的是SELECT语句的改写,对于一些复杂的SELECT语句,改写过程中会进行一些优化,例如将子查询改成JOIN,过滤条件下推等。因为SQL改写很复杂,所以很多数据库中间件并不支持复杂的SQL(通常有一个支持的SQL),只能支持一些简单的OLTP场景。

当然也有一些数据库中间件,不满足于只支持OLTP,在迈向OLAP的方向上进行了更多的努力。例如阿里的TDDL、蚂蚁的Zdal、大众点评的zebra,都引入了apache calcite,尝试对复杂的查询SQL(例如嵌套子查询,join等)进行支持,通过过滤条件下推,流式读取,并结合RBO(基于规则的优化)、CBO(基于代价的优化)来对一些简单的OLAP场景进行支持。

1.4.4 SQL执行

当经过SQL改写阶段后,会产生多个SQL,需要到不同的分片上去执行,通常我们会使用一个线程池,将每个SQL包装成一个任务,提交到线程池里面并发的去执行,以提升效率。

img

这些执行的SQL中,如果有一个失败,则整体失败,返回异常给业务代码。

1.4.5 结果集合并

结果集合并,是数据库中间件的一大难点,需要case by case的分析,主要是考虑实现的复杂度,以及执行的效率问题,对于一些复杂的SQL,可能并不支持。例如:

对于查询条件:大部分中间件都支持=、IN作为查询条件,且可以作为分区字段。但是对于NIT IN、BETWEEN…AND、LIKE,NOT LIKE等,只能作为普通的查询条件,因为根据这些条件,无法记录到底是在哪个分库或者分表,只能全表扫描。

聚合函数:大部分中间件都支持MAX、MIN、COUNT、SUM,但是对于AVG可能只是部分支持。另外,如果是函数嵌套、分组(GROUP BY)聚合,可能也有一些数据库中间件不支持。

子查询:分为FROM部分的子查询和WHERE部分的子查询。大部分中对于子查询的支持都是非常有限,例如语法上兼容,但是无法识别子查询中的分区字段,或者要求子查询的表名必须与外部查询表名相同,又或者只能支持一级嵌套子查询。

JOIN:对于JOIN的支持通常很复杂,如果做不到过滤条件下推和流式读取,在中间件层面,基本无法对JOIN进行支持,因为不可能把两个表的所有分表,全部拿到内存中来进行JOIN,内存早就崩了。当然也有一些取巧的办法,一个是Binding Table,另外一个是小表广播(见后文)。

分页排序:通常中间件都是支持ORDER BY和LIMIT的。但是在分库分表的情况下,分页的效率较低。例如对于limit 100,10 ORDER BY id。表示按照id排序,从第100个位置开始取10条记录。那么,大部分数据库中间件实际上是要从每个分表都查询110(100+10)条记录,拿到内存中进行重新排序,然后取出10条。假设有10个分表,那么实际上要查询1100条记录,而最终只过滤出了10记录。因此,在分页的情况下,通常建议使用"where id > ? limit 10”的方式来进行查询,应用记住每次查询的最大的记录id。之后查询时,每个分表只需要从这个id之后,取10条记录即可,而不是取offset + rows条记录。

关于JOIN的特属说明:

Binding Table:

适用于两个表之间存在关联关系,路由规则相同。例如,有user表和user_account表,由于user_account与user表强关联,我们可以将这两个表的路由规则设置为完全一样,那么对于某个特定用户的信息,其所在的user分表和user_account分表必然唯一同一个分库下,后缀名相同的分表中。在join时,某一个分库内的join,就可以拿到这个用户以及账号的完整信息,而不需要进行跨库join,这样就不需要把用户的数据库拿到内存中来进行join。

img

小表广播:

小表广播通常是某一个表的数据量比较少, 例如部门表department。另外一个表数据量比较大,例如user。此时user需要进行分库分表,但是department不需要进行分库分表。为了达到JOIN的目的,我们可以将 department表在每个分库内都实时同步一份完整的数据。这样,在JOIN的时候,数据库中间件只需要将分库JOIN的结果进行简单合并即可。

下图演示了小表广播的流程,用户在更新department表时,总是更新分库db0的department表,同步组件将变更信息同步到其他分库中。

img

注:图中的同步组件指的是一般是伪装成数据库的从库,解析源库binlog,插入目标库。有一些开源的组件,如canal、puma可以实现这个功能,当然这些组件的应用场景非常广泛,不仅限于此。笔者曾写过一个系列的canal源码解析文章,目前完成了大部分。

1.4.6 二级索引

通常情况下,分库分表的时候,分区字段只有一个。例如对于用户表user,按照user_id字段进行分区,那么之后查询某个用户的信息,只能根据user_id作为分区字段。使用其他字段,则需要扫描所有分表,效率很低。但是又有根据其他字段查询某个用户信息的需求,例如根据手机号phone_id。

此时,我们可以将按照user_id插入的数据,进行一份全量拷贝。通过同步组件,重新按照phone_id插入到另一个分库分表集群中,这个集群就成为二级索引,或者叫辅维度同步。此后,对于根据user_id的操作,就在原来的分库分表集群中进行操作;根据phone_id的操作,就到二级索引集群中去进行操作。

需要注意的是,对于更新操作,只能操作原集群,二级索引集群只能执行查询操作。原集群的增量数据变更信息,实时的通过同步组件,同步到二级索引集群中。

img

注:这是一个很常见的面试题。阿里的一些面试官,比较喜欢问。一些面试者,可能自己想到了这个方案,因为考虑到这样比较浪费资源,就自行排除了。事实上,这点资源相对于满足业务需求来说,都不是事。

1.4.7 分布式id生成器

在分库分表的情况下,数据库的自增主键已经无法使用。所以要使用一个分布式的id生成器。分布式事务id生成器要满足以下条件:唯一、趋势递增(减少落库时的索引开销)、高性能、高可用。

目前主流的分布式id生成方案都有第三方组件依赖,如:

  • 基于zk
  • 基于mysql
  • 基于缓存

twitter的snowflake算法是一个完全去中心化的分布式id算法,但是限制workid最多能有1024,也就是说,应用规模不能超过1024。虽然可以进行细微的调整,但是总是有数量的限制。

另外,美团之前在github开源了一个leaf组件,是用于生成分布式id的,感兴趣的读者可以研究一下。

这里提出一种支持动态扩容的去中心化分布式id生成方案,此方案的优势,除了保证唯一、趋势递增,没有第三方依赖,支持存储的动态扩容之外,还具有以下优势:

  • 支持按照时间范围查询,或者 时间范围+ip查询,可以直接走主键索引;
  • 每秒的最大序列id就是某个ip的qps等

12位日期+10位IP+6位序列ID+4位数据库扩展位

其中:

12位日期:格式为yyMMddHHmmss,意味着本方案的id生成策略可以使用到2099年,把时间部分前置,从而保证趋势递增。

10位ip:利用ip to decimal算法将12位的ip转为10进制数字。通过ip地址,来保证全局唯一。如果ip地址被回收重复利用了,也不用担心id的唯一性,因为日期部分还在变化。

6位序列id:意味着每秒最多支持生成100百万个id(0~999999)。不足6位前置补0,如000123。

4位数据库扩展位:为了实现不迁移数据的情况下,实现动态扩容,其中2位表示DB,2位表示TB,最多可扩容到10000张表。假设每张表存储1000万数据,则总共可以支持存储1000亿条数据。

关于数据库扩展位实现动态扩容图解:

img

首先明确一点,路由策略始终根据数据库最后四位,确定某一条记录要到哪个分库的哪个分表中。例如xxxx0001,意味着这条记录肯定是在00分库的01分表上。

接着,就要在id的生成策略上做文章。

假设初始状态为两个分库db_00,db_01,每个分库里面有10张分表,tb_00tb_09。此时,业务要保证生成id的时候,始终保证db的两位在0001之间,tb的两位始终在00~09之间。路由策略根据这些id,可以找到正确的分库分表。

现在需要扩容到10个分库,每个分表10个分表。那么DBA首先将新增的分库:db_02db_09创建好,每个分库里面再创建10个分表:tb_01tb_09。业务同学在此基础上,将id生成策略改成:db的两位在00~09之间,tb的两位规则维持不变(只是分库数变了,每个分库的分表数没变)。而由于路由从策略是根据最后四位确定到哪个分库,哪个分表,当这些新的分库分表扩展位id出现时,自然可以插入到新的分库分表中。也就实现了动态扩容,而无需迁移数据。

当然,新的分库分表中,一开始数据是没有数据的,所以数据是不均匀的,可以调整id扩展位中db和tb生成某个值的概率,使得落到新的分库分表中的概率相对大一点点(不宜太大),等到数据均匀后,再重新调整成完全随机。

此方案的核心思想是,预分配未来的可能使用到的最大资源数量。通常,100个分库,每个分库100张分表,能满足绝大部分应用的数据存储。如果100个分库都在不同的mysql实例上,假设每个mysql实例都是4T的磁盘,那么可以存储400T的数据,基本上可以满足绝大部分业务的需求。

当然,这个方案不完美。如果超过这个值,这种方案可能就不可行了。然而,通常一个技术方案,可以保证在510年之间不需要在架构上做变动,应该就算的上一个好方案了。如果你追求的是完美的方案,可能类似于TIDB这种可以实现自动扩容的数据库产品更适合,不过目前来说,TIDB等类似产品还是无法取代传统的关系型数据库的。说不定等到510年后,这些产品更成熟了,你再迁移过去也不迟。

1.4.8 分布式事务

在分库分表的情况下,由于操作多个分库,此时就涉及到分布式事务。例如执行一个批量插入SQL,如果记录要插入到不同的分库中,就无法保证一致性。因此,通常情况下,数据库中间件,只会保证单个分库的事务,也就是说,业务方在创建一个事务的时候,必须要保证事务中的所有操作,必须最终都在一个分库中执行。

事实上,在微服务的架构下,事务的问题更加复杂,如下图

img

Service A在执行某个操作时,需要操作数据库,同时调用Service B和Service C,Service B底层操作的数据库是分库分表的,Service C也要操作数据库。

这种场景下,保证事务的一致性就非常麻烦。一些常用的一致性算法如:paxios协议、raft协议也无法解决这个问题,因为这些协议都是资源层面的一致性。在微服务架构下,已经将事务的一致性上升到了业务的层面。

如果仅仅考虑分库分表,一些同学可能会想到XA,但是性能很差,对数据库的版本也有要求,例如必须使用mysql 5.7,官方还建议将事务隔离级别设置为串行化,这是无法容忍的。

由于分布式事务的应用场景,并不是仅仅分库分表,因此通常都是会有一个专门的团队来做分布式事务,并不一定是数据库中间件团队来做。例如,sharding-jdbc就使用了华为开源的一套微服务架构解决方案service comb中的saga组件,来实现分布式事务最终一致性。阿里也有类似的组件,在内部叫TXC,在阿里云上叫GTS,最近开源到了GitHub上叫fescar(Fast & Easy Commit And Rollback)。蚂蚁金服也有类似的组件,叫DTX,支持FMT模式和TCC模式。其中FMT模式就类似于TXC。

总体来说,实际上TCC更能满足业务的需求,虽然接入更加复杂。关于fescar,最近比较火,这是java写的,具体可以参考:https://github.com/alibaba/fescar。

2 sharding jdbc

3 MySQL Proxy

MySQL Proxy 是 MySQL 官方提供的一个数据库代理层产品,和 MySQL Server 一样,
同样是一个基于 GPL 开源协议的开源产品。可用来监视、分析或者传输他们之间的通讯信
息。他的灵活性允许你最大限度的使用它,目前具备的功能主要有连接路由, Query 分析,
Query 过滤和修改,负载均衡,以及基本的 HA 机制等

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值