菜鸟笔记
提升您的技术认知

逻辑库、物理库、分库分表(TDDL)

 

逻辑库/逻辑文件:给用户看的(即Database和Table就是我们常说的逻辑库的范畴)

物理库/物理文件:存储在计算机中的(即机器和Port就是我们常说的物理库的范畴。)

一个服务器有多个实例(port);

一个实例有多个Database(db);

一个DB有多张表(Table);

 

一:分布式数据库的演化过程

TDDL是一个分布式数据库中间件,主要为了解决分布式数据产生的相关问题,因此在我们学习TDDL之前我们先了解分布式数据库的演化过程。

1、单库单表

通常刚开始的手,应用的数据比较少,也不会很复杂,所以应用只有一个数据库,数据库中也是一个业务对应一张表,这也是我们刚开始接触数据层的形态。

2、读写分离

随着业务的发展,数据量和访问量不断增加,很多时候业务读多写少,比如:新闻网站,因为数据库的读压力远大于写压力。所以这时候再原来的数据库master基础上增加一个备用数据库slave,备库合主库存储着相同的数据。但是,备库只提供服务,主库提供写服务。以后的写操作已经事务中的读操作就走主库,其他走备库,这就是所谓的读写分离。

读写分离会直接带来两个问题:

1)数据复制问题

因为最新写入的数据只会存储在主库中,之后想要在备库中读取到新数据就必须要从主库复制过来,这会带来一定的延迟,造成短期的数据不一致性。但这个问题应该也没有什么特别好的办法,主要依赖于数据库提供的数据复制机制,常用的是根据数据库日志 bin-log 实现数据复制。

2)数据源选择问题

读写分离之后我们都知道写要找主库,读要找备库,但是程序不知道,所以我们在程序中应该根据 SQL 来判断出是读操作还是写操作,进而正确选择要访问的数据库。

数据量和访问量持续上升,读写分离已经无法满足业务,此时要考虑分库分表。

3、垂直分库

不同表放在不同数据库,但是垂直分库会带来以下问题:

垂直分库会带来以下问题:

1:ACID(事务操作) 被打破

数据分到不同的数据库之后,原来的事务操作将会受很大影响,比如说注册账户的时候需要在用户表和用户信息表中插入一条数据,单机数据库利用事务可以很好地完成这个任务,但是多机将会变得比较麻烦。以下两点也是因为数据库在不同的机子上而导致简单的操作变得复杂。

2:Join 操作困难

3:外键约束受影响

4、水平分库

把一张表分成多张表:A->A1,A2,A3。水平分表会在垂直分库的基础上带来更多的影响:

1)自增主键会有影响

这个影响很明显,分表中如果使用的是自增主键的话,那么就不能产生唯一的 ID 了,因为逻辑上来说多个分表其实都属于一张表,自增主键无法标识每一条数据。

2)有些单表查询会变成多表

比如说 count 操作,原来是一张表的问题,现在要从多张分表中共同查询才能得到结果。

二:TDDL介绍

1、产生背景

数据库从单机走向分布式将会面临很多很多的问题,分布式数据库中间件就是为了解决这些问题而生的。TDDL作为开源分布式数据库中间件之一,由阿里研发,其出现解决了以上分布式数据库需求带来的问题,总结起来如下:

1)单一数据库无法满足性能需求

随着业务的发展,数据量急剧增大,使用单库单表时,数据库压力过大,因此通过分库分表,读写分离的方式,减轻数据库的压力。

2)系统容灾

使用单机数据库时,如果数据库宕机,则无法对外提供服务,因此产生了主备数据库的方案,当主库宕机后,会自动切换到备库,不影响对外的服务。

3)运维管理

直接连接单机数据库时,无法动态的切换数据源,使用TDDL中间件,可以动态的切换数据源。

2、模型结构

TDDL主要部署在ibatis、mybatis或者其他ORM框架之下,jdbc Driver之上。整个中间件实现了JDBC规范,所以可以将TDDL当做普通数据源实例并且注入到各种ORM框架中使用。由下图中的结构模型图可以看出,TDDL是JDBC或者持久框架层与底层JDBC驱动交互的桥梁,或者也可以称之为中转站,起到进出的加工作用。

4、分布式数据库逻辑结构

