mysql分布式数据库的逻辑库物理库和分库分表和TDDL图文详解
Posted Kotlin 开发者社区
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了mysql分布式数据库的逻辑库物理库和分库分表和TDDL图文详解相关的知识,希望对你有一定的参考价值。
什么逻辑库、物理库?
逻辑库/逻辑文件:给用户看的(即Database和Table就是我们常说的逻辑库的范畴)
物理库/物理文件:存储在计算机中的(即机器和Port就是我们常说的物理库的范畴。)
一个服务器有多个实例(port);
一个实例有多个Database(db);
一个DB有多张表(Table);
物理表、库:
物理表是具体某个数据源中的一张表。对于mysql就是一个table,对于Hbase可以是一张hbase表,对于ES是一个索引。
逻辑表、库:
可以理解为数据库中的视图,是一张虚拟表。可以映射到一张物理表,也可以由多张物理表组成,这些物理表可以来自于不同的数据源。对于mysql, Hbase和ES,要组成一张逻辑表,只需要他们有相同含义的key即可。这个key在mysql中是主键,Hbase中是生成rowkey用的值,是ES中的key。
什么是分库分表?
用1张图来说明:
为什么要分库分表?
数据库瓶颈↑
不管是IO瓶颈,还是CPU瓶颈,最终都会导致数据库的活跃连接数增加,进而逼近甚至达到数据库可承载活跃连接数的阈值。在业务Service来看就是,可用数据库连接少甚至无连接可用。接下来就可以想象了吧(并发量、吞吐量、崩溃)。
1、IO瓶颈
第一种:磁盘读IO瓶颈,热点数据太多,数据库缓存放不下,每次查询时会产生大量的IO,降低查询速度 -> 分库和垂直分表。
第二种:网络IO瓶颈,请求的数据太多,网络带宽不够 -> 分库。
2、CPU瓶颈
第一种:SQL问题,如SQL中包含join,group by,order by,非索引字段条件查询等,增加CPU运算的操作 -> SQL优化,建立合适的索引,在业务Service层进行业务计算。
第二种:单表数据量太大,查询时扫描的行太多,SQL效率低,CPU率先出现瓶颈 -> 水平分表。
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架构实现原理介绍
TDDL, Taobao Distribute Data Layer
一、分布式数据库的演化
Tddl是一个分布式数据库中间件,主要是为了解决分布式数据库产生的相关问题,因此在学习tddl之前我们先了解一下分布式数据库的演化历程,所谓磨刀不误砍柴工,知其然亦要知其所以然,分布式数据库与数据库中间件息息相关,了解学习是很有必要的。整个分布式数据库的演化过程如下:
1、单库单表
通常刚开始的时候,应用的数据比较少,也不会很复杂,所以应用只有一个数据库,数据库中的业务表是一张对应业务的完整的表,这也是我们刚开始接触数据库时的数据库形态。
2、读写分离
随着业务的发展,数据量与数据访问量不断增长,很多时候应用的主要业务是读多写少的,比如说一些新闻网站,运营在后台上传了一堆新闻之后,所有的用户都会去读取这些新闻资讯,因此数据库面临的读压力远大于写压力,那么这时候在原来数据库 Master 的基础上增加一个备用数据库 Slave,备库和主库存储着相同的数据,但只提供读服务,不提供写服务。以后的写操作以及事务中的读操作就走主库,其它读操作就走备库,这就是所谓的读写分离。
读写分离会直接带来两个问题:
1)数据复制问题
因为最新写入的数据只会存储在主库中,之后想要在备库中读取到新数据就必须要从主库复制过来,这会带来一定的延迟,造成短期的数据不一致性。但这个问题应该也没有什么特别好的办法,主要依赖于数据库提供的数据复制机制,常用的是根据数据库日志 binary-log 实现数据复制。
2)数据源选择问题
读写分离之后我们都知道写要找主库,读要找备库,但是程序不知道,所以我们在程序中应该根据 SQL 来判断出是读操作还是写操作,进而正确选择要访问的数据库。
3、垂直分库
数据量与访问量继续上升时,主备库的压力都在变大,这时候可以根据业务特点考虑将数据库垂直拆分,即把数据库中不同的业务单元的数据划分到不同的数据库里面。比如说,还是新闻网站,注册用户的信息与新闻是没有多大关系的,数据库访问压力大时可以尝试把用户信息相关的表放在一个数据库,新闻相关的表放在一个数据库中,这样大大减小了数据库的访问压力。
垂直分库会带来以下问题:
1)ACID 被打破
数据分到不同的数据库之后,原来的事务操作将会受很大影响,比如说注册账户的时候需要在用户表和用户信息表中插入一条数据,单机数据库利用事务可以很好地完成这个任务,但是多机将会变得比较麻烦。以下两点也是因为数据库在不同的机子上而导致简单的操作变得复杂。
2)Join 操作困难
3)外键约束受影响
4、水平分表
经过长时间积累,特别对于 ugc 的业务,数据量会达到惊人的地步,每张表存放着大量的数据,任何 CRUD 都变成了一次极其消耗性能的操作,这个时候就会考虑水平分表,把一张表内大量的数据拆分成多张子表,比如说原来一张表中存放 50000 条数据,水平分成 5 张表之后,每张表只要存放 10000 条数据。这种结构可以比较容易的存储和维护海量数据。
水平分表会在垂直分库的基础上带来更多的影响:
1)自增主键会有影响
这个影响很明显,分表中如果使用的是自增主键的话,那么就不能产生唯一的 ID 了,因为逻辑上来说多个分表其实都属于一张表,自增主键无法标识每一条数据。
2)有些单表查询会变成多表
比如说 count 操作,原来是一张表的问题,现在要从多张分表中共同查询才能得到结果。
二、TDDL介绍
1、产生背景
由一中可以看出,数据库从单机走向分布式将会面临很多很多的问题,分布式数据库中间件就是为了解决这些问题而生的。Tddl作为开源分布式数据库中间件之一,由阿里研发,其出现解决了以上分布式数据库需求带来的问题,总结起来如下:
1)单一数据库无法满足性能需求
随着业务的发展,数据量急剧增大,使用单库单表时,数据库压力过大,因此通过分库分表,读写分离的方式,减轻数据库的压力。
2)系统容灾
使用单机数据库时,如果数据库宕机,则无法对外提供服务,因此产生了主备数据库的方案,当主库宕机后,会自动切换到备库,不影响对外的服务。
3)运维管理
直接连接单机数据库时,无法动态的切换数据源,使用TDDL中间件,可以动态的切换数据源。
2、发展史
1)TDDL 2.0 (2009~2011) 第一个流行版本
2)TDDL 3.1 (2012~) 规则版本升级
3)TDDL 3.3 (2013~) 引入druid链接池
4)Andor (2012~2013) 一次全新的尝试,支持跨库查询
5)TDDL 5.0 (2013) 基于Andor + TDDL3.3的发展而来,保留各自的优点
6)TDDL 5.1 (2014~) 集成cobar,提供server模式,解决跨语言查询
三、TDDL原理解析
1、整体结构
系统整体架构
1)包结构
2)模型结构
Tddl主要部署在ibatis、mybatis或者其他ORM框架之下,JDBC Driver之上。整个中间件实现了JDBC规范,所以可以将Tddl当做普通数据源实例并且注入到各种ORM框架中使用。由下图中的结构模型图可以看出,tddl是JDBC或者持久框架层与底层JDBC驱动交互的桥梁,或者也可以称之为中转站,起到进出的加工作用。
3)分布式数据库逻辑结构
分布式数据库在经过读写分离和分库分表之后,数据库的结构变得复杂,逻辑上大致如下图所示:
2、三层数据源结构(核心组件)
TDDL作为中间件,其作用是根据路由规则,将sql路由到正确的分库、分表上去执行,再将结果进行汇总,返回给用户,对于用户,不需要了解TDDL的原理,可以像使用单库单表一样去使用分布式数据库。在解析执行和封装这些类似于单库单表的SQL语句时,tddl有专门的组件来进行处理,这些组件就是核心的三层数据源,分别是Matrix层、Group层以及Atom层,其结构如下图所示:
1)Matrix层
改成功能在于分库分表路由,SQL语句的解释、优化和执行,事务的管理规则的管理,各个子表查询出来结果集的Merge等。
如上所述,Matrix 层可以解决分库分表带来的问题,从本质上来看,分库分表带来的最直接的影响是数据访问的路由。单库单表的时候,什么都不用想,就是去这个 DB 中找到这张 Table 再进行查询,但是多库多表的时候就必须要考虑数据存到哪个数据库,为什么要存到这个数据库诸如此类的问题。这里面涉及到数据访问路由算法,它规定了数据的存储位置,同样也由它来指明该去哪里查询数据,常见的数据访问路由算法有以下几种:
a)固定哈希算法
固定哈希就再简单不过了,就是根据某个字段(如整形的 id 或者字符串的 hashcode)对分库的数量或者分表的数量进行取模,根据余数路由到对应的位置。下面图中的例子,数据库垂直拆分成 4 个,其中有一张表水平拆分成两张,利用固定哈希算法进行路由的过程如下:
b)一致性哈希算法
固定哈希算法足够简单实用,基本能保证数据均匀分布,它也是 TDDL 的默认路由算法,但是在数据库扩容的时候,固定哈希算法带来的数据迁移成本也是不得不考虑的。依然是上面的例子,数据库拆分成 4 个,当需要增加数据库的时候,假设变成 5 个,由于取模的结果发生变化,原来数据库中的绝大部分数据都要进行迁移,只有在数据库倍增的时候,数据迁移量才是最少的,但也高达 50%,而且倍增的成本比较高。
所以一致性哈希算法应景而生,它的原理就是通过该算法计算出 key 的 hashcode 之后对 2^32 取模,那么数据都会落在 0~2^32 所组成的环中;同样的,可以利用一致性哈希算法对机器的唯一属性计算所在位置,然后数据存储在顺时针方向最近的机器上。如图所示:
对于一致性哈希,增删机器的成本就降低很多了,比如说在上图 node2 与 node4 之间增加一台机器 node5,那么需要迁移的数据只分布在 node2 与 node5 之间,相比固定哈希来说迁移量小了很多。
c)虚拟节点
一致性哈希已经可以解决大部分需求了,但是对于数据集中在热点的情况,一致性哈希同样面临比较大的挑战。比如说,上图的 node2 与 node4 之间集中了整个环中的大部分数据,当加入 node5 之后,其实起到的效果比较有限,因为还是要有大量的数据进行迁移。引入虚拟节点之后,情况就不一样了,所谓虚拟节点,它就是物理节点的映射,一个物理节点可以复制出多个虚拟节点,尽可能的让它均匀分布在环上,那么即使数据再集中,其实也会存储在不同的节点上,很好地起到了负载均衡的作用。
d)自定义路由规则
这是最不常用的方法,不过 TDDL 也支持,你可以实现自己的算法进行数据访问路由,但一般要么效果很差要么成本很高。
2)Group层
改层的作用在于数据库读写分离,基本上主数据库负责读写,备份数据库只负责读;主备切换状态对调后备库变为主库,主库变为备库;权重的选择 根据权重选择要去读哪些库;数据保护,数据库down掉后的线程保护, 数据库挂掉后的线程保护,不会因为一个数据库挂掉导致所有线程卡死。
读写分离与主备切换带来的问题是 Group 层解决的核心。首先简单介绍一下主备切换,由于主库或者备库都有可能挂掉,但很小概率同时挂,所以当一方挂掉的时候,Group 层要马上把流量切到另一个库,保证挂掉一个不会让应用挂掉。
读写分离最大的问题是数据复制,通常有两种复制场景,一种是镜像复制,即主库和从库的数据结构是一模一样的,通常根据主库上的日志变化,在从库中执行相同的操作;另外一种是非对称复制,意思就是主库与备库是以不同的方式分库的,它们的结构虽然相同,但是主备库中存储的记录是不相同的,主要目的是查询条件不同时,把请求分发到更加适合的库去操作。举个例子,对于订单数据库,买家会根据自己的 ID 去查自己的交易记录,所以主库可以用买家 ID 分库,保证单个买家的记录在同一个数据库中;但是卖家如果想看交易记录的话可能就得从多个库中进行查询,这时候可以利用卖家 ID 进行分库作为备库,这样一来主备库的复制就不能简单的镜像复制了,在进行复制操作之前还需要进行路由。
3)Atom层
Atom 模块真正和物理数据库交互,提供数据库配置动态修改能力。
改层负责动态创建,添加,减少数据源。管理着底层的数据库IP,连接等信息;底层对物理数据库做了代理,对单库的JDBC做了一层封装,执行底层单库的SQL;线程数、执行次数等状态的统计等。
3、执行流程
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台用010亿,另一台用1020亿.。
优势:利用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">
</bean>
5、tddl适用场景
1)高并发实时交易场景
面向客户端的电商、金融、O2O、零售等行业普遍存在用户基数大、营销活动频繁、核心交易系统数据库响应日益变慢的问题,制约业务发展。TDDL 提供线性水平扩展能力,能够实时提升数据库处理能力,提高访问效率,峰值 TPS 达150万+,轻松应对高并发的实时交易场景。
2)海量数据存储访问场景
企业客户随着业务的快速发展,业务数据增长迅猛,会产生超过单机数据库存储能力极限的数据,造成数据库容量瓶颈,限制业务发展。TDDL 可以线性扩展存储空间,提供 PB 级存储能力,可广泛应用于工业制造、智能家居、车联网等超大规模数据存储访问场景。
3)高性价比数据库解决方案
初创型企业初期发展阶段技术积累相对比较薄弱,资金投入有限,业务发展快,数据库的稳定性风险高。TDDL 继承了阿里巴巴多年的分布式数据库技术积累,能够提供简单易用的数据库运维系统,降低企业的技术运维成本,赋予企业强大的数据库支撑能力。
当业务数据和访问量增加到一定量时,如政务机构、大型企业、银行等行业为了支持大规模数据存储和高并发数据库访问,传统方案需要强依赖小型机和高端存储等高成本的商业解决方案,以达到扩展服务能力的目的。TDDL 能够利用普通服务器提供阿里巴巴双十一同等处理能力的高性价比国产数据库解决方案。
4)数据存储平滑扩容
当应用单机存储(MySQL)出现容量或性能瓶颈时,TDDL 提供在线数据扩容功能(该功能需要结合阿里其它内部中间件使用)。传统数据库容量扩展往往意味着服务中断,很难做到业务无感知或者少感知。
怎样进行分库分表?
分库分表步骤:
根据容量(当前容量和增长量)评估分库或分表个数 -> 选key(均匀)-> 分表规则(hash或range等)-> 执行(一般双写)-> 扩容问题(尽量减少数据的移动)。
分库分表维度:
1、水平分库
2、水平分表
3、垂直分库
4、垂直分表
1、水平分库
概念:以 字段为依据 ,按照一定策略(hash、range等),将一个 库中的数据拆分到多个 库中。
结果:
每个 库的 结构都一样;
每个 库的 数据都不一样,没有交集;
所有 库的 并集是全量数据;
场景:系统绝对并发量上来了,分表难以根本上解决问题,并且还没有明显的业务归属来垂直分库。
分析:库多了,io和cpu的压力自然可以成倍缓解。
2、水平分表
概念:以 字段为依据 ,按照一定策略(hash、range等),将一个 表中的数据拆分到多个 表中。
结果:
每个 表的 结构都一样;
每个 表的 数据都不一样,没有交集;
所有 表的 并集是全量数据;
场景:系统绝对并发量并没有上来,只是单表的数据量太多,影响了SQL效率,加重了CPU负担,以至于成为瓶颈。
分析:表的数据量少了,单次SQL执行效率高,自然减轻了CPU的负担。
水平分库分表,拆分策略为常用的hash法。
例如, 使用TDDL进行动态路由规则的配置如下:
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
<!-- 这个bean配置为TDDL规则总配置 -->
<bean id="vtabroot" class="com.taobao.tddl.rule.VirtualTableRoot" init-method="init">
<!-- 没有被配置在tableRules的逻辑表都将在这个group里,以单表形式执行 -->
<property name="defaultDbIndex" value="TDDL_DEFAULT_GROUP"/>
<!-- 数据库类型,默认是mysql -->
<property name="dbType" value="MYSQL"></property>
<!-- 该map配置有分表的逻辑表,有几个表有分表就配置几个键值对(该事例表示只有三个表需要分表)-->
<property name="tableRules">
<map>
<!-- key是逻辑表名,value指的是对应具体配置的id -->
<entry key="user" value-ref="user_bean"></entry>
<!-- 逻辑表名为admin,具体的分表规则在id="admin_bean"的配置中 -->
<entry key="admin" value-ref="admin_bean"></entry>
<!-- 这张表是单表,可以配置在这,不配置的话默认走defaultDbIndex -->
<entry key="picture" value-ref="picture_bean"></entry>
</map>
</property>
</bean>
<!-- 按照user_id取模划分64张表,表明具体为'user_0000'-'user_0063',
'user_0000'-'user_0031' 在'TDDL_0000_GROUP'中,
'user_0032'-'user_0063' 在'TDDL_0001_GROUP'中 -->
<bean id="user_bean" class="com.taobao.tddl.rule.TableRule">
<!-- groupKey格式框架,{}中的数将会被dbRuleArray的值替代,并保持位数 -->
<property name="dbNamePattern" value="TDDL_{0000}_GROUP"/>
<!-- 具体的分库规则 -->
<property name="dbRuleArray">
<!-- 按照user_id取模划分64张表,结果除以32后分到两个库中 -->
<value>(#user_id,1,64#.longValue() % 64).intdiv(32)</value>
</property>
<!-- 具体表名格式框架,{}中的数将会被tbRuleArray的值替代,并保持位数 -->
<property name="tbNamePattern" value="user_{0000}"></property>
<!-- 具体的分表规则 -->
<property name="tbRuleArray">
<!-- 按照user_id取模划分64张表 -->
<value>#user_id,1,64#.longValue() % 64</value>
</property>
<!-- 全表扫描开关,默认关闭,是否允许应用端在没有给定分表键值的情况下查询全表 -->
<property name="allowFullTableScan" value="true"/>
</bean>
<!-- 按照user_id取模划分4张表,表名具体为'admin_00'-'admin_03',
所有表都在'TDDL_DEFAULT_GROUP'中 -->
<bean id="admin_bean" class="com.taobao.tddl.rule.TableRule">
<!-- 分库规则,该表没有分库,所有表都在这个group上 -->
<property name="dbNamePattern" value="TDDL_DEFAULT_GROUP"/>
<property name="tbNamePattern" value="admin_{00}"></property>
<property name="tbRuleArray">
<value>#user_id,1,4#.longValue() % 4</value>
</property>
</bean>
<!-- 即不分库也不分表 -->
<bean id="picture_bean" class="com.taobao.tddl.rule.TableRule">
<property name="dbNamePattern" value="TDDL_DEFAULT_GROUP"/>
<property name="tbNamePattern" value="picture"></property>
</bean>
</beans>
说明:
需要定义一个VirtualTableRoot,对应bean id="vtabroot,同时声明init-method="init"
每个逻辑表需要单独定义一个TableRule,每个逻辑表都可以定义自己的分库分表条件.
每张逻辑表定义需要正式被启用,需要在VirtualTableRoot中的tableRules,定义逻辑表名和规则的映射.
比如,这里对外的逻辑表名为user,用户可通过select * from user进行操作.
分库/分表同时存在
例子:
分库:(#user_id,1,64#.longValue() % 64).intdiv(32)
分表:(#user_id,1,64#.longValue() % 64
比如user表希望进行即分库又分表,比如按照user_id取模划分64张表,表明具体为'user_0000'-'user_0063'
'user_0000'-'user_0031' 在'TDDL_0000_GROUP'中
'user_0032'-'user_0063' 在'TDDL_0001_GROUP'中
此时的枚举逻辑会有所变化,枚举算法:
首先按照分库的Rule规则进行计算,同时记录每个分库的计算结果对应的枚举值 【比如计算出TDDL_0000_GROUP,对应的枚举值为0~31.】
然后基于每个分库的计算结果对应的枚举值,再按照分表的Rule规则进行枚举计算,最后得出整个的分库分表规则
总结需要额外考虑问题:
分库分表有无公共字段 【无影响,各自算各自的】
分库分表有公共字段 【有影响,先算库规则,基于库规则的枚举值,再算表规则】
a. 库规则和表规则的步长,类型,范围值都一样
b. 库规则和表规则的步长,类型,范围值存在不一样【比如库规则是1_month,按照月进行分库,表规则是1_date,按照天进行分表】
3、垂直分库
概念:以 表为依据,按照业务归属不同,将不同的 表拆分到不同的 库中 。
结果:
每个 库的 结构都不一样;
每个 库的 数据也不一样,没有交集;
所有 库的 并集是全量数据;
场景:系统绝对并发量上来了,并且可以抽象出单独的业务模块。
分析:到这一步,基本上就可以服务化了。例如,随着业务的发展一些公用的配置表、字典表等越来越多,这时可以将这些表拆到单独的库中,甚至可以服务化。再有,随着业务的发展孵化出了一套业务模式,这时可以将相关的表拆到单独的库中,甚至可以服务化。
4、垂直分表
概念:以 字段为依据,按照字段的活跃性,将 表中字段拆到不同的 表(主表和扩展表)中。
结果:
每个 表的 结构都不一样;
每个 表的 数据也不一样,一般来说,每个表的 字段至少有一列交集,一般是主键,用于关联数据;
所有 表的 并集是全量数据;
场景:系统绝对并发量并没有上来,表的记录并不多,但是字段多,并且热点数据和非热点数据在一起,单行数据所需的存储空间较大。以至于数据库缓存的数据行减少,查询时会去读磁盘数据产生大量的随机读IO,产生IO瓶颈。
分析:可以用列表页和详情页来帮助理解。垂直分表的拆分原则是将热点数据(可能会冗余经常一起查询的数据)放在一起作为主表,非热点数据放在一起作为扩展表。这样更多的热点数据就能被缓存下来,进而减少了随机读IO。拆了之后,要想获得全部数据就需要关联两个表来取数据。但记住,千万别用join,因为join不仅会增加CPU负担并且会讲两个表耦合在一起(必须在一个数据库实例上)。关联数据,应该在业务Service层做文章,分别获取主表和扩展表数据然后用关联字段关联得到全部数据。
分库分表总结
分库分表,首先得知道瓶颈在哪里,然后才能合理地拆分(分库还是分表?水平还是垂直?分几个?)。切不可为了分库分表而拆分。
选key很重要,既要考虑到拆分均匀,也要考虑到非partition key的查询。
只要能满足需求,拆分规则越简单越好。
参考资料
https://blog.csdn.net/qq_39416311/article/details/86730483
http://blog.itpub.net/26736162/viewspace-2651606/
Kotlin 开发者社区
越是喧嚣的世界,越需要宁静的思考。
以上是关于mysql分布式数据库的逻辑库物理库和分库分表和TDDL图文详解的主要内容,如果未能解决你的问题,请参考以下文章