CobarClient是阿里巴巴公司开发一个的开源的、基于iBatis和Spring的分布式数据库访问层。为了支持iBatis,Spring框架提供了一个SqlMapClientTemplate,通过模板模式简化了在Spring框架中对于iBatis中的使用。而CobarClient则继承了SqlMapClientTemplate,提供了CobarSqlMapClientTemplate给应用使用。CobarSqlMapClientTemplate和SqlMapClientTemplate继承关系如下:

在 CobarSqlMapClientTemplate持有的众多属性中,比较重要的有cobarDataSourceService(提供多数据源的管理服务)、router(Sql路由接口的实现)、concurrentRequestProcessor(Sql并发执行的命令类),下面就从路由规则到最终的Sql执行来分析一下CobarClient的原理。

  1. 路由规则的生成与执行。

根据CobarClient的文档,CobarClient一共提供了四种路由规则:

  • NamespaceShardingRule:针对iBatis中Sql Map定义的,属于某一个NameSpace的Sql语句执行某一个路由规则,根据路由的结果选择一个或者多个数据分区。
  • Namespace(Only)Rule:针对iBatis中Sql Map定义的,属于某一个NameSpace的Sql语句无差别的将Sql语句路由到一个或者多个数据分区。
  • SqlActionShardingRule:针对iBatis中Sql Map定义的某一个Sql Id对应的Sql语句,执行某一个路由规则,根据路由的结果选择一个或者多个数据分区。
  • SqlAction(Only)Rule:针对iBatis中Sql Map定义的某一个Sql Id对应的Sql语句,无差别的将这个语句路由到一个或者多个数据分区。

在CobarClient中这四个路由规则,分别对应四个具体的实现类,而这四个实现类,都实现了IRoutingRule这个接口,继承关系如下所示:

在4种路由规则分别具体对应的实现类中,都实现了isDefinedAt(IBatisRoutingFact routingFact)这个方法,这个方法实现的功能判断现在要执行的Sql语句是不是可以匹配当前的路由规则实例。这个方法的参数,routingFact的类型是IBatisRoutingFact,代表的是一个路由上下文信息:

public class IBatisRoutingFact {
  private String action; // SQL identity
  private Object argument; // the argument of SQL action
}
举个例子,看看IBatisNamespaceRule类中isDefinedAt方法的实现如下:
public boolean isDefinedAt(IBatisRoutingFact routingFact) {
  Validate.notNull(routingFact);
  String namespace = StringUtils.substringBeforeLast(routingFact.getAction(), ".");
  return StringUtils.equals(namespace, getTypePattern());
}

实际完成的工作很简单,就是看看当前的Sql Id的NameSpace是否和规则定义的NameSpace一致,如果一致,表示使用该路由规则。清楚了路由规则的实现之后,那么这么多路由规则又是怎么汇总到CobarSqlMapClientTemplate中的呢?根据CobarClient文档中的说明:

默认情况下, CobarClientInternalRouter将接收4组不同类型的路由规则, 但路由规则的类型对于用户来说实际上是不必要的, 所以, 为了避免用户过多的纠缠于CobarClientInternalRouter的实现细节, 我们给出了针对CobarClientInternalRouter配置的一个Spring的FactoryBean实现, 以帮助简化CobarClientInternalRouter的配置, 该FactoryBean实现类为com.alibaba.cobar.client.router.config.CobarInteralRouterXmlFactoryBean。CobarInteralRouterXmlFactoryBean将根据指定的xml形式的配置文件中的内容, 自动构建不同类型的路由规则, 然后注入到它将最终返回的CobarClientInternalRouter实例之上。 而读取, 解析配置信息, 并构建不同类型路由规则等 “琐事” 将完全对用户透明。

一个典型的路由规则文件可能如下

<rules>
  <rule>
    <namespace>com.alibaba.cobar.client.entity.Follower</namespace>
    <shards>partition1</shards>
  </rule>
  <rule>
    <sqlmap>com.alibaba.cobar.client.entity.Follower.create</sqlmap>
    <shards>p1, p2</shards>
  </rule>
  <rule>
    <sqlmap>com.alibaba.cobar.client.entity.Follower.create</sqlmap>
    <shardingExpression>id&gt;10000 and id&lt; 20000</shardingExpression>
    <shards>p1, p2</shards>
  </rule>
  <rule>
    <namespace>com.alibaba.cobar.client.entity.Follower</namespace>
    <shardingExpression>id&gt;10000 and id&lt; 20000</shardingExpression>
    <shards>p1, p2</shards>
  </rule>