分布式数据库在经过读写分离和分库分表之后,数据库的结构变得复杂,逻辑上大致如下图所示:

除了最底层的物理DB,上两层的逻辑层是分布式数据库中间件着重要解决的问题,tddl的设计亦是以图中的三层逻辑模型为基础进行的。为了解决每一层所要应对的问题,tddl在结构上至上而下也分了三层,分别是 Matrix 层、Group层以及 Atom 层。具体来说,从逻辑上来看,最顶层是要进行分库分表才过渡到中间层的,分库分表所带来的问题是在 Matrix 层解决 ,包括SQL解释、优化和执行等;中间层是经过读写分离和主备切换才会出现最底层,因此读写分离与主备切换的工作由 Group 层解决;至于 Atom 层,它面对的是实实在在的每一个数据库,更多的工作在与对数据库的连接管理,比如说当数据库的 IP 地址发生改变时,Atom 层要动态感知,以免连接找不到地址。 

5、三层数据源结构(核心组件)

TDDL作为中间件,其作用是根据路由规则,将sql路由到正确的分库、分表上去执行,再将结果进行汇总,返回给用户,对于用户,不需要了解TDDL的原理,可以像使用单库单表一样去使用分布式数据库。在解析执行和封装这些类似于单库单表的SQL语句时,tddl有专门的组件来进行处理,这些组件就是核心的三层数据源,分别是Matrix层、Group层以及Atom层,其结构如下图所示:

 

5.1:Matrix层

功能:分库分表路由,SQL语句的解释、优化和执行,事务的管理规则的管理,各个子表查询出来结果集的Merge等。

常见的数据访问路由算法有以下几种:

a)固定哈希算法

固定哈希就再简单不过了,就是根据某个字段(如整形的 id 或者字符串的 hashcode)对分库的数量或者分表的数量进行取模,根据余数路由到对应的位置。比如A%4=xx

b)一致性哈希算法

固定哈希算法虽然足够简单,但是在数据库扩容的时候不得不考虑成本(本来4个库,现在变成5个库)。假如原有4个库,现在想拆分成 5 个,由于取模的结果发生变化,原来数据库中的绝大部分数据都要进行迁移。

所以一致性哈希算法应景而生,它的原理就是通过该算法计算出 key 的 hashcode 之后对 2^32 取模,那么数据都会落在 0~2^32 所组成的环中;同样的,可以利用一致性哈希算法对机器的唯一属性计算所在位置,然后数据存储在顺时针方向最近的机器上。如图所示:

 

对于一致性哈希,增删机器的成本就降低很多了,比如说在上图 node2 与 node4 之间增加一台机器 node5,那么需要迁移的数据只分布在 node2 与 node5 之间,相比固定哈希来说迁移量小了很多。

c)虚拟节点

一致性哈希已经可以解决大部分需求了,但是对于数据集中在热点的情况,一致性哈希同样面临比较大的挑战。比如说,上图的 node2 与 node4 之间集中了整个环中的大部分数据,当加入 node5 之后,其实起到的效果比较有限,因为还是要有大量的数据进行迁移。引入虚拟节点之后,情况就不一样了,所谓虚拟节点,它就是物理节点的映射,一个物理节点可以复制出多个虚拟节点,尽可能的让它均匀分布在环上,那么即使数据再集中,其实也会存储在不同的节点上,很好地起到了负载均衡的作用。

d)自定义路由规则

这是最不常用的方法,不过 TDDL 也支持,你可以实现自己的算法进行数据访问路由,但一般要么效果很差要么成本很高。

5.2:Group层

作用:读写分离;权重的选择,根据权重选择要去读哪些库;数据保护,数据库down掉后的线程保护, 数据库挂掉后的线程保护,不会因为一个数据库挂掉导致所有线程卡死。

读写分离与主备切换带来的问题是 Group 层解决的核心。首先简单介绍一下主备切换,由于主库或者备库都有可能挂掉,但很小概率同时挂,所以当一方挂掉的时候,Group 层要马上把流量切到另一个库,保证挂掉一个不会让应用挂掉。

