论坛首页 Java企业应用论坛

分布式访问框架halo-dal设计思想

浏览 12285 次
精华帖 (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的设计思想就是以上这些了,有不妥之处请大家指出。




   发表时间:2012-05-05  
呵呵,楼主讲的很清楚,比阿里的CobarClient从实现上来说应该简单一些,而且没有ORM持久层的限制,我已经迫不及待的要研究halo-dal的源码了。
0 请登录后投票
   发表时间:2012-05-05  
rain2005 写道
呵呵,楼主讲的很清楚,比阿里的CobarClient从实现上来说应该简单一些,而且没有ORM持久层的限制,我已经迫不及待的要研究halo-dal的源码了。

欢迎研究 改造 指正批评。
0 请登录后投票
   发表时间:2012-05-07  
LZ 你考虑过你的那个例子中 level字段的值能否update的问题了么?
0 请登录后投票
   发表时间:2012-05-07   最后修改:2012-05-07
char1st 写道
LZ 你考虑过你的那个例子中 level字段的值能否update的问题了么?

如果是作为分区key的话,你会去修改他的值么,如果修改了,就会涉及到数据变动。这就是个大问题了。
这个问题我觉得应该留给调用者去处理,是否进行数据移动。
谢谢你提的意见,这个问题我以前也遇到过,处理方式也就是根据业务规则进行数据移动。
0 请登录后投票
   发表时间:2012-05-07  
发了1个半小时把楼主的代码看了一遍,DALCurrentStatus中的STATUS_DSKEY对应的值也就算当前数据源的key应该可以直接作为DALConnection中的实例变量吧?毕竟这里没有并发访问的情况。
0 请登录后投票
   发表时间:2012-05-07  
还有这里不支持sql关键字大写,我好像记得hibernate生成的sql都是大写的?
0 请登录后投票
   发表时间: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);
0 请登录后投票
   发表时间:2012-05-07  
楼主的代码很清晰,很不错,主要的复杂点在sql解析这块
0 请登录后投票
   发表时间:2012-05-07  
rain2005 写道
还有这里不支持sql关键字大写,我好像记得hibernate生成的sql都是大写的?


看来根据你说的情况,我需要对大写进行一下处理了。

感谢你的测试啊。太感谢了
0 请登录后投票
论坛首页 Java企业应用版

跳转论坛:
Global site tag (gtag.js) - Google Analytics