</rules>
解析了完所有的路由规则配置文件之后,CobarClientInternalRouter类的实例将持有一个List,该List的定义如下:
private List<Set<IRoutingRule<IBatisRoutingFact, List<String>>>> ruleSequences

实际上,这个List只有4个元素,每一个元素是一个集合,分别对应之前提到的四种路由规则。而每个集合中的元素都是一个具体的规则实现类。当具体执行一个Sql语句之前,CobarSqlMapClientTemplate通过它持有的CobarClientInternalRouter实例的doRoute方法来实现具体的路由规则选择,可见CobarClientInternalRouter实际上充当了一个门面(Facade)的作用,汇总了所有的路由信息,而且CobarSqlMapClientTemplate是一个更高层的门面。
   在doRoute方法中,有两层循环,第一层循环,遍历ruleSequence这个List,第二层循环遍历List中每个Set

for (Set<IRoutingRule<IBatisRoutingFact, List<String>>> ruleSet : getRuleSequences()) {
  ruleToUse = searchMatchedRuleAgainst(ruleSet, routingFact);
    if (ruleToUse != null) {
      break;
    }
}
在searchMatchedRuleAgainst方法中,执行第二次遍历
for (IRoutingRule<IBatisRoutingFact, List<String>> rule : rules) {
  if (rule.isDefinedAt(routingFact)) {
      return rule;
    }
}

在这里最终调用之前提到的isDefinedAt方法,两次遍历了ruleSequence之后,将获得一个Map,
SortedMap<String, DataSource> resultMap 表示的这次Sql语句执行需要用对应的数据源。

2.Sql的执行
   当CobarSqlMapClientTemplate通过CobarClientInternalRouter获取到了本次Sql执行对应的数据源之后,将进入到Sql执行的阶段。在Spring框架中,提供了一个接口SqlMapClientCallback接口,CobarSqlMapClientTemplate在每一次执行Sql语句之前,都会生成一个SqlMapClientCallback的内部类,该内部类实现了SqlMapClientCallback接口中定义的回调函数,doInSqlMapClient。例如,在某一次查询语句之前,对应的内部类可能是:

callback = new SqlMapClientCallback() {
  public Object doInSqlMapClient(SqlMapExecutor executor) throws SQLException {
    return executor.queryForList(statementName, parameterObject,skipResults, maxResults);
    }
};

具体到Sql语句的执行,CobarClient命令模式结合回调函数的方式来实现,标准的命令模式结构图如下:

在CobarSqlMapClientTemplate中,担任ConcreteCommand角色的是IConcurrentRequestProcessor,它的执行方法为:

List<Object> process(List<ConcurrentRequest> requests)

不同的是,为了支持Sql并行执行,这里传入的不是单个的request,而是由多个ConcurrentRequest类型的request组成的List(这里的ConcurrentRequest可以看成命令模式的中的Receiver),不同的是在ConcurrentRequest类中是通过多线程加调用SqlMapClientCallback中的回调函数的方式来实现命令模式中Receiver的action方法的。在IConcurrentRequestProcessor调用process方法之后,会通过多线程的方式来执行SqlMapClientCallback中的回调函数,

request.getExecutor().submit(new Callable<Object>() {
  public Object call() throws Exception {
    try {
      return executeWith(connection, action);
    } finally {
      latch.countDown();
    }
  }
} )

executeWith中connection具体的数据源的连接,action就是之前提到的匿名内部类,在executeWith方法中,会调用这个内部的回调函数:

protected Object executeWith(Connection connection, SqlMapClientCallback action) {
        SqlMapSession session = getSqlMapClient().openSession();
        try {
            try {
                session.setUserConnection(connection);
            } catch (SQLException e) {
                throw new CannotGetJdbcConnectionException("Could not get JDBC Connection", e);
            }
            try {
                return action.doInSqlMapClient(session);
            } catch (SQLException ex) {
                throw new SQLErrorCodeSQLExceptionTranslator().translate("SqlMapClient operation",
                        null, ex);
            }
        } finally {
            session.close();
        }
    }

当执行完SqlMapClientCallback中的doInSqlMapClient方法之后,CobarSqlMapClientTemplate还会对多个Sql语句执行获取的结果进行简单的合并之后在返回给应用程序,至此,整个Sql的分布式数据库执行才算结束。

