• CobarClient源码分析


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

    屏幕快照 2014-02-16 下午7.07.02

    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这个接口,继承关系如下所示:

    屏幕快照 2014-02-16 下午7.30.22

    在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命令模式结合回调函数的方式来实现,标准的命令模式结构图如下:
    2010070907240523

    在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是一个不错的选择。

  • 相关阅读:
    怎么在excel单元格里原有的筛选里面添加新选项
    redis通信协议
    nginx路由文件配置
    R语言绘制相对性关系图
    Generator函数的语法
    360前端星计划作业
    工厂模式
    ReferenceError与undefined的区别
    for...in与点语法
    博客申请成功
  • 原文地址:https://www.cnblogs.com/javanerd/p/3551831.html
Copyright © 2020-2023  润新知