TinyDbRouter开源喽~~~

前面有过一篇文章介绍TinyDbRouter,但是当时没有开出来,主要原因是:1偶的粉丝数太少,期望到100的时候,纪念性的发布这个重量级框架,另外一个原因是当时有个编译问题没有完美的解决,偶担心同学们使用的时候不方便--其实偶也不方便,尤其是发布和测试的时候。

现在粉够100了,那个编译问题也顺利的解决了,OK,没有什么理由不快些把它开放给大家。

前面偶起的名字是TinyDBCluster,后来由于有同学们反应说这个与数据库集群歧义,因此还是改成TinyDBRouter了,如果看到两个名字,请把它们当成一样的,后面就专门用TinyDBRouter

其实在开发TinyDbRouter之前,偶主要是想找一个比较合适的数据库分区、分表方案,为此也学习了各种实现方案,比如就了解过routing4db,偶也专门做了对比,当然由于对routing4db的了解毕竟有不足,因此可能有许多不准确的地方;另外也对淘宝系的tddl做了相关研究,但是最后偶还是决定自己尝试写一下,当然写完之后感觉还是不错的,因此才有现在开源的TinyDbRouter。

好的,上面是一些背景情况,现在言归正传,我们正式说框架。

关于Tiny DBRouter的原理性文章,请移步查阅,这里主要讲使用。

要想使用Tiny DBRouter,很简单,首先搞清楚是jdbc3(JDK1.5)还是jdbc4(JDK1.6及以上)的规范。

然后选择对应的Maven坐标:

<dependency>
	<groupId>org.tinygroup</groupId>
	<artifactId>org.tinygroup.dbrouterjdbc3</artifactId>
	<version>0.1.0-SNAPSHOT</version>
</dependency>

或者

<dependency>
	<groupId>org.tinygroup</groupId>
	<artifactId>org.tinygroup.dbrouterjdbc4</artifactId>
	<version>0.1.0-SNAPSHOT</version>
</dependency>

之所以是SNAPSHOT版本,是因为Tiny框架的升级是阶段性升级的,过一段时间就会变成0.0.13正式版本。

当把相关jar包下载到本地之后,接下来就是配置分区分表数据源了。

我们拿一个例子来说明:

differentSchemaAggregate.xml

<routers>
    <router id="aggregate" user-name="luog" password="123456"
             key-generator-class="org.tinygroup.dbrouter.impl.keygenerator.RouterKeyGeneratorLong">
        <key-generator-config increment="1" step="100" data-source-id="ds0"/>
        <data-source-configs>
            <data-source-config id="ds0" driver="com.mysql.jdbc.Driver"
                                user-name="root" password="123456" url="jdbc:mysql://192.168.51.29:3306/test0"
                                test-sql=""/>
            <data-source-config id="ds1" driver="com.mysql.jdbc.Driver"
                                user-name="root" password="123456" url="jdbc:mysql://192.168.51.29:3306/test1"
                                test-sql=""/>
            <data-source-config id="ds2" driver="com.mysql.jdbc.Driver"
                                user-name="root" password="123456" url="jdbc:mysql://192.168.51.29:3306/test2"
                                test-sql=""/>
        </data-source-configs>
        <partitions>
            <partition id="abc" mode="2">
                <partition-rules>
                    <partition-rule
                            class="org.tinygroup.dbrouter.impl.partionrule.PartionRuleByTableName"
                            table-name="score"/>
                </partition-rules>
                <shards>
                    <shard id="shard0" data-source-id="ds0">
                        <shard-rules>
                            <shard-rule
                                    class="org.tinygroup.dbrouter.impl.shardrule.ShardRuleByIdDifferentSchema"
                                    table-name="score" primary-key-field-name="id" remainder="0"/>
                        </shard-rules>
                    </shard>
                    <shard id="shard1" data-source-id="ds1">
                        <shard-rules>
                            <shard-rule
                                    class="org.tinygroup.dbrouter.impl.shardrule.ShardRuleByIdDifferentSchema"
                                    table-name="score" primary-key-field-name="id" remainder="1"/>
                        </shard-rules>
                    </shard>
                    <shard id="shard2" data-source-id="ds2">
                        <shard-rules>
                            <shard-rule
                                    class="org.tinygroup.dbrouer.impl.shardrule.ShardRuleByIdDifferentSchema"
                                    table-name="score" primary-key-field-name="id" remainder="2"/>
                        </shard-rules>
                    </shard>
                </shards>
            </partition>
        </partitions>
    </router>