读写分离最大的问题是数据复制,通常有两种复制场景,一种是镜像复制,即主库和从库的数据结构是一模一样的,通常根据主库上的日志变化,在从库中执行相同的操作;另外一种是非对称复制,意思就是主库与备库是以不同的方式分库的,它们的结构虽然相同,但是主备库中存储的记录是不相同的,主要目的是查询条件不同时,把请求分发到更加适合的库去操作。举个例子,对于订单数据库,买家会根据自己的 ID 去查自己的交易记录,所以主库可以用买家 ID 分库,保证单个买家的记录在同一个数据库中;但是卖家如果想看交易记录的话可能就得从多个库中进行查询,这时候可以利用卖家 ID 进行分库作为备库,这样一来主备库的复制就不能简单的镜像复制了,在进行复制操作之前还需要进行路由。

5.3:Atom层

Atom 模块真正和物理数据库交互,提供数据库配置动态修改能力。

该层负责动态创建,添加,减少数据源。管理着底层的数据库IP,连接等信息;底层对物理数据库做了代理,对单库的JDBC做了一层封装,执行底层单库的SQL;线程数、执行次数等状态的统计等。

6:执行流程

1)执行流程

 

TDDL的工作流程类似上图,client发送一条SQL的执行语句,会优先传递给Matrix层。由Martix 解释 SQL语句,优化,并根据查询条件路由到各个group,转发sql进行查询,各个group根据权重选择其中一个Atom进行查询,各个Atom再将结果返回给Matrix,Matrix将结果合并返回给client。具体的工作流程的可以拆分成如下图:

Matrix层会先执行以下四个过程:

a)Sql的解析。首先将Sql语句解析成一颗抽象语法树(Abstract Syntax Tree),解析成我们比较好处理的一个结构

b)规则的匹配与计算。基于上一步创建的语法树查找匹配的规则,再根据规则去确定分库分表的结果。这里有一个概念就是规则,规则这里可以简单的看做就是定义数据库怎么进行分库分表,要分成几张库几张表,库名和表名的命名是怎么样的。规则的匹配就是根据SQL的语句确定,具体查询的子表是哪几张。

c)表名替换。对于开发人员来说,它查询的表直接就是select * from A.B limit 10(A为数据库名,B为数据表名)。但底层其实会把这些表名替换成类似select * from A_000.B_001,select * from A_000.B_002,select * from A_001.TABLE_001这样的形式。表名替换就是把总表的名称替换为这些子表的名字。

d)Sql的转发。将上一步生成的各个sql语句转发到对应的Group进行执行。这里如上图,我查询的条件是where id = 2 or 3。那么转发给Group0的查询为where id=3,转发给group1的查询为where id =2 。查询的条件也会发生一定修改。

这样四个步骤可以在Matrix层就实现了分库分表的功能,对原始的Sql进行分解,将原本单库单表的查询语句,底层转发到多库多表并行的进行执行,提高了数据库读写的性能。

接下来由Group执行两个过程:

e)根据权重选择AtomDs。通常会在主节点和副节点上读取数据,只在主节点上写入数据。

f)具有重试的策略地在AtomDs上执行SQL。这个可以防止单个的AtomDs发生故障,那么会进入读重试,以确保尽可能多的数据访问可以在正常数据库中访问。

然后是Atom层执行两个过程:

g)读写数控制、线程并发数控制 。同时会统计线程数、执行次数等信息。

h)执行sql,返回结果集。Atom底层利用druid进行连接池的管理,具体查询还是对JDBC做了一定封装。执行完Sql后对将结果返回给Matrix。

最后Matrix执行最后一个过程:

i)结果集合并。Matrix将Atom层的返回的各个结果集进行合并Merge,返回给Client端。

2)路由与扩容(固定哈希算法为例)

a)数据库水平拆分路由

 

f(pavarotti17)= hash(pavarotti17) % 1024,然后根据该值找对应的DB,

 

b)扩容

固定哈希算法是常用的算法,其扩容一般推荐每次以2倍的形式扩容,这样只需要迁移一半的数据。

4、Senquence全局唯一id标识生成原理

1)背景

目前基于tddl进行分库分表后,原本一个数据库上的自增id的结果,在分库分表下并不是全局唯一的。所以,分库分表后需要有一种技术可以生成全局的唯一id。

2)工作原理

a)主要职责

生成全局唯一的id;保持高性能;保持高可用。

b)目前常见的几种全局ID的思路

