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)的更多相关文章

  1. CobarClient源码分析

    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. 标清与高清,720p和1080p,720i和720p,h264与h265

    480x320, 640x480 标清 1024x720p 高清 1920x1080i (隔行扫描) 也属于高清 1920x1080p 全高清 3840x2160,7680x4320 超(高)清   ...

  2. Clustering with the ArcGIS API for Flex

    Clustering is an excellent technique for visualizing lotss of point data. We've all seen application ...

  3. ZeroClipboard实现复制

    今天利用ZeroClipboard实现了批量复制ip到剪贴版的公里,第一种方式总是有错,需要点击两次才能成功复制,第二种方法成功.在这里记录下,方法一: $('#copy_ips').zclip({ ...

  4. Discuz开源论坛本地部署自动生成数据库

    这个版本可能比较有点老,但是万变不离其宗,再新的版本都是在已有的基础上更新的,所以掌握方法是最重要的! 先上几张安装成功后的图 (安装成功的论坛首页) (后台管理登录页) 下面说一下安装步骤 第一步: ...

  5. Spark Streaming源码解读之Receiver生成全生命周期彻底研究和思考

    本期内容 : Receiver启动的方式设想 Receiver启动源码彻底分析 多个输入源输入启动,Receiver启动失败,只要我们的集群存在就希望Receiver启动成功,运行过程中基于每个Tea ...

  6. skiplist

    §1 Skip List 介绍 Skip List是一种随机化的数据结构,基于并联的链表,其效率可比拟于二叉查找树(对于大多数操作需要O(log n)平均时间).基本上, 跳跃列表是对有序的链表增加上 ...

  7. webService访问加密

    WebService加密,可以对 WebService设置访问用户名和密码,增强 WebService的安全性 使 WebService只能被授权用户使用. 具体实现步骤: 1. 定义一个 soaph ...

  8. ITop

    iTop,即IT运营门户(IT Operation Portal),是一个开源web应用程序,用于IT环境的日常运营.它基于ITIL最佳实践,而又不拘泥于任何具体流程.它很灵活,可以适应不管是非正 式 ...

  9. List集合转换为数组形式

    通过List集合对象的转类型函数 .ToArray() 如List<decimal> → deciaml[] 代码如下: var ComIdList = ComList.Select(p ...

  10. objective c, protocol

    OC中协议类似于java中的接口,在多个类具有类似的方法时可以将这些方法定义到protocol中,然后各个类分别实现protocol中的各个方法. 例:有两个类Square和Circle, 定义一个p ...