</routers>

内容虽然比较长,但是其实很简单的,听偶娓娓道来:

一个配置文件可以配置多个数据库集群,因此根节点叫routers,接下来一段router就是一个集群喽。


<router id="aggregate" user-name="luog" password="123456"
             key-generator-bean="routerKeyGeneratorLong">

id非常重要,在通过jdbc访问数据库集群的时候,在url中要用到id,用户名和密码就是在通过jdbc连接时的用户名密码,呵呵,现在密码是明码,后续版本密码部分,会改为加密存储。

采用逻辑主键时,经常需要生成一个主键,由于集群环境中,主键的生成是一个细致活,原来采用数据库的自动生成序列、自增长啥的都不好用了,因此,一定需要一个集群模式的主键生成器。不过不用担心,框架已经提供了整型、长整型、UUID三种分布式主键生成器,大多数的情况下都够用了,如果再不够,请给我们提需求或者自已动手丰衣足食,自行进行扩展。

<key-generator-config increment="1" step="100" data-source-id="ds0"/>

这里定义了数据主键生成的一些参数配置,首先,需要一个数据源的名称,因为有一些数据需要在数据库中存储。increment表示每次主键增长幅度,step表示每申请一次缓冲多个主键。当然,这两个参数都可以忽略,这时就采用系统默认值了--多数情况下都够了。

<data-source-configs>
   <data-source-config id="ds0" driver="com.mysql.jdbc.Driver" user-name="root" password="123456" url="jdbc:mysql://192.168.51.29:3306/test0" test-sql=""/>
   <data-source-config id="ds1" driver="com.mysql.jdbc.Driver" user-name="root" password="123456" url="jdbc:mysql://192.168.51.29:3306/test1" test-sql=""/>
   <data-source-config id="ds2" driver="com.mysql.jdbc.Driver" user-name="root" password="123456" url="jdbc:mysql://192.168.51.29:3306/test2" test-sql=""/>
        </data-source-configs>

这里定义的就是集群中要用到的数据源的列表,熟悉jdbc的同学一看就知道什么意思就不讲了,为什么这里统一一个区域定义数据源呢??因为如果是同库分表的话,数据源实际上就是一个,这个时候只用定义一个就够了。

接下来就是定义分区了:

一个集群可以包含多个分区,一个分区可以包含多个分片。

<partition id="abc" mode="2">
mode这里用于声明分区的模式,分区有两种方式,为1的时候表示读写分离模式,为2的时候表示分表模式。
<partition-rule class="org.tinygroup.dbrouter.impl.partionrule.PartionRuleByTableName" table-name="score"/>
</partition-rules>

一个分区可以包含多个分区规则,分区规则主要用于确定哪些表跑到一个分区。这里很简单,配置的是只要表名是score,就跑到本分区来执行。

一个区分又可以有多个分片,每个分片可以有一到多个分片规则,以决定是否到当前分片执行。

<shard id="shard0" data-source-id="ds0">
    <shard-rules>
        <shard-rule
                class="org.tinygroup.dbrouter.impl.shardrule.ShardRuleByIdDifferentSchema"
                table-name="score" primary-key-field-name="id" remainder="0"/>
    </shard-rules>
</shard>

上面的规则是指根据score表的id值对shard数进行取余,余数为0的命中。 另外的两个就是说余数为1和为2的时候执行。

很明显分片规则和分区规则都是可以自行扩展的---凡是可以指定bean或类名的,都是可以进行扩展滴。

