分库分表中间件DDAL的设计及使用

原文地址:https://zhuanlan.zhihu.com/p/25166375


hellojavaer/ddalgithub.com分库分表中间件DDAL的设计及使用_第1张图片

1. DDAL分层架构设计

DDAL在设计上主要可以分为三层:

  1. 最上层为DDALDataSource,用于解析数据源的富客户端(smart client)和轻客户端(light client)的协议实现,目前DDAL实现的是富客户端(smart client);

    
    

  1. 中间层为DBClusterManager,用于实现数据库多集群的路由管理,实际中的一个应用场景为应用访问就近数据库集群(需要注意这里定义的集群的含义是每个集群的功能是对等的,而非主从的关系的集群);
  2. 底层为DDRDataSource,它是真正用于实现分库分表路由的组件实现;
分库分表中间件DDAL的设计及使用_第2张图片

基于上图架构的一个完整的工程示例:

hellojavaer/ddalgithub.com分库分表中间件DDAL的设计及使用_第3张图片

另一个简单的读写分离的工程示例

hellojavaer/ddalgithub.com分库分表中间件DDAL的设计及使用_第4张图片


2. DDAL的执行原理

  1. 定义分库分表路由规则


    
    

2.绑定路由规则到逻辑表名或逻辑库(逻辑schema)上,并设置分片字段(也可以不设置分片字段)


    
    
    
    
    
    
    
    
    
    

通过这个绑定,可以计算出一个逻辑表名对应的物理表名和一个逻辑库(schema)对应的为物理库(schema)

3. 解析并重写sql

DDAL支持的sql主语法有select,insert,delete,update,支持主语法下的几乎所有sql子语法,包括sub-select,union,exsit, join等

解析sql的过程就是识别sql中的所有表名,如果某一个表名能匹配到步骤2中的逻辑表名,则该表名会根据步骤1中规则进行表名重写,如果没匹配到则不做任何操作,同时所有被重写后的表名会被放在一个结果集用于步骤4数据源的获取。

在重写sql时一个关键的点是获取路由信息,DDAL对分表路由信息的获取分为两大类:

(1)通过sql中的分片值

(2)通过ShardRouteContext;

其中方式(1)优先于方式(2),只有当方式(1)获取失败后才会尝试从方式(2)中获取。而如果两种方式都不能获取到路由信息则会抛出异常,比如:

select * from tb where id = 9

上面的tb匹配了步骤2的绑定,因此会执行表名重写;而id字段又匹配了分片字段,因此会执行方式(1)的分片值重写,重写后的结果为

SELECT * FROM base_01.tb_0001 WHERE id = 9

而如果当sql中不含分片字段或分片字段不能计算路由信息时(比如: id != 9)

select * from tb where WHERE name = 'test'

这时可以通过ShardRouteContext设置路由信息

ShardRouteContext.setRouteInfo('base', 9);

重新后的sql为

SELECT * FROM base_01.tb_0001 WHERE name = 'test'

同时DDAL为了简化ShardRouteContext的调用,提供ShardRoute注解进行配置,注解的配置和ShardRouteContext是完全等效的,因为ShardRoute底层使用的就是ShardRouteContext,例如:

 // 当id的值为9时,路由结果的信息完全等同于 ShardRouteContext.setRouteInfo('base', 9);
 @ShardRoute(scName = "base", sdValue = "{$0}")
 public void test(Long id){}


4.选取数据源并执行

这一步的核心是数据源管理器,它通过将步骤3中计算出的物理库的结果集用于筛选出实际的数据源去执行。而DDAL目前的数据源管理器包含两个:SingleDataSourceManager和DefaultReadWriteDataSourceManager。

SingleDataSourceManager不依赖步骤3计算的物理库结果集,因为它内部只管理了一个数据源,所以不论步骤3返回什么结果集,SingleDataSourceManager都只会把它管理的那一个数据源返回用于sql的执行;

DefaultReadWriteDataSourceManager实现了一个读写分离的数据源管理器,读和写都可以配置多个数据源,读数据源支持配置负载权重。由于内部包含多个数据源因此步骤3的物理库结果集必须至少包含一个结果,用于匹配实际的数据源(如果未匹配中则会抛出异常)


以上完整的配置信息参考以下链接

https://github.com/hellojavaer/ddal/blob/master/ddal-example/ddal-example-example0/src/main/resources/datasource.xmlgithub.com

3.DDAL对分布式事务的处理

DDAL的设计方向完整的保留数据库ACID的所有特性,在一个数据源连接下的跨schema和跨table操作是允许的(因为底层数据库是支持的);当出现跨连接操作时,DDAL设计方案是将跨连接的数据进行分组使得分组后的每组数据都在一个连接内。在DDAL中提供的一个实现类是ShardRouteUtils,你可以先使用groupSdValuesByRouteInfo对shard-value进行分组,然后使用groupRouteInfosByDataSource对关联的数据源进行分组。分组后的shard-value再进行操作时每一组的操作就都能完整的保证ACID,不同分组间的操作异常由业务自行进行控制;

4.DDAL分布式主键设计

参考以下链接

hellojavaer:ddal-sequence设计方案zhuanlan.zhihu.com分库分表中间件DDAL的设计及使用_第5张图片

5. DDAL特性概览

  • 1. 使用上对业务代码没有任何入侵:只需要代理原有数据源即可实现分表路由功能,DDAL除了依赖jdbc本身的相关api外,不依赖spring,hibernate,mybatis等其他提框架提供的数据访问层的接口和实现;
  • 2. 同时支持单数据源模式和读写组合数据源模式,灵活解决数据库从单一数据库发展到数据集群的不同阶段需要的不同解决方案;
  • 3. 最小程度限制sql:DDAL对sql的限制主要分为两点。1)禁止跨数据源查询,2)分表需要提供分表路由信息;在满足这两点的前提下你的sql几乎是'自由的':允许多表jion,允许嵌套子查询,可以对分表字段使用in和between操作以及not操作;(相关设计细节参考DDAL wiki)
  • 4. 分表路由规则配置灵活且简单:分表路由的字段可以是整型也可以是字符型,这里没有任何限制,你可以根据自己的业务规则灵活配置;
  • 5. 支持分表不含分片字段;
  • 6. 支持读写分离;
  • 7. 支持数据源负载均衡配置且可以动态调整:DDAL默认提供了通过MBean动态调节数据库负载的方式,使用上只需要在启动应用后开启jconsole就可以控制各个数据源的负载;
  • 8. 支持无分库分表的普通主从模式:在业务发展的初期,数据库可能并没有做分库分表,但为了提高读性能可能会发展为读写分离的主从模式数据库集群。在这种场景下只需要省略掉分表的配置即可方案实现该业务功能;
  • 9. 支持注解方式的分库分表路由;
  • 10. 支持数据库集群路由
  • 11. 提供分布式主键模块:参考 wiki;

你可能感兴趣的:(分库分表中间件DDAL的设计及使用)