CobarClient是阿里巴巴公司开发一个的开源的、基于iBatis和Spring的分布式数据库访问层。为了支持iBatis,Spring框架提供了一个SqlMapClientTemplate,通过模板模式简化了在Spring框架中对于iBatis中的使用。而CobarClient则继承了SqlMapClientTemplate,提供了CobarSqlMapClientTemplate给应用使用。CobarSqlMapClientTemplate和SqlMapClientTemplate继承关系如下:
在 CobarSqlMapClientTemplate持有的众多属性中,比较重要的有cobarDataSourceService(提供多数据源的管理服务)、router(Sql路由接口的实现)、concurrentRequestProcessor(Sql并发执行的命令类),下面就从路由规则到最终的Sql执行来分析一下CobarClient的原理。
根据CobarClient的文档,CobarClient一共提供了四种路由规则:
在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是一个不错的选择。
原文:http://www.cnblogs.com/javanerd/p/3551831.html