用白话总结一下,上面的配置:

定义了一个标识为“aggregate”的集群,其用户名密码为“luog”和"123456",定义的主键产生器是每次增加1,每次取100个,用完之后,再去取100个,以此类推。

定义了三个数据源,备用。

定义了一个分区abc,把所有score表的处理都交给此分区进行处理,它的分区模式是分表模式。也就是说score表中的数据会被分解到多个表当中去。

接下来给分区abc定义了三个分片,这三个分片分别指向上面的三个数据源中的一个,第一个负责处理socre表中的id对3取余余数为0的数据;第二个负责处理score表中的id对3取余余数为1的数据;第三个负责处理score表中的id对3取余余数为2的数据;

OK,上面的定义就算完成了,下面上大菜,看测试用例:

public class AggregateTest extends TestCase {


    Statement stmt;


    private static boolean hasInit;


    @Override
    protected void setUp() throws Exception {
        super.setUp();
        RouterManager routerManager = RouterManagerBeanFactory.getManager();
        routerManager.addRouters("/differentSchemaAggregate.xml");
        Class.forName("org.tinygroup.dbrouterjdbc3.jdbc.TinyDriver");
        Connection conn = DriverManager.getConnection("jdbc:dbrouter://aggregate", "luog", "123456");
        stmt = conn.createStatement();
        prepareRecord();
    }


    private void prepareRecord() throws SQLException {
        //删除数据
        if (!hasInit) {
            stmt.execute("delete from score");
            stmt.executeUpdate("insert into score(id,name,score,course) values(1,'xiaohuihui',99,'shuxue')");
            stmt.executeUpdate("insert into score(id,name,score,course) values(2,'xiaohuihui',97,'yuwen')");
            stmt.executeUpdate("insert into score(id,name,score,course) values(3,'xiaom',95,'shuxue')");
            stmt.executeUpdate("insert into score(id,name,score,course) values(4,'xiaof',97,'yingyu')");
            stmt.executeUpdate("insert into score(id,name,score,course) values(5,'xiaom',100,'yuwen')");
            stmt.executeUpdate("insert into score(id,name,score,course) values(6,'xiaof',95,'yuwen')");
            stmt.executeUpdate("insert into score(id,name,score,course) values(7,'xiaohuihui',95,'yingyu')");
            stmt.executeUpdate("insert into score(id,name,score,course) values(8,'xiaom',96,'yingyu')");
            stmt.executeUpdate("insert into score(id,name,score,course) values(9,'xiaof',96,'shuxue')");
            hasInit = true;
        }
    }




    @Override
    protected void tearDown() throws Exception {
        super.tearDown();
    }


    public void testCount() throws SQLException {
        String sql = "select count(*),name from score group by name";
        ResultSet resultSet = stmt.executeQuery(sql);
        while (resultSet.next()) {
            String name = resultSet.getString(2);
            if (name.equals("xiaohuihui")) {
                assertEquals(3, resultSet.getInt(1));
            } else if (name.equals("xiaom")) {
                assertEquals(3, resultSet.getInt(1));
            } else if (name.equals("xiaof")) {
                assertEquals(3, resultSet.getInt(1));
            }
        }
    }


    public void testMax() throws SQLException {
        String sql = "select max(score) score,course from score group by course";
        ResultSet resultSet = stmt.executeQuery(sql);
        while (resultSet.next()) {
            String course = resultSet.getString(2);
            if (course.equals("shuxue")) {
                assertEquals(99, resultSet.getInt(1));
            } else if (course.equals("yingyu")) {
                assertEquals(97, resultSet.getInt(1));
            } else if (course.equals("yuwen")) {
                assertEquals(100, resultSet.getInt(1));
            }
        }
    }




    public void testMaxSingle() throws SQLException {
        String sql = "select max(score) score from score";
        ResultSet resultSet = stmt.executeQuery(sql);
        resultSet.next();
        assertEquals(100, resultSet.getInt(1));
    }


