CobarClient源码分析
CobarClient是阿里巴巴公司开发一个的开源的、基于iBatis和Spring的分布式数据库访问层。为了支持iBatis,Spring框架提供了一个SqlMapClientTemplate,通过模板模式简化了在Spring框架中对于iBatis中的使用。而CobarClient则继承了SqlMapClientTemplate,提供了CobarSqlMapClientTemplate给应用使用。CobarSqlMapClientTemplate和SqlMapClientTemplate继承关系如下:
在 CobarSqlMapClientTemplate持有的众多属性中,比较重要的有cobarDataSourceService(提供多数据源的管理服务)、router(Sql路由接口的实现)、concurrentRequestProcessor(Sql并发执行的命令类),下面就从路由规则到最终的Sql执行来分析一下CobarClient的原理。
- 路由规则的生成与执行。
根据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>10000 and id< 20000</shardingExpression>
<shards>p1, p2</shards>
</rule>
<rule>
<namespace>com.alibaba.cobar.client.entity.Follower</namespace>
<shardingExpression>id>10000 and id< 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不是没有缺点
- 数据合并功能的羸弱:CobarClient并没有引入Sql语句的解析,对于数据的合并是在应用层做的,而且现在只能支持简单的合并,当应用程序有比较复杂的结果集处理要求的时候,例如order by,group by等,需要自己去实现merge类,而且由于没有Sql语句的解析,即使实现了,也很难做到通用。
- 路由算法的效率:根据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),这样算法的时间消耗不会随着路由规则的增多而增长。 - 路由规则的设计和解析:路由规则的解析,CobarClient使用的XStream这个开源的包,这样的话,当路由规则文件要新增结点或者某个结点要新增属性的时候,XStream并不能做到很灵活。此外,CobarInteralRouterXmlFactoryBean中根据路由规则解析文件的时候,基本上都是用if else的语句来做判断的,这是一种代码的坏味道。此外,根据之前路由规则的继承图可以看到,在具体的路由规则实现类之上,还有三个抽象的路由规则类,我推倒是CobarClient的设计者刚开始的时候,是为了以后的路由规则能够扩展,但是他最后发现四种规则就能涵盖一切情况,但是没有对代码进行必要的重构。
- 代码的风格:这是一个小问题,但是确实很影响代码的理解。例如在iBatis语境中默认的Sql Id到了IBatisRoutingFact就变成
private String action了。Rule文件中的namespace到了具体的规则类之中,就变成了private String typePatten。这种情况充斥于整个代码之中,于是最后你能发现,ICobarRouter的默认实现类是CobarClientInternalRouter而不是DefaultCobarClientInternalRouter。
瑕不掩瑜,如果需要一个轻量级的,支持分布式数据库的数据访问层框架,并且应用不需要对于查询的结果集做过于复杂的排序聚会等操作,CobarClient是一个不错的选择。
CobarClient源码分析的更多相关文章
- CobarClient源码分析(1)
CobarClient是阿里巴巴公司开发一个的开源的.基于iBatis和Spring的分布式数据库访问层.为了支持iBatis,Spring框架提供了一个SqlMapClientTemplate,通过 ...
- ABP源码分析一:整体项目结构及目录
ABP是一套非常优秀的web应用程序架构,适合用来搭建集中式架构的web应用程序. 整个Abp的Infrastructure是以Abp这个package为核心模块(core)+15个模块(module ...
- HashMap与TreeMap源码分析
1. 引言 在红黑树--算法导论(15)中学习了红黑树的原理.本来打算自己来试着实现一下,然而在看了JDK(1.8.0)TreeMap的源码后恍然发现原来它就是利用红黑树实现的(很惭愧学了Ja ...
- nginx源码分析之网络初始化
nginx作为一个高性能的HTTP服务器,网络的处理是其核心,了解网络的初始化有助于加深对nginx网络处理的了解,本文主要通过nginx的源代码来分析其网络初始化. 从配置文件中读取初始化信息 与网 ...
- zookeeper源码分析之五服务端(集群leader)处理请求流程
leader的实现类为LeaderZooKeeperServer,它间接继承自标准ZookeeperServer.它规定了请求到达leader时需要经历的路径: PrepRequestProcesso ...
- zookeeper源码分析之四服务端(单机)处理请求流程
上文: zookeeper源码分析之一服务端启动过程 中,我们介绍了zookeeper服务器的启动过程,其中单机是ZookeeperServer启动,集群使用QuorumPeer启动,那么这次我们分析 ...
- zookeeper源码分析之三客户端发送请求流程
znode 可以被监控,包括这个目录节点中存储的数据的修改,子节点目录的变化等,一旦变化可以通知设置监控的客户端,这个功能是zookeeper对于应用最重要的特性,通过这个特性可以实现的功能包括配置的 ...
- java使用websocket,并且获取HttpSession,源码分析
转载请在页首注明作者与出处 http://www.cnblogs.com/zhuxiaojie/p/6238826.html 一:本文使用范围 此文不仅仅局限于spring boot,普通的sprin ...
- ABP源码分析二:ABP中配置的注册和初始化
一般来说,ASP.NET Web应用程序的第一个执行的方法是Global.asax下定义的Start方法.执行这个方法前HttpApplication 实例必须存在,也就是说其构造函数的执行必然是完成 ...
随机推荐
- thinkphp导出csv文件,用表格输出excel
1.thinkphp导出csv文件 导出csv文件可能就那几行代码,今天有个问题困扰我好久,就是导出之后出现一些html代码,这个不应该,view里面是空的,controller中最后也没有$this ...
- 关于移动端APP开发-字体样式变大问题
前两天在写App项目的时候发现一个问题,就是明明css写的样式是14px,刚开始在页面显示时并未出现问题,可是内容一多,字体突然变大了. what?,不明所以,在各大网站上找了好久才知道是浏览器的字体 ...
- Hibernate事务、缓存和连接池
一.事务 1.数据库事务的概念 数据库事务是指由一个或多个SQL语句组成的工作单元,这个工作单元中的SQL语句相互依赖,如果有一个SQL语句执行失败,就必须撤销整个工作单元.在并发环境中,多个事务同时 ...
- memset struct含有string的崩溃
2019/4/2 补充一下 这里如果填充为0,则不会崩溃,填充为非0时,再次调用赋值就会崩溃 推测非0拷贝破坏了string内部的数据结构,不要对任何类使用memset https://blog.cs ...
- hdu2824 The Euler function(欧拉函数个数)
版权声明:本文为博主原创文章,未经博主同意不得转载. vasttian https://blog.csdn.net/u012860063/article/details/36426357 题目链接:h ...
- mybatis #{}和${}的区别是什么
#{}和${}的区别是什么?正确的答案是:#{}是预编译处理,${}是字符串替换.(1)mybatis在处理#{}时,会将sql中的#{}替换为?号,调用PreparedStatement的set方法 ...
- 20165318 《Java程序设计》实验一(Java开发环境的熟悉)实验报告
20165318 <Java程序设计>实验一(Java开发环境的熟悉)实验报告 一.实验报告封面 课程:Java程序设计 班级:1653班 姓名:孙晓暄 ...
- 1034. [ZJOI2008]泡泡堂【贪心】
Description 第XXXX届NOI期间,为了加强各省选手之间的交流,组委会决定组织一场省际电子竞技大赛,每一个省的代表 队由n名选手组成,比赛的项目是老少咸宜的网络游戏泡泡堂.每一场比赛前,对 ...
- 关于HTML Button点击自动刷新页面的问题解决
原因 button,input type=button按钮在IE和w3c,firefox浏览器区别: 1.当在IE浏览器下面时,button标签按钮,input标签type属性为button的按钮是一 ...
- 真机测试出现INSTALL_FAILED_USER_RESTRICTED安装错误
之前用小米测试的时候遇到一个问题,两个一样型号的手机一个能直接用Android Studio安装公司的项目一个却不可以,总是报INSTALL_FAILED_USER_RESTRICTED错误,具体见下 ...