3. 一点体会
CobarClient扩展了Spring提供的SqlMapClientTemplate,在Sql执行之前根据数据路由规则获取了真正要执行的数据分区,在Sql执行之后在进行结果集的简单合并,最终实现了分布式数据库的数据路由功能,但是CorBarClient不是没有缺点

  1. 数据合并功能的羸弱:CobarClient并没有引入Sql语句的解析,对于数据的合并是在应用层做的,而且现在只能支持简单的合并,当应用程序有比较复杂的结果集处理要求的时候,例如order by,group by等,需要自己去实现merge类,而且由于没有Sql语句的解析,即使实现了,也很难做到通用。
  2. 路由算法的效率:根据CobarClient的文档:
             我们可以根据情况提供不同的ICobarRouter实现类, 比如Cobar Client默认提供的com.alibaba.cobar.client.router.CobarClientInternalRouter和 om.alibaba.cobar.client.router.DefaultCobarClientInternalRouter, 或者如果路由规则数量很多, 为了保证性能, 也可以实现基于Rete等算法的实现类等. 没有特殊需求的情况下,我们默认采用CobarClientInternalRouter作为CobarSqlMapClientTemplate使用的 默认Router实现. 但用户也可以根据情况选用DefaultCobarClientInternalRouter, 二者的使用是类似的。DefaultCobarClientInternalRouter在CobarClientInternalRouter的基础 上, 对路由规则的匹配进行了分组优化, 通过配置时期的复杂度换取运行时期的简单高效。 如果规则很多的话,可以考虑使用DefaultCobarClientInternalRouter。
            之前代码分析的CobarClientInternalRouter的doRoute实现,在两层的循环中,如果有n个具体的路由规则,那么最坏的情况,要执行n次路由判断,算法的效率是O(n)。文档说DefaultCobarClientInternalRouter的doRoute实现效率更高(但是DefaultCobarClientInternalRouter的doRoute方法没有用到cache,而CobarClientInternalRouter用到了),但实际上只是对于ruleSequences根据Namespace做了分组,那么当有n个路由规则,m个namespace的时候,算法的效率是O(n/m)。由于在每个sql语句执行之前,都需要执行doRoute方法来路由,所以如果用HashMap来保存Sql Id和路由规则的对应关系,算法的效率将提高到O(1),这样算法的时间消耗不会随着路由规则的增多而增长。
  3. 路由规则的设计和解析:路由规则的解析,CobarClient使用的XStream这个开源的包,这样的话,当路由规则文件要新增结点或者某个结点要新增属性的时候,XStream并不能做到很灵活。此外,CobarInteralRouterXmlFactoryBean中根据路由规则解析文件的时候,基本上都是用if else的语句来做判断的,这是一种代码的坏味道。此外,根据之前路由规则的继承图可以看到,在具体的路由规则实现类之上,还有三个抽象的路由规则类,我推倒是CobarClient的设计者刚开始的时候,是为了以后的路由规则能够扩展,但是他最后发现四种规则就能涵盖一切情况,但是没有对代码进行必要的重构。
  4. 代码的风格:这是一个小问题,但是确实很影响代码的理解。例如在iBatis语境中默认的Sql Id到了IBatisRoutingFact就变成
    private String action了。Rule文件中的namespace到了具体的规则类之中,就变成了private String typePatten。这种情况充斥于整个代码之中,于是最后你能发现,ICobarRouter的默认实现类是CobarClientInternalRouter而不是DefaultCobarClientInternalRouter。

瑕不掩瑜,如果需要一个轻量级的,支持分布式数据库的数据访问层框架,并且应用不需要对于查询的结果集做过于复杂的排序聚会等操作,CobarClient是一个不错的选择。