    public void testSum() throws SQLException {
        String sql = "select sum(score) score,name from score group by name";
        ResultSet resultSet = stmt.executeQuery(sql);
        while (resultSet.next()) {
            String name = resultSet.getString(2);
            if (name.equals("xiaohuihui")) {
                assertEquals(291, resultSet.getInt(1));
            } else if (name.equals("xiaom")) {
                assertEquals(291, resultSet.getInt(1));
            } else if (name.equals("xiaof")) {
                assertEquals(288, resultSet.getInt(1));
            }
        }
    }


    public void testMin() throws SQLException {
        String sql = "select min(score) score,name from score group by name";
        ResultSet resultSet = stmt.executeQuery(sql);
        while (resultSet.next()) {
            String name = resultSet.getString(2);
            if (name.equals("xiaohuihui")) {
                assertEquals(95, resultSet.getInt(1));
            } else if (name.equals("xiaom")) {
                assertEquals(95, resultSet.getInt(1));
            } else if (name.equals("xiaof")) {
                assertEquals(95, resultSet.getInt(1));
            }
        }
    }


    public void testMinSingle() throws SQLException {
        String sql = "select min(score) score from score";
        ResultSet resultSet = stmt.executeQuery(sql);
        resultSet.next();
        assertEquals(95, resultSet.getInt(1));
    }


    public void testAvg() throws SQLException {
        String sql = "select avg(score) score,name from score group by name";
        ResultSet resultSet = stmt.executeQuery(sql);
        while (resultSet.next()) {
            String name = resultSet.getString(2);
            if (name.equals("xiaohuihui")) {
                assertEquals(97.0, resultSet.getDouble(1));
            } else if (name.equals("xiaom")) {
                assertEquals(97.0, resultSet.getDouble(1));
            } else if (name.equals("xiaof")) {
                assertEquals(96.0, resultSet.getDouble(1));
            }
        }
    }


    public void testMultiWithOrderby() throws SQLException {
        String sql = "select min(score) minscore,max(score) maxscore,sum(score) sumscore,avg(score) avgscore, name from score group by name order by name";
        ResultSet resultSet = stmt.executeQuery(sql);
        while (resultSet.next()) {
            String name = resultSet.getString("name");
            if (name.equals("xiaohuihui")) {
                assertEquals(95.0, resultSet.getDouble(1));
                assertEquals(99.0, resultSet.getDouble(2));
                assertEquals(291.0, resultSet.getDouble(3));
                assertEquals(97.0, resultSet.getDouble(4));
            } else if (name.equals("xiaom")) {
                assertEquals(95.0, resultSet.getDouble(1));
                assertEquals(100.0, resultSet.getDouble(2));
                assertEquals(291.0, resultSet.getDouble(3));
                assertEquals(97.0, resultSet.getDouble(4));
            } else if (name.equals("xiaof")) {
                assertEquals(95.0, resultSet.getDouble(1));
                assertEquals(97.0, resultSet.getDouble(2));
                assertEquals(288.0, resultSet.getDouble(3));
                assertEquals(96.0, resultSet.getDouble(4));
            }
        }
    }


    public void testMultiSingle() throws SQLException {
        String sql = "select min(score) minscore,max(score) maxscore,sum(score) sumscore,avg(score) avgscore from score";
        ResultSet resultSet = stmt.executeQuery(sql);
        resultSet.next();
        assertEquals(95.0, resultSet.getDouble(1));
        assertEquals(100.0, resultSet.getDouble(2));
        assertEquals(870.0, resultSet.getDouble(3));
        assertEquals(97.0, Math.ceil(resultSet.getDouble(4)));
    }