方案一

oracle sequence:基于第三方oracle的SEQ.NEXTVAL来获取一个ID

优势:简单可用。

缺点:需要依赖第三方oracle数据库。

方案二

mysql id区间隔离:不同分库设置不同的起始值和步长,比如2台mysql,就可以设置一台只生成奇数,另一台生成偶数. 或者1台用0~10亿,另一台用10~20亿.。

优势:利用mysql自增id 。

缺点:运维成本比较高,数据扩容时需要重新设置步长。

方案三

基于数据库更新+内存分配:在数据库中维护一个ID,获取下一个ID时,会对数据库进行ID=ID+100 WHERE ID=XX,拿到100个ID后,在内存中进行分配 。

优势:简单高效。

缺点:无法保证自增顺序。

目前tddl sequence也是选择的方案3进行实现,但会有几点额外的要求:

a. 只要生成id的数据库不全部挂掉,均可以顺畅提供服务;

b. 生成id的数据库数量不定,按照应用对容灾的需求指定不同机架不同机房的数据库; (比如需要考虑单元化多机房的id生成)

c. 支持生成id的数据库hang住快速略过和恢复自动加入 。

总结一下:生成id的数据库可以是多机,其中的一个或者多个数据库挂了,不能影响id获取,保证严格高可用。

目前我们针对多机的id生成方案: 每个数据库只拿自己的那一段id,如下图左:

 

sample_group_0-sample_group_3是我们生成全局唯一id的4个数据库,那么每个数据库对于同一个id有一个起始值,比如间隔是1000。

应用真正启动的时候,可能某一台机器上去取id,随机取到了sample_group_1,那么这台机器上的应用会拿到1000-1999这一千个id(批量取,这个也就保证了应用端取id性能),而这个时候4个数据库上id起始值会变成右图所示,你也许注意到了,下次从sample_group_1上取得的id就变成了4000-4999。那么也就是这样,完全避免了多机上取id的重复。比如sample_group_1他会永远只会取到1000-1999,4000-4999,8000-8999,12000-12999…其他数据库也一样,相互不会重叠。

这种产生全局唯一id的方式相当有效,保证基本的全局唯一特性和高性能的同时,可以对生成id的数据库分机架分机房部署达到容灾的目的。

3)配置

<bean class="com.taobao.tddl.client.sequence.impl.GroupSequence" id="sequence" init-method="init">

<property name="sequenceDao" ref="sequenceDao_one_db">

<property name="name" value="ni">

</property></property></bean>

7:TDDL适用场景

1)高并发实时交易场景

面向客户端的电商、金融、O2O、零售等行业普遍存在用户基数大、营销活动频繁、核心交易系统数据库响应日益变慢的问题,制约业务发展。 TDDL 提供线性水平扩展能力,能够实时提升数据库处理能力,提高访问效率,峰值 TPS 达150万+,轻松应对高并发的实时交易场景。

2)海量数据存储访问场景

企业客户随着业务的快速发展,业务数据增长迅猛,会产生超过单机数据库存储能力极限的数据,造成数据库容量瓶颈,限制业务发展。 TDDL 可以线性扩展存储空间,提供 PB 级存储能力,可广泛应用于工业制造、智能家居、车联网等超大规模数据存储访问场景。

3)高性价比数据库解决方案

初创型企业初期发展阶段技术积累相对比较薄弱,资金投入有限,业务发展快,数据库的稳定性风险高。TDDL 继承了阿里巴巴多年的分布式数据库技术积累,能够提供简单易用的数据库运维系统,降低企业的技术运维成本,赋予企业强大的数据库支撑能力。

当业务数据和访问量增加到一定量时,如政务机构、大型企业、银行等行业为了支持大规模数据存储和高并发数据库访问,传统方案需要强依赖小型机和高端存储等高成本的商业解决方案,以达到扩展服务能力的目的。TDDL 能够利用普通服务器提供阿里巴巴双十一同等处理能力的高性价比国产数据库解决方案。

4)数据存储平滑扩容

当应用单机存储(MySQL)出现容量或性能瓶颈时,TDDL 提供在线数据扩容功能(该功能需要结合阿里其它内部中间件使用)。传统数据库容量扩展往往意味着服务中断,很难做到业务无感知或者少感知。