前言mysql
在开始讲解淘宝的 TDDL(Taobao Distribute Data Layer) 技术以前,请容许笔者先吐槽一番。首先要开喷的是淘宝的社区支持作的无比的烂, TaoCode 开源社区上面,几乎历来都是有人提问,无人响应。再者版本迭代速度也一样差强人意 , 就目前而言 TDDL 的版本已经全线开源(Group、Atom、Matrix)你们能够在Github上下载源码 。web
目录算法
1、互联网当下的数据库拆分过程spring
2、 TDDL 的架构原型sql
3、下载 TDDL 的 Atom 层和 Group 层源代码数据库
4、 Diamond 简介tomcat
5、 Diamond 的安装和使用架构
6、动态数据源层的 Master/Salve 读写分离 配置与实现并发
7、 Matrix 层的分库分表配置与实现oracle
1、互联网当下的数据库拆分过程
对于一个刚上线的互联网项目来讲,因为前期活跃用户数量并很少,并发量也相对较小,因此此时企业通常都会选择将全部数据存放在 一个数据库 中进行访问操做。但随着后续的市场推广力度不断增强,用户数量和并发量不断上升,这时若是仅靠一个数据库来支撑全部访问压力,几乎是在 自寻死路 。因此一旦到了这个阶段,大部分 Mysql DBA 就会将数据库设置成 读写分离状态 ,也就是一个 Master节点对应多个 Salve 节点。通过 Master/Salve 模式的设计后,彻底能够应付单一数据库没法承受的负载压力,并将访问操做分摊至多个 Salve 节点上,实现真正意义上的读写分离。但你们有没有想过,单一的 Master/Salve 模式又能抗得了多久呢?若是用户数量和并发量出现 量级 上升,单一的 Master/Salve 模式照样抗不了多久,毕竟一个 Master 节点的负载仍是相对比较高的。为了解决这个难题,Mysql DBA 会在单一的 Master/Salve 模式的基础之上进行数据库的 垂直分区 (分库)。所谓垂直分区指的是能够根据业务自身的不一样,将本来冗余在一个数据库内的业务表拆散,将数据分别存储在不一样的数据库中,同时仍然保持 Master/Salve模式。通过垂直分区后的 Master/Salve 模式彻底能够承受住不可思议的高并发访问操做,可是否能够永远 高枕无忧 了?答案是否认的,一旦业务表中的数据量大了,从维护和性能角度来看,不管是任何的 CRUD 操做,对于数据库而言都是一件极其耗费资源的事情。即使设置了索引, 仍然没法掩盖由于数据量过大从而致使的数据库性能降低的事实 ,所以这个时候 Mysql DBA 或许就该对数据库进行 水平分区 (分表, sharding ),所谓水平分区指的是将一个业务表拆分红多个子表,好比 user_table0 、 user_table1 、 user_table2 。子表之间经过某种契约关联在一块儿,每一张子表均按段位进行数据存储,好比 user_table0 存储 1-10000 的数据,而 user_table1 存储 10001-20000 的数据,最后 user_table3 存储 20001-30000 的数据。通过水平分区设置后的业务表,必然可以将本来一张表维护的海量数据分配给 N 个子表进行存储和维护,这样的设计在国内一流的互联网企业比较常见,如图 1-1 所示:
图 1-1 水平分区
上述笔者简单的讲解了数据库的分库分表原理。接下来请你们认真思考下。本来一个数据库可以完成的访问操做,如今若是按照分库分表模式设计后,将会显得很是麻烦,这种麻烦尤为体如今 访问操做 上。由于持久层须要判断出对应的数据源,以及数据源上的水平分区,这种访问方式咱们称之为访问 “ 路由 ” 。按照常理来讲,持久层不该该负责数据访问层 (DAL) 的工做,它应该只关心 one to one 的操做形式,因此淘宝的 TDDL 框架诞生也就顺其天然了。
2、 TDDL 的架构原型
淘宝根据自身业务需求研发了 TDDL ( Taobao Distributed Data Layer )框架,主要用于解决 分库分表场景下的访问路由(持久层与数据访问层的配合)以及异构数据库之间的数据同步 ,它是一个基于集中式配置的 JDBC DataSource 实现,具备分库分表、 Master/Salve 、动态数据源配置等功能。
就目前而言,许多大厂也在出一些更加优秀和社区支持更普遍的 DAL 层产品,好比 Hibernate Shards 、 Ibatis-Sharding 等。若是你要问笔者还为何还要对 TDDL进行讲解,那么笔者只能很 无奈 的表示公司要这么干,由于不少时候技术选型并非笔者说了算,而是客户说了算。当笔者费劲全部努力在 google 上寻找 TDDL的相关使用说明和介绍时,内心一股莫名的火已经开始在蔓延,对于更新缓慢(差很少一年没更新过 SVN ),几乎没社区支持(提问从不响应)的产品来讲,除了蜗居在企业内部,一定走不了多远,最后的结局注定是 悲哀 的。好了,既然抱怨了一番,不管如何仍是要坚持讲解完。 TDDL 位于数据库和持久层之间,它直接与数据库创建交道,如图 1-2 所示:
图 1-2 TDDL 所处领域模型定位
传说淘宝很早之前就已经对数据进行过度库分表处理,应用层链接多个数据源,中间有一个叫作 DBRoute 的技术对数据库进行 统一 的路由访问。 DBRoute 对数据进行多库的操做、数据的整合,让应用层像操做一个数据源同样操做多个数据库。可是随着数据量的增加,对于库表的分法有了更高的要求,例如,你的商品数据到了百亿级别的时候,任何一个库都没法存放了,因而分红 2 个、 4 个、 8 个、 16个、 32 个 …… 直到 1024 个、 2048 个。好,分红这么多,数据可以存放了,那怎么查询它?这时候,数据查询的中间件就要可以承担这个重任了,它对上层来讲,必须像查询一个数据库同样来查询数据,还要像查询一个数据库同样快( 每条查询要求在几毫秒内完成 ), TDDL 就承担了这样一个工做( 其余 DAL 产品作得更好 ),如图 1-3 所示:
图 1-3 TDDL 分库分表查询策略
上述笔者描述了 TDDL 在分库分表环境下的查询策略,那么接下来笔者有必要从淘宝官方 copy 它们本身对 TDDL 优势的一些描述,真实性不敢保证,毕竟没彻底开源,和社区零支持,你们看一看就算了,别认真。
淘宝人自定的 TDDL 优势:
1 、数据库主备和动态切换;
2 、带权重的读写分离;
3 、单线程读重试;
4 、集中式数据源信息管理和动态变动;
5 、剥离的稳定 jboss 数据源;
6 、支持 mysql 和 oracle 数据库;
7 、基于 jdbc 规范,很容易扩展支持实现 jdbc 规范的数据源;
8 、无 server,client-jar 形式存在,应用直连数据库;
9 、读写次数 , 并发度流程控制,动态变动;
10 、可分析的日志打印 , 日志流控,动态变动;
注意 :
TDDL 必需要依赖 diamond 配置中心( diamond 是淘宝内部使用的一个管理持久配置的系统,目前淘宝内部绝大多数系统的配置)。
接下来,笔者将会带领各位一块儿分析 TDDL 的体系架构。 TDDL 其实主要能够划分为 3 层架构,分别是 Matrix 层、 Group 层和 Atom 层。 Matrix 层用于实现分库分表逻辑,底层持有多个 Group 实例。而 Group 层和 Atom 共同组成了 动态数据源 , Group 层实现了数据库的 Master/Salve 模式的写分离逻辑,底层持有多个Atom 实例。最后 Atom 层 (TAtomDataSource) 实现数据库ip,port,password,connectionProperties 等信息的动态推送 , 以及持有原子的数据源分离的 JBOSS 数据源)。
图 1-4 TDDL 体系结构
章节的最后,咱们还须要对 TDDL 的原理进行一次剖析。由于咱们知道持久层只关心对数据源的 CRUD 操做,而多数据源的访问,并不该该由它来关心。也就是说 TDDL 透明给持久层的数据源接口应该是统一且 “ 单一 ” 的,至于数据库 到底如何分库分表,持久层无需知道,也无需 编写对应的 SQL 去实行 应对策略 。这个时候对 TDDL 一些疑问就出现了, TDDL 须要对 SQL 进行二次解析和拼装吗?答案是 不解析仅拼装 。说白了 TDDL 只须要从持久层拿到发出的 SQL
再按照一些分库分表条件,进行特定的 SQL 扩充以此知足访问路路由操做。
如下是淘宝团队对 TDDL 的官方原理解释:
1 、 TDDL 除了拿到分库分表条件外,还须要拿到 order by 、 group by 、 limit 、join 等信息, SUM 、
MAX 、 MIN 等聚合函数信息, DISTINCT 信息。具备这些关键字的 SQL 将会在单库和多库状况下进行 , 语义是不一样的。 TDDL 必须对使用这些关键字的 SQL 返回的结果作出合适的处理;
2 、 TDDL 行复制须要从新拼写 SQL, 带上 sync_version 字段;
3 、不经过 sql 解析 , 由于 TDDL 遵照 JDBC 规范 , 它不可能去扩充 JDBC 规范里面的接口 , 因此只能经过 SQL 中加额外的字符条件 ( 也就是 HINT 方式 ) 或者ThreadLocal 方式进行传递 , 前者使 SQL 过长 , 后者难以维护 , 开发 debug 时不容易跟踪 , 并且须要断定是在一条 SQL 执行后失效仍是 1 个链接关闭后才失效;
4 、 TDDL 如今也同时支持 Hint 方式和 ThreadLocal 方式传递这些信息;
3、下载 TDDL 的 Atom 层和 Group 层源代码
前面咱们谈及了 TDDL 的动态数据源主要由 2 部分构成,分别是 Atom 和Group 。 Group 用于实现数据库的 Master/Salve 模式的写分离逻辑,而 Atom 层则是持有数据源。很是遗憾的 TDDL 中还有一层叫作 Matrix ,该层是整个 TDDL 最为核心的地方,淘宝也并无对这一层实现开源,而 Matrix 层主要是创建在动态数据源之上的分库分表实现。换句话说, TDDL 是基于模块化结构的,开发人员能够选用 TDDL 中的部分子集。
你们能够从淘宝的 TaoCode 上下载 TDDL 的源码带,而后进行构件的打包。TDDL 的项目主要是基于 Maven 进行管理的,因此建议你们若是不了解 Maven 的使用,仍是参考下笔者的博文《 Use Maven3.x 》。
你们下载好 TDDL 的源代码后,经过 IDE 工具导入进来后能够发现,开源的TDDL 的工程结构有以下几部份组成:
tddl-all –
— tbdatasource
— tddl-atom-datasource
— tddl-common
— tddl-group-datasource
— tddl-interact
— tddl-sample
你们可使用 Maven 的命令“ mvn package “将 TDDL 的源代码打包成构件。若是你的电脑上并无安装 Maven 的插件到不是没有办法实现构件打包,你可使用eclipse 的导出命令,将源代码导出成构件形式也能够。
4、 Diamond 简介
使用任何一种框架都须要配置一些配置源信息,毕竟每一种框架都有本身的规范,使用者务必遵照这些规范来实现本身的业务与基础框架的整合。天然 TDDL 也不例外,也是有配置信息须要显式的进行配置,在 TDDL 中,配置能够基于 2 种方式,一种是基于本地配置文件的形式,另一种则是基于 Diamond 的形式进行配置,在实际开发过程当中,因为考虑到配置信息的集中管理所带来的好处,大部分开发人员愿意选择将 TDDL 的配置信息托管给 Diamond ,因此本文仍是以Diamond 做为 TDDL 的配置源。
diamond 是淘宝内部使用的一个管理持久配置的系统,它的特色是简单、可靠、易用,目前淘宝内部绝大多数系统的配置,由 diamond 来进行统一管理。diamond 为应用系统提供了获取配置的服务,应用不只能够在启动时从 diamond获取相关的配置,并且能够在运行中对配置数据的变化进行感知并获取变化后的配置数据。
5、 Diamond 的安装和使用
Diamond 和 TDDL 不一样,它已经实现了彻底意义上的开源。你们能够从淘宝的TaoCode
上下载 Diamond 的源代码, SVN 下载地址为http://code.taobao.org/svn/diamond/trunk 。当你们成功下载好 Diamond 的源代码后,咱们接下来就须要开始 Diamond 的环境搭建工做。
首先咱们须要安装好 Mysql 数据库,以 root 用户登陆,创建用户并赋予权限,创建数据库,而后建表,语句分别以下:
create database diamond;
grant all on diamond.* to zh@’%’ identified by ‘abc’;
use diamond
create table config_info (
‘ id’ bigint(64) unsigned NOT NULL auto_increment,
‘ data_id’ varchar(255) NOT NULL default ’ ’,
‘ group_id’ varchar(128) NOT NULL default ’ ’,
‘ content’ longtext NOT NULL,
‘ md5 ′ varchar(32) NOT NULL default ’ ’ ,
‘ gmt_create ’ datetime NOT NULL default ’ 2010-05-05 00:00:00 ′ ,
‘ gmt_modified ’ datetime NOT NULL default ’ 2010-05-05 00:00:00 ′ ,
PRIMARY KEY (‘id’),
UNIQUE KEY ‘uk_config_datagroup’ (‘data_id’,'group_id’));
完成后,请将数据库的配置信息( IP ,用户名,密码)添加到 diamond-server 工程的 src/resources/jdbc.properties 文件中的 db.url , db.user , db.password 属性上面,这里创建的库名,用户名和密码,必须和 jdbc.properties 中对应的属性相同。
tomcat 是 Damond 的运行容器,在 diamond-server 源代码根目录下,执行 mvn clean package -Dmaven.test.skip ,成功后会在 diamond-server/target 目录下生成diamond-server.war 。打包完成后,将 diamond-server.war 放在 tomcat 的webapps 目录下。最后启动 tomcat ,即启动了 Diamond 。
http server 用来存放 diamond server 等地址列表,能够选用任何 http server ,这里以 tomcat 为例。通常来说, http server 和 diamond server 是部署在不一样机器上的,这里简单起见,将两者部署在同一个机器下的同一个 tomcat 的同一个应用中,注意,若是部署在不一样的 tomcat 中,端口号必定是 8080 ,不能修改(因此必须部署在不一样的机器上)。
在 tomcat 的 webapps 中的 diamond-server 中创建文件 diamond ,文件内容是diamond-server 的地址列表,一行一个地址,地址为 IP ,例如 127.0.0.1 ,完成这些步骤后,就等于已经完成 Diamond 的安装。
6、动态数据源层的 Master/Salve 读写分离 配置与实现
其实使用 TDDL 并不复杂,只要你会使用 JDBC ,那么 TDDL 对于你来讲无非就只须要将 JDBC 的操做链接替换为 TDDL 的操做链接,剩余操做如出一辙。而且因为 TDDL 遵循了 JDBC 规范,因此你彻底还可使用 Spring JDBC 、 Hibernate等第三方持久层框架进行 ORM 操做。
咱们来看看如何 TDDL 中配置 TDDL 的读写分离, Atom+Group 组成了 TDDL 的动态数据源,这 2 层主要负责数据库的读写分离。
TGroupDataSource 的配置
一、 配置读写分离权重:
KEY : com.taobao.tddl.jdbc.group_V2.4.1_ “ groupKey ” (Matrix 中为“ dbKey ” )
VALUE : dbKey:r10w0,dbKey2:r0w10
TAtomDataSource 的配置(由 3 部分组成, global 、 app 、 user )
一、 基本数据源信息 (global) :
KEY : com.taobao.tddl.atom.global. “ dbKey ”
VALUE :(
ip= 数据库 IP
port= 数据库端口
dbName= 数据库昵称
dbType= 数据库类型
dbStatus=RW )
二、 数据库密码信息 (user) :
KEY : com.taobao.tddl.atom.passwd. “ dbKey ” . “ dbType ” . “ dbUserName ”
VALUE :数据库密码
三、 数据库链接信息( app ,若是不配置时间单位,缺省为分钟):
KEY : com.taobao.tddl.atom.app. “ appName ” . “ dbKey ”
VALUE :(
userName= 数据库用户
minPoolSize= 最小链接数
maxPoolSize= 最大链接数
idleTimeout= 链接的最大空闲时间
blockingTimeout= 等待链接的最大时间
checkValidConnectionSQL=select 1
connectionProperties=rewriteBatchedStatements=true&characterEncoding=UTF8&connectTimeout=1000&autoReconnect=true&socketTimeout=12000 )
应用层使用 TDDL 示例:
public class UseTDDL { private static final String APPNAME = "tddl_test"; private static final String GROUP_KEY = "tddltest"; private static TGroupDataSource tGroupDataSource ; /* 初始化动态数据源 */ static { tGroupDataSource = new TGroupDataSource(); tGroupDataSource .setAppName( APPNAME ); tGroupDataSource .setDbGroupKey( GROUP_KEY ); tGroupDataSource .init(); } @Test public void testQuery() { final String LOAD_USER = "SELECT userName FROM tddl_table WHERE userName=?"; Connection conn = null ; PreparedStatement pstmt = null ; ResultSet rs = null ; try { conn = tGroupDataSource .getConnection(); pstmt = conn.prepareStatement(LOAD_USER); pstmt.setString(1, "tddl-test2"); rs = pstmt.executeQuery(); while (rs.next()) System. out .println("data: " + rs.getString(1)); } catch (Exception e) { e.printStackTrace(); } finally { try { if ( null != rs) rs.close(); if ( null != pstmt) pstmt.close(); if ( null != conn) conn.close(); } catch (Exception e) { e.printStackTrace(); } } } }
|
7、 Matrix 层的分库分表配置与实现
在上一章节中,笔者演示了如何在 Diamond 中配置数据库的读写分离,那么本章笔者则会演示若是配置 TDDL 的分库分表。
TDDL 的 Matrix 层是创建在动态数据源之上的,因此分库分表的配置和读写分离的基本配置也是同样的,只不过咱们须要新添加 dbgroups 和 shardrule 项。dbgroups 项包含了咱们所须要配置的全部 AppName 选项,而 shardrule 则是具体的分库分表规则。这里有一点须要提醒各位,在开源版本的 TDDL 中,配置TGroupDataSource 读写分离是使用 dbKey ,然而在 Matrix 中则是使用appName 。
1 、配置 Group 组:
KEY : com.taobao.tddl.v1_ “ appName ” _dbgroups
VALUE : appName1 , appName2
2 、配置分库分表规则:
KEY : com.taobao.tddl.v1_”appName”_shardrule
VALUE :(
<?xml version="1.0" encoding="gb2312"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
<bean id="root" class="com.taobao.tddl.common.config.beans.AppRule" init-method="init">
<property name="readwriteRule" ref="readwriteRule" />
</bean>
<bean id="readwriteRule" class="com.taobao.tddl.common.config.beans.ShardRule">
<property name="dbtype" value="MYSQL" />
<property name="tableRules">
<map>
<entry key="tddl_table" value-ref="tddl_table" />
</map>
</property>
</bean>
<bean id="tddl_table" init-method="init"
class="com.taobao.tddl.common.config.beans.TableRule">
<!-- 数据库组 index 号 -->
<property name="dbIndexes" value="tddl_test,tddl_test2" />
<!-- 分库规则 -->
<property name="dbRuleArray" value="(#id#.longValue() % 4).intdiv(2)"/>
<!-- 分表规则 , 须要注意的是,由于 taobao 目前 dba 的要求是全部库内的表名必须彻底不一样,所以这里多加了一个映射的关系
简单来讲,分表规则只会算表的 key.
俩库 4 表 : db1(tab1+tab2) db2(tab3+tab4)
db1 == key: 0 value tab1
key: 1 value tab2
db2 == key: 0 value tab3
key: 1 value tab4
-->
<property name="tbRuleArray" value="#id#.longValue() % 4 % 2"/>
<property name="tbSuffix" value="throughAllDB:[_0-_3]" />
</bean>
</beans>
)
TDDL 的分库分表配置形式彻底是采用 Spring 的配置形式,这一点你们应该是很是熟悉的。那么接下来咱们一步一步的分析 TDDL 的分库分表规则。
在元素 <map/> 中咱们能够定义咱们所须要的分表,也就是说,当有多个表须要实现分表逻辑的时候,咱们能够在集合中进行定义。固然咱们还须要外部引用<bean/> 标签中定义的具体的表逻辑的分库分表规则。
在分库分表规则中,咱们须要定义 数据库组 index 号,也就是说咱们须要定义咱们有多少的 appNames ,接下来咱们就能够定义分库和分表规则了。 TDDL的分库分表规则彻底是采用取余方式,好比 <property name="dbRuleArray" value="(#id#.longValue() % 4).intdiv(2)"/> , value 属性中包含有具体的分库规则,其中“ #id# ”做为咱们的分库分表条件,此值在数据库中对应的类型必须是整类,而后进行取余后再进行 intdiv 。或许有些朋友看不太明白这个是什么意思,咱们用简单的一点的话来讲就是,“ #id#.longValue() % 4).intdiv(2) ”的含义是咱们须要分 2个库和 4 个表,那么咱们怎么知道咱们的数据到底落盘到哪个库呢?打个比方,若是咱们的 id 等于 10 ,首先 10%4 等于 2 ,而后 2/2 等于 1 , TDDL 分库规则下标从 0 开始,那么咱们的数据就是落盘到第 2 个库。
当你们明白 TDDL 的分库规则后,咱们接下来再来分析分表规则 <property name="tbRuleArray" value="#id#.longValue() % 4 % 2"/> 。和分库规则相似的是,咱们都采用取余算法首先进行运算,只不过度表尾运算也是使用取余,而不是除算。打个比方,若是咱们的 id 等于 10 ,首先 10%4 等于 2 ,而后 2%2 等于 0 ,那么咱们的数据就是落盘到第 2 个库的第 1 张表。
应用层使用 TDDL 示例:
public class UseTDDL { private static final String APPNAME = "tddl_test"; private static final TDataSource dataSource ; /* 初始化动态数据源 */ static { dataSource = new TDataSource(); dataSource .setAppName( APPNAME ); dataSource .setUseLocalConfig( false ); dataSource .setDynamicRule( false ); dataSource .init(); } @Test public void query() { final String LOAD_USER = "SELECT userName FROM tddl_table WHERE id = ?"; Connection conn = null ; PreparedStatement pstmt = null ; ResultSet rs = null ; try { conn = dataSource .getConnection(); pstmt = conn.prepareStatement(LOAD_USER); pstmt.setLong(1, 3); rs = pstmt.executeQuery(); while (rs.next()) System. out .println("data: " + rs.getString(1)); } catch (Exception e) { e.printStackTrace(); } finally { try { if ( null != rs) rs.close(); if ( null != pstmt) pstmt.close(); if ( null != conn) conn.close(); } catch (Exception e) { e.printStackTrace(); } } } @Test public void insert() { final String LOAD_USER = "insert into tddl_table values(?, ?)"; Connection conn = null ; PreparedStatement pstmt = null ; try { conn = dataSource .getConnection(); pstmt = conn.prepareStatement(LOAD_USER); pstmt.setLong(1, 10); pstmt.setString(2, "JohnGao"); pstmt.execute(); System. out .println("insert success..."); } catch (Exception e) { e.printStackTrace(); } finally { try { if ( null != pstmt) pstmt.close(); if ( null != conn) conn.close(); } catch (Exception e) { e.printStackTrace(); } } } } |