    public void testMaxWithFirstAndLast() throws SQLException {
        String sql = "select max(score) score,name,course from score group by name order by score";
        ResultSet resultSet = stmt.executeQuery(sql);
        resultSet.absolute(1);
        assertEquals(97, resultSet.getInt(1));
        assertEquals("xiaof", resultSet.getString(2));
        resultSet.first();
        assertTrue(resultSet.isFirst());
        assertEquals(97, resultSet.getInt(1));
        assertEquals("xiaof", resultSet.getString(2));
        resultSet.last();
        assertTrue(resultSet.isLast());
        assertEquals(100, resultSet.getInt(1));
        assertEquals("xiaom", resultSet.getString(2));


    }


    public void testMaxWithOrderBy() throws SQLException {
        String sql = "select max(score) score,course from score group by course order by score";
        ResultSet resultSet = stmt.executeQuery(sql);
        resultSet.next();
        assertEquals("yingyu", resultSet.getString(2));
        assertEquals(97, resultSet.getInt(1));
        resultSet.next();
        assertEquals("shuxue", resultSet.getString(2));
        assertEquals(99, resultSet.getInt(1));
        resultSet.next();
        assertEquals("yuwen", resultSet.getString(2));
        assertEquals(100, resultSet.getInt(1));
    }




}

 
 
 
 

  
  

上面首先在setUp中做了一点初始化工作,主要就是下面两句:用于加载一个集群配置,实际使用有两种方法:

编程方式,约定方式。下面用的就是编译方式,如果用编写方式就简单了,只要按约定放在合适的位置,框架会自动加载配置文件,就可以不写下面的两行了。

RouterManager routerManager = RouterManagerBeanFactory.getManager();
		routerManager.addRouters("/differentSchemaAggregate.xml");

其它的工作就与普通的JDBC没有任何不同了。

我们看看初始化之后, 数据的情况:


从上面可以看到,数据确实已经插入到三个数据表中。

后面的几个测试用例主要测试的是聚合统计方面的处理,实际上,所有的SQL语句都可以正常的执行,对于上层应用来说,它根本就不知道分表了。

急性子的同学们可能要问:

那如果我输入select * from score where id=3,结果会正确出来么?当然

那如果我输入select * from score order by id,结果会正确出来么?当然

我要说的,还远不止如此:

实际上TinyDBRouter已经竭尽全力,来支持数据库的特性:

比如:自增长

还是上面的score类子,如果在插入的时候不指定id值,如下:

insert into score(name,score,course) values('xiaohuihui',97,'yuwen')

TinyDBRouter会同样进行正常的插入,完全透明的处理好分布式主键的问题。这个与类似的框架比就先进许多了。类似的框架都是需要必须输入id,并且自己保证或必须调用其分库分表方案中提供的API来获取主键。这实际上就是有侵入性,也就是人编程人员可以感知到分库分表的存在,且必须按照相应规范进行使用。而使用TinyDBRouter,开发人员可以完全不知道有这么一层存在。

比如统计处理:

假设在一个表中有9条数据,我们执行下面的语句:

select avg(score) score,name from score group by name

我们都知道实际处理是名字相同的score值加起来,然后除以记录数,得到平均值。

但是现在数据都分成3个表了,如果在3个表上执行同样的处理:

select avg(score) score,name from score group by name
然后再进行平均值计算,可不可以呢???答案是否定的。卖个关子这里不做解释,有不理解的同学,下面回帖发问。但是TinyDBRouter框架却可以保证结果的正确性。

数据库支持的普适性:

TinyDBRouter理论上支持各种数据库,各种ORMapping框架,而一般的框架是针对某种ORMapping框架做的,比如:专门针对iBatis,Hibernate的;有的只针对MySql或Oracle等。

SQL支持的普适性:

TinyDBRouter理论上支持所有不违反TinyDBRouter适应规则的SQL。而许多同类框架则有诸多限制。

TinyDBRouter使用限制:

  • 不支持跨分区关联查询
  • 分表模式中只支持光标分页,不支持SQL分页
  • 不支持savePoint
  • 暂时不支持存储过程

总结

TinyDBRouter确实是非常优秀的分区分表方案,当然它也有缺点,那就是测试还不够充分,没有得到充分的验证。

转载于:https://my.oschina.net/tinyframework/blog/201307

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值