CobarClient源码分析的更多相关文章

  1. CobarClient源码分析(1)

    CobarClient是阿里巴巴公司开发一个的开源的.基于iBatis和Spring的分布式数据库访问层.为了支持iBatis,Spring框架提供了一个SqlMapClientTemplate,通过 ...

  2. ABP源码分析一:整体项目结构及目录

    ABP是一套非常优秀的web应用程序架构,适合用来搭建集中式架构的web应用程序. 整个Abp的Infrastructure是以Abp这个package为核心模块(core)+15个模块(module ...

  3. HashMap与TreeMap源码分析

    1. 引言     在红黑树--算法导论(15)中学习了红黑树的原理.本来打算自己来试着实现一下,然而在看了JDK(1.8.0)TreeMap的源码后恍然发现原来它就是利用红黑树实现的(很惭愧学了Ja ...

  4. nginx源码分析之网络初始化

    nginx作为一个高性能的HTTP服务器,网络的处理是其核心,了解网络的初始化有助于加深对nginx网络处理的了解,本文主要通过nginx的源代码来分析其网络初始化. 从配置文件中读取初始化信息 与网 ...

  5. zookeeper源码分析之五服务端(集群leader)处理请求流程

    leader的实现类为LeaderZooKeeperServer,它间接继承自标准ZookeeperServer.它规定了请求到达leader时需要经历的路径: PrepRequestProcesso ...

  6. zookeeper源码分析之四服务端(单机)处理请求流程

    上文: zookeeper源码分析之一服务端启动过程 中,我们介绍了zookeeper服务器的启动过程,其中单机是ZookeeperServer启动,集群使用QuorumPeer启动,那么这次我们分析 ...

  7. zookeeper源码分析之三客户端发送请求流程

    znode 可以被监控,包括这个目录节点中存储的数据的修改,子节点目录的变化等,一旦变化可以通知设置监控的客户端,这个功能是zookeeper对于应用最重要的特性,通过这个特性可以实现的功能包括配置的 ...

  8. java使用websocket,并且获取HttpSession,源码分析

    转载请在页首注明作者与出处 http://www.cnblogs.com/zhuxiaojie/p/6238826.html 一:本文使用范围 此文不仅仅局限于spring boot,普通的sprin ...

  9. ABP源码分析二:ABP中配置的注册和初始化

    一般来说,ASP.NET Web应用程序的第一个执行的方法是Global.asax下定义的Start方法.执行这个方法前HttpApplication 实例必须存在,也就是说其构造函数的执行必然是完成 ...

随机推荐

  1. Linux crontab命令详解

    crontab:定时任务的守护进程,精确到分,设计秒的我们一般写脚本  -->相当于闹钟        日志文件:  ll /var/log/cron*        编辑文件: vim /et ...

  2. 为什么mysql要做主从复制?

    为什么MySQL要做主从复制(读写分离)? 通俗来讲,如果对数据库的读和写都在同一个数据库服务器中操作,业务系统性能会降低. 为了提升业务系统性能,优化用户体验,可以通过做主从复制(读写分离)来减轻主 ...

  3. IP地址编址

    比特:一比特就是一个数字,1或者0. 字节:以字节是7比特或者8比特,取决于是否使用奇偶校验 八位组:8比特构成 网络地址:用来将数据包发送到远端网路 比如10.0.0.0 广播地址:将信息发送给网络 ...

  4. 【转】【Flex】#010 操作XML文件(E4X)

    该教程转载来自于:http://blog.chinaunix.net/uid-14767524-id-2785506.html    [看到这边文章的位置,具体原作者未知] 经过一些排版的修改,其他内 ...

  5. PAT 1001A+B Format

    Github 1001 题目速览 1.解题的思路过程 认真读题,题目为A+BFormat,简单的计算a+b问题,特殊在于输出的形式. 输入形式为每个输入文件包含一个测试样例,每个测试样例仅包含一对整型 ...

  6. 团队作业1——团队展示&教辅宝

    1.队名:PHILOSOPHER 2.队员学号: [组长]金盛昌(201421122043).刘文钊(20142112255).陈笑林(201421122042). 张俊逸(201421122044) ...

  7. 手写阻塞队列(Condition实现)

    自己实现阻塞队列的话可以采用Object下的wait和notify方法,也可以使用Lock锁提供的Condition来实现,本文就是自己手撸的一个简单的阻塞队列,部分借鉴了JDK的源码.Ps:最近看面 ...

  8. Spring 读取配置文件的俩种方式

    读取配置可通过 org.springframework.core.env.Environment 类来获取, 也可以通过@Value的方式来获取 注解形式: @PropertySource({&quo ...

  9. 51 nod 1682 中位数计数

    题目链接:https://www.51nod.com/onlineJudge/questionCode.html#!problemId=1682 1682 中位数计数 基准时间限制:1 秒 空间限制: ...

  10. Java并发编程--7.Java内存操作总结

    主内存和工作内存 工作规则 Java内存模型, 定义变量的访问规则, 即将共享变量存储到内存和取出内存的底层细节  所有的变量都存储在主内存中,每条线程有自己的工作内存,工作内存中用到的变量, 是从主 ...