锁定老帖子 主题:分布式访问框架halo-dal设计思想
精华帖 (2) :: 良好帖 (1) :: 新手帖 (0) :: 隐藏帖 (0)
|
|
---|---|
作者 | 正文 |
发表时间:2012-05-04
最后修改:2012-05-04
halo-dal原帖 地址 http://www.iteye.com/topic/1123284 近期刚刚完成halo-dal的代码,一些iteye的朋友通过qq问我设计思路。由于在qq上写的语言组织不是很完整,特此开一帖,详细写一下halo-dal的一些心得与技巧,希望大家能从中获得灵感,或者能指出我设计的不足之处。 halo-dal的雏形在2009年中旬的时候就已经存在了,当时看了手机之家 许超前 设计的 dal 相关的ppt,那时对此很好奇,工作之余设计了 hkframe。这个是建立在jdbc基础之上的一个分布式访问框架以及一个快速开发的web框架,当时没有开源的原因在于自认为没有做到很好,不是我理想的代码。后来一直在我自己的项目上使用。没有再进行更新。 到2011年时,又由于工作需要,再次捡起以前的代码翻看了2天,决定重新设计hkframe,用了2个星期完成了halo 框架,使用全新的思想进行设计。为什么叫halo,由于当时在玩一个游戏 halo,非常喜欢,在玩完游戏之余,非常兴奋的状态下完成的代码,所以起名的时候就写了halo。halo中还是包括一个jdbc访问框架和web框架。当时的框架是一个分布式访问和轻量级orm的结合体,进行开发,效率非常高。由于使用了asm来解决使用反射的问题,所以性能也很不错。唯一缺憾的地方就是只能用jdbc来写,而且必须是我提供的api。 设计halo-dal的原因为公司开发需要,发现没有合适的解决方案,公司内部jdbc hibernate ibatis 都在使用,而且很多设计方案代码入侵性较强,耦合度高。搜索发现hibernate share 只解决了分表问题,无法解决分库问题。ibatis没有多加搜索。后来就有一个想法,能不能设计一种简单的框架能支持各种现有的数据库访问框架呢。由此出现了halo-dal的设计。主要利用五一假期时间完成了代码。 进入正题::: halo-dal 分布式数据访问设计 功能主要解决的问题是 : 如何能方便的选择 datasource 和真正的 tablename 顺便要做到: 尽量使用jdbc原有接口,做到最小化的代码入侵。(旨在能使用大家各自喜欢的hibernate ibatis mybatis 也能解决分布式访问问题) 那么所有设计的入口就只能在Connection以下。 为了简化第一个版本的开发,我只想支持PreparedStatement方式,不支持普通的Statement 以及批处理使用的BatchPreparedStatement 什么是我需要的信息? 我的关注点放到了sql上,因为根据jdbc使用经验来说,操作数据库离不开2个最主要的参数sql ,以及sql中赋值的参数 Object[] values。也就是说我只需要拦截到sql以及alues就可以进行分析。sql中包含最显而易见的一个信息,那就是tablename,那么datasource从哪里获得?没有别的东西,只能是靠sql 与values。 每个模块完全独立设计,只靠接口传递信息: 第一个要解决的问题就是解析sql与values获得我需要的信息,对于sql解析,我使用的就是最普通的字符串indexof substring这些api。解析后,我需要的信息就清楚了tablename、条件表达式、赋值表达式。 以下解释用到的例子 sql: select * from user where level=? Object[] values =new Object[]{1}; tablename是什么? 在此的table name 不是真正的表名称。而是一个逻辑表明称:我称他为logicTableName,这个逻辑表名。最终会把sql中的tablename 替换成真正的表名。 条件表达式、赋值表达式是什么: level=? 真正的值是1 那么表达式 就是 level、=、1,组合就成为了SQLExpression。 分表分库的路由解析器: 分析完sql,那就该进行分表分库信息的获取了,这个信息的解析,就在PartitionParser中,在此类的方法中传入tablename 以及表达式等有效信息。 例如level,这个是对user表进行分区的条件,那么我们只需要对level以及他的值进行相关运算即可。这个时候就用到了SQLExpression的数据. 通过解析,最终会获得 dsKey(datasource对应的key) 和 真正的表名称 realTableName,返回的对象PartitionTableInfo包含了这些信息。 如何使用解析的结果? 上面的步骤完成了sql解析以及获得真正datasource key和真正的表名,那么接下来就是如何组织上面2段程序。 既然要解析sql,那么就要找到能获得sql的位置。在Connection中与sql有关的几个主要接口就是 public PreparedStatement prepareStatement(String sql) throws SQLException;
public PreparedStatement prepareStatement(String sql, int autoGeneratedKeys)
throws SQLException;
......
因此,我只能通过自定义DALConnection implements Connection 来实现一个虚拟的Connection,这样就可以截取sql信息。 DALDataSource DALConnection DALPreparedStatement DataSource.getConnection方法是获得链接的入口,为了获得DALConnection,我就需要自定义DALDataSource implements DataSource.所有真实的DataSource全部由DALDataSource来掌管,这些真实的DataSource全部都通过一个map来保存key为 dsKey(程序中会用到),value就是DataSource。 使用了DALDataSource DALConnection的好处就是能延迟开启真正的DataSource,这样才能保证先解析,再定位。例如,在使用spring 进行事务管理时, 假设 service.methodTX这个方法是需要开启事务,通常只有获得了Connection,才能setAutoCommit(false);,但是我们在这个步骤还没有获得sql,就无法进行解析,只有通过DALDataSource来获得一个DALConnection,这个DALConnection在此时是与任何真实的Connection无关的。因此并不会开启真正的Connection,以及真正的执行setAutoCommit(false);,所有对Connection进行的设置,都会通过DALConnection进行记录,直到获取真正的Connection时,才全部设置到真身。 通过DALConnection.prepareStatement,我们也无法获取真正的PreparedStatement,只有继续造假,创建了DALPreparedStatement.代码的重点就是DALPreparedStatement,因为通过PreparedSatement才可以设置sql需要的参数。DALPreparedStatement,会记录所有对本身的操作,直到获得了真正的PreparedStatement,才把记录的操作设置到真身。 在执行PreparedStatement.execute*()系列方法之前,我们都已经传递了sql,并进行参数赋值,那么我们就可以进行真正的分析了。通过调用prepare()方法就可以进行进行数据访问路由分析。 public boolean execute() throws SQLException {
this.prepare();
return ps.execute();
}
prepare这个方法会调用上面讲述的sql分析器 访问路由器,获得dsKey 、realTableName接下来 再次通过sql解析器替换sql中对应的表名。dsKey会存储到一个ThreadLocal类型的对象中,通过DALConnection.getCurrentConnection就可以获得真正的Connection,并开启PreparedStatement,最终执行的execute就落到了指定的数据库上。 整个halo-dal的设计思想就是以上这些了,有不妥之处请大家指出。 声明:ITeye文章版权属于作者,受法律保护。没有作者书面许可不得转载。
推荐链接
|
|
返回顶楼 | |
发表时间:2012-05-05
呵呵,楼主讲的很清楚,比阿里的CobarClient从实现上来说应该简单一些,而且没有ORM持久层的限制,我已经迫不及待的要研究halo-dal的源码了。
|
|
返回顶楼 | |
发表时间:2012-05-05
rain2005 写道 呵呵,楼主讲的很清楚,比阿里的CobarClient从实现上来说应该简单一些,而且没有ORM持久层的限制,我已经迫不及待的要研究halo-dal的源码了。
欢迎研究 改造 指正批评。 |
|
返回顶楼 | |
发表时间:2012-05-07
LZ 你考虑过你的那个例子中 level字段的值能否update的问题了么?
|
|
返回顶楼 | |
发表时间:2012-05-07
最后修改:2012-05-07
char1st 写道 LZ 你考虑过你的那个例子中 level字段的值能否update的问题了么?
如果是作为分区key的话,你会去修改他的值么,如果修改了,就会涉及到数据变动。这就是个大问题了。 这个问题我觉得应该留给调用者去处理,是否进行数据移动。 谢谢你提的意见,这个问题我以前也遇到过,处理方式也就是根据业务规则进行数据移动。 |
|
返回顶楼 | |
发表时间:2012-05-07
发了1个半小时把楼主的代码看了一遍,DALCurrentStatus中的STATUS_DSKEY对应的值也就算当前数据源的key应该可以直接作为DALConnection中的实例变量吧?毕竟这里没有并发访问的情况。
|
|
返回顶楼 | |
发表时间:2012-05-07
还有这里不支持sql关键字大写,我好像记得hibernate生成的sql都是大写的?
|
|
返回顶楼 | |
发表时间:2012-05-07
rain2005 写道 发了1个半小时把楼主的代码看了一遍,DALCurrentStatus中的STATUS_DSKEY对应的值也就算当前数据源的key应该可以直接作为DALConnection中的实例变量吧?毕竟这里没有并发访问的情况。
STATUS_DSKEY代表的就是当前需要使用的真实的DataSource。你可以看 DALPreparedStatement 中 prepare()方法这几句 DALCustomInfo dalCustomInfo = DALCurrentStatus.getCustomInfo(); DALFactory dalFactory = DALFactory.getInstance(); List<Object> values = dalParameters.getValues(); SQLInfo sqlInfo = dalFactory.getSqlAnalyzer().analyse(sql, values.toArray(new Object[values.size()])); // 如果用户没有自定义设置,那么以解析结果为dsKey if (dalCustomInfo == null) { DALCurrentStatus.setDsKey(this.parsePartitionDsKey(sqlInfo)); } this.sql = dalFactory.getSqlAnalyzer() .outPutSQL(sqlInfo, dalCustomInfo); |
|
返回顶楼 | |
发表时间:2012-05-07
楼主的代码很清晰,很不错,主要的复杂点在sql解析这块
|
|
返回顶楼 | |
发表时间:2012-05-07
rain2005 写道 还有这里不支持sql关键字大写,我好像记得hibernate生成的sql都是大写的?
看来根据你说的情况,我需要对大写进行一下处理了。 感谢你的测试啊。太感谢了 |
|
返回顶楼 | |