744057173的gravatar头像
7440571732017-08-28 15:09:16
java分布式架构搭建

一、前言

用Java开发企业应用软件, 经常会采用Spring+MyBatis+Mysql搭建数据库框架。如果数据量很大,一个MYSQL库存储数据访问效率很低,往往会采用分库存储管理的方式。本文讲述如何通过Spring+Mybatis构建多数据库访问的架构,并采用多线程提升数据库的访问效率。

二、整体方案

 

这里写图片描述

 

三、开发环境准备

3.1 下载Spring、Mybatis、Mysql组件。

3.2 Eclipse:Java开发IDE。引入如下jar包:

 

这里写图片描述

 

代码结构如下:

 

这里写图片描述

 

四、构建数据库集群

在MYSQL中创建11个数据库(test1/2/3/4/5/6/7/8/9/10/11)创建一个简单的表: 

这里写图片描述

 

在test1的tbl_Demo表中插入5千万条数据,其它10个数据库的tbl_Demo表中分别插入5百万条数据(用函数)。

在test1的tbl_Demo表中插入5千万条数据,其它10个数据库的tbl_Demo表中分别插入5百万条数据(用函数)。

五、创建Mybatis数据库映射接口

/**
 * Mybatis 映射接口
 * 
 *
 * @author elon
 * @version 1.0, 2015年10月23日
 */
public interface IDemo 
{   
    public void insertDemo(DemoDAO demo);
    public List<Integer> selectGroup();
}

/**
 * 
 * Mybatis 映射服务接口
 *
 * @author elon
 * @version 1.0, 2015年10月23日
 */
public interface IDemoService
{   
    public void insertDemo(DemoDAO demo);
    public List<Integer> selectGroup();
}

/**
 * 
 * Mybatis 映射服务实现
 *
 * @author elon
 * @version 1.0, 2015年10月23日
 */
public class DemoServiceImpl implements IDemoService
{
    private IDemo idemo = null;

    public void setIdemo(IDemo idemo) {
        this.idemo = idemo;
    }

    @Override
    public void insertDemo(DemoDAO demo)
    {
        idemo.insertDemo(demo);
    }

    @Override
    public List<Integer> selectGroup()
    {   
        return idemo.selectGroup();
    }
}

六、创建数据库标识管理和动态数据源

/**
 * 
 * 保存数据库标识。每个线程由独立的对象存储
 *
 * @author elon
 * @version 1.0, 2015年10月23日
 */
public class DBIndetifier
{   
    private static ThreadLocal<String> dbKey = new ThreadLocal<String>();

    public static void setDBKey(final String dbKeyPara)
    {   
        dbKey.set(dbKeyPara);
    }

    public static String getDBKey()
    {
        return dbKey.get();
    }
}

/**
 * 
 * 动态数据源。可根据不同的数据索引连接不同的数据库
 *
 * @author elon
 * @version 1.0, 2015年10月23日
 */
public class DynamicDataSource extends AbstractRoutingDataSource
{
    @Override
    public Object determineCurrentLookupKey()
    {
        return DBIndetifier.getDBKey();
    }
}

七、创建数据库访问对象

/**
 * 
 * 数据库访问对象。用于插入数据。
 *
 * @author elon
 * @version 1.0, 2015年10月23日
 */
public class DemoDAO
{
    private int a;
    private String b;
    private int c;

    public int getA()
    {
        return a;
    }
    public void setA(int a)
    {
        this.a = a;
    }
    public String getB()
    {
        return b;
    }
    public void setB(String b)
    {
        this.b = b;
    }
    public int getC()
    {
        return c;
    }
    public void setC(int c)
    {
        this.c = c;
    }
}

/**
 * 
 * 映射结果定义
 *
 * @author elon
 * @version 1.0, 2015年10月23日
 */
public class DemoResult implements Serializable
{
    /**
     * Comment for <code>serialVersionUID</code><br>
     * 
     */
    private static final long serialVersionUID = -413001138792531448L;
    private long sum;

    public long getSum()
    {
        return sum;
    }

    public void setSum(long sum)
    {
        this.sum = sum;
    }

    @Override
    public String toString()
    {
        return String.valueOf(sum);
    }
}

八、创建数据库访问任务

/**
 * 数据库访问任务定义。将每一个对数据库访问的请求包装为一个任务对象,放到任务管理中,
 * 然后等待任务执行完成,取出执行结果。
 *
 * @author elon
 * @version 1.0, 2015年10月23日
 */
public class DBTask implements Runnable
{   
    // 操作数据库标识,用于指定访问的数据库。与spring配置文件中的数据动态数据源定义一致。
    private final String dbKey;

    // mybatis数据库访问对象
    private final Object dbAccessObject;

    // mysbatis数据库访问方法名称,用于反射调用
    private final String methodName;

    // 存储可变参数的值
    private final Object[] paraArray;

    // 存储可变参数类型
    @SuppressWarnings("rawtypes")
    private final Class[] paraClassArray;

    // 数据库操作结果。查询操作返回查询结果; 插入、删除、修改操作返回null。
    private Object operateResult;

    // 操作数据库抛出的异常信息
    private Exception exception;

    // 标识任务是否已经执行
    private boolean finish;

    /**
     * 构造函数
     * @param dbKey 数据库标识
     * @param dbAccessObject 数据库访问对象
     * @param methodName 数据库访问方法名称
     * @param paraArray 参数列表
     */
    public DBTask(final String dbKey, final Object dbAccessObject, final String methodName, 
                  final Object... paraArray)
    {
        this.dbKey = dbKey;
        this.dbAccessObject = dbAccessObject;
        this.methodName = methodName;
        this.paraArray = paraArray;
        finish = false;
        exception = null;

        paraClassArray = new Class[paraArray.length];
        for (int index = 0; index < paraArray.length; ++index)
        {
            paraClassArray[index] = paraArray[index].getClass();
        }

        operateResult = null;
    }

    /**
     * 
     * 任务执行函数
     *
     */
    @Override
    public void run()
    {
        try
        {   
            DBIndetifier.setDBKey(dbKey);
            Method method = dbAccessObject.getClass().getMethod(methodName, paraClassArray);

            // 查询操作返回查询结果; 插入、删除、修改操作返回null
            operateResult = method.invoke(dbAccessObject, paraArray);
        }
        catch (Exception e)
        {
            exception = e;
            e.printStackTrace();
        }

        finish = true;
    }

    /**
     * 
     * 返回操作结果。查询操作返回查询结果; 插入、删除、修改操作返回null
     *
     * @return 操作结果
     */
    public Object getRetValue()
    {
        return operateResult;
    }

    /**
     * 抛出数据库操作异常
     * 
     * @return 异常
     */
    public Exception getException()
    {
        return exception;
    }

    /**
     * 
     * 返回任务是否已执行
     *
     * @return 标记
     */
    public boolean isFinish()
    {
        return finish;
    }
}

九、创建数据库任务管理器

/**
 * 数据库访问任务管理。将数据库访问任务放到线程池中执行。
 * 
 *
 * @author elon
 * @version 1.0, 2015年10月23日
 */
public class DBTaskMgr
{
    private static class DBTaskMgrInstance
    {
        public static final DBTaskMgr instance = new DBTaskMgr();
    }

    public static DBTaskMgr instance()
    {
        return DBTaskMgrInstance.instance;
    }

    private ThreadPoolExecutor pool;

    public DBTaskMgr()
    {
        pool = new ThreadPoolExecutor(10, 50, 60, TimeUnit.SECONDS,                                            
                                      new ArrayBlockingQueue<Runnable>(10000),
                                      new ThreadPoolExecutor.CallerRunsPolicy());
    }

    public void excute(Runnable task)
    {
        pool.execute(task);
    }
}

十、创建MyBatis配置文件

10.1 mybatis.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <mappers>
        <mapper resource="cfg/demoMapper.xml"/>
    </mappers>
</configuration>

10.2 demoMapper.xml

<?xml version="1.0" encoding="UTF-8" ?>  
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> 
<mapper namespace="com.elon.IDemo">    
    <insert id="insertDemo" parameterType="com.elon.DemoDAO">
        insert into tbl_demo(a, b, c) values(#{a}, #{b}, #{c});
    </insert>

    <resultMap id="demoResult" type="com.elon.DemoResult">
        <id property="sum" column="sumColum"/>
    </resultMap>

    <select id="selectGroup" resultMap="demoResult">
        select sum(a) as sumColum from tbl_demo group by c;
    </select>
</mapper>

十一、创建Spring配置文件

11.1 spring.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">

    <bean id="dataSource_1" class="org.apache.commons.dbcp.BasicDataSource">
        <property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
        <property name="url" value="jdbc:mysql://10.70.69.69:3306/test1"></property>
        <property name="username" value="user123"></property>
        <property name="password" value="user123"></property>
        <property name="maxActive" value="100"></property>
        <property name="maxIdle" value="30"></property>
        <property name="maxWait" value="500"></property>
        <property name="defaultAutoCommit" value="true"></property>
    </bean>

    <bean id="dataSource_2" class="org.apache.commons.dbcp.BasicDataSource">
        <property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
        <property name="url" value="jdbc:mysql://10.70.69.69:3306/test2"></property>
        <property name="username" value="user123"></property>
        <property name="password" value="user123"></property>
        <property name="maxActive" value="100"></property>
        <property name="maxIdle" value="30"></property>
        <property name="maxWait" value="500"></property>
        <property name="defaultAutoCommit" value="true"></property>
    </bean>

    <bean id="dataSource_3" class="org.apache.commons.dbcp.BasicDataSource">
        <property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
        <property name="url" value="jdbc:mysql://10.70.69.69:3306/test3"></property>
        <property name="username" value="user123"></property>
        <property name="password" value="user123"></property>
        <property name="maxActive" value="100"></property>
        <property name="maxIdle" value="30"></property>
        <property name="maxWait" value="500"></property>
        <property name="defaultAutoCommit" value="true"></property>
    </bean>

    <bean id="dataSource_4" class="org.apache.commons.dbcp.BasicDataSource">
        <property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
        <property name="url" value="jdbc:mysql://10.70.69.69:3306/test4"></property>
        <property name="username" value="user123"></property>
        <property name="password" value="user123"></property>
        <property name="maxActive" value="100"></property>
        <property name="maxIdle" value="30"></property>
        <property name="maxWait" value="500"></property>
        <property name="defaultAutoCommit" value="true"></property>
    </bean>

    <bean id="dataSource_5" class="org.apache.commons.dbcp.BasicDataSource">
        <property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
        <property name="url" value="jdbc:mysql://10.70.69.69:3306/test5"></property>
        <property name="username" value="user123"></property>
        <property name="password" value="user123"></property>
        <property name="maxActive" value="100"></property>
        <property name="maxIdle" value="30"></property>
        <property name="maxWait" value="500"></property>
        <property name="defaultAutoCommit" value="true"></property>
    </bean>

    <bean id="dataSource_6" class="org.apache.commons.dbcp.BasicDataSource">
        <property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
        <property name="url" value="jdbc:mysql://10.70.69.69:3306/test6"></property>
        <property name="username" value="user123"></property>
        <property name="password" value="user123"></property>
        <property name="maxActive" value="100"></property>
        <property name="maxIdle" value="30"></property>
        <property name="maxWait" value="500"></property>
        <property name="defaultAutoCommit" value="true"></property>
    </bean>


    <bean id="dataSource_7" class="org.apache.commons.dbcp.BasicDataSource">
        <property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
        <property name="url" value="jdbc:mysql://10.61.67.246:3306/test7"></property>
        <property name="username" value="user123"></property>
        <property name="password" value="user123"></property>
        <property name="maxActive" value="100"></property>
        <property name="maxIdle" value="30"></property>
        <property name="maxWait" value="500"></property>
        <property name="defaultAutoCommit" value="true"></property>
    </bean>

    <bean id="dataSource_8" class="org.apache.commons.dbcp.BasicDataSource">
        <property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
        <property name="url" value="jdbc:mysql://10.61.67.246:3306/test8"></property>
        <property name="username" value="user123"></property>
        <property name="password" value="user123"></property>
        <property name="maxActive" value="100"></property>
        <property name="maxIdle" value="30"></property>
        <property name="maxWait" value="500"></property>
        <property name="defaultAutoCommit" value="true"></property>
    </bean>

    <bean id="dataSource_9" class="org.apache.commons.dbcp.BasicDataSource">
        <property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
        <property name="url" value="jdbc:mysql://10.61.67.246:3306/test9"></property>
        <property name="username" value="user123"></property>
        <property name="password" value="user123"></property>
        <property name="maxActive" value="100"></property>
        <property name="maxIdle" value="30"></property>
        <property name="maxWait" value="500"></property>
        <property name="defaultAutoCommit" value="true"></property>
    </bean>

    <bean id="dataSource_10" class="org.apache.commons.dbcp.BasicDataSource">
        <property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
        <property name="url" value="jdbc:mysql://10.61.67.246:3306/test10"></property>
        <property name="username" value="user123"></property>
        <property name="password" value="user123"></property>
        <property name="maxActive" value="100"></property>
        <property name="maxIdle" value="30"></property>
        <property name="maxWait" value="500"></property>
        <property name="defaultAutoCommit" value="true"></property>
    </bean>

    <bean id="dataSource_11" class="org.apache.commons.dbcp.BasicDataSource">
        <property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
        <property name="url" value="jdbc:mysql://10.61.67.246:3306/test11"></property>
        <property name="username" value="user123"></property>
        <property name="password" value="user123"></property>
        <property name="maxActive" value="100"></property>
        <property name="maxIdle" value="30"></property>
        <property name="maxWait" value="500"></property>
        <property name="defaultAutoCommit" value="true"></property>
    </bean>

    <bean id="dataSource" class="com.elon.DynamicDataSource">
        <property name="targetDataSources">
            <map>
                <entry key="test1" value-ref="dataSource_1"/>
                <entry key="test2" value-ref="dataSource_2"/>
                <entry key="test3" value-ref="dataSource_3"/>
                <entry key="test4" value-ref="dataSource_4"/>
                <entry key="test5" value-ref="dataSource_5"/>
                <entry key="test6" value-ref="dataSource_6"/>
                <entry key="test7" value-ref="dataSource_7"/>
                <entry key="test8" value-ref="dataSource_8"/>
                <entry key="test9" value-ref="dataSource_9"/>
                <entry key="test10" value-ref="dataSource_10"/>
                <entry key="test11" value-ref="dataSource_11"/>
            </map>
        </property>
    </bean>

    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="configLocation" value="classpath:cfg/mybatis.xml"></property>
        <property name="dataSource" ref="dataSource" />
    </bean>

    <bean id="iDemo" class="org.mybatis.spring.mapper.MapperFactoryBean">
        <property name="mapperInterface" value="com.elon.IDemo"></property>
        <property name="sqlSessionFactory" ref="sqlSessionFactory"></property>
    </bean>

    <bean id="iDemoService" class="com.elon.DemoServiceImpl">
        <property name="idemo" ref="iDemo"></property>
    </bean>

</beans>

十二、测试代码

public class TestMain
{
    /**
     * 测试代码
     * 
     *
     * @param args
     */
    public static void main(String[] args)
    {
        @SuppressWarnings("resource")
        ApplicationContext context = new ClassPathXmlApplicationContext("cfg/spring.xml");
        IDemoService service1 = (IDemoService)context.getBean("iDemoService");

        // 创建任务对象
        DBTask task1 = new DBTask("test1", service1, "selectGroup");
        DBTask task2 = new DBTask("test2", service1, "selectGroup");
        DBTask task3 = new DBTask("test3", service1, "selectGroup");
        DBTask task4 = new DBTask("test4", service1, "selectGroup");
        DBTask task5 = new DBTask("test5", service1, "selectGroup");
        DBTask task6 = new DBTask("test6", service1, "selectGroup");
        DBTask task7 = new DBTask("test7", service1, "selectGroup");
        DBTask task8 = new DBTask("test8", service1, "selectGroup");
        DBTask task9 = new DBTask("test9", service1, "selectGroup");
        DBTask task10 = new DBTask("test10", service1, "selectGroup");
        DBTask task11 = new DBTask("test11", service1, "selectGroup");

        DemoDAO demo = new DemoDAO();
        demo.setA(10000000);
        demo.setB("12121212");
        demo.setC(100);
        DBTask taskInsert = new DBTask("test2", service1, "insertDemo", demo);

        SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

        System.out.println("开始插入数据:" + format.format(new Date()));
        DBTaskMgr.instance().excute(taskInsert);

        while (true)
        {
            if (!taskInsert.isFinish())
            {
                try
                {
                    Thread.sleep(1000);
                }
                catch (InterruptedException e)
                {
                    e.printStackTrace();
                }
            }
            else
            {
                break;
            }
        }

        System.out.println("插入数据结束:" + format.format(new Date()));

        System.out.println("开始查询5千万数据表:" + format.format(new Date()));
        DBTaskMgr.instance().excute(task1);

        while (true)
        {
            if (!task1.isFinish())
            {
                try
                {
                    Thread.sleep(1000);
                }
                catch (InterruptedException e)
                {
                    e.printStackTrace();
                }
            }
            else
            {
                break;
            }
        }

        System.out.println(task1.getRetValue());
        System.out.println("查询5千万数据表结束:" + format.format(new Date()));

        List<DBTask> taskList = new ArrayList<DBTask>();
        taskList.add(task2);
        taskList.add(task3);
        taskList.add(task4);
        taskList.add(task5);
        taskList.add(task6);
        taskList.add(task7);
        taskList.add(task8);
        taskList.add(task9);
        taskList.add(task10);
        taskList.add(task11);

        System.out.println("开始查询10个5百万数据表:" + format.format(new Date()));
        for (DBTask task : taskList)
        {
            DBTaskMgr.instance().excute(task);
        }

        while (true)
        {
            int success  = 0;
            for (DBTask task : taskList)
            {
                if (!task.isFinish())
                {   
                    try
                    {
                        Thread.sleep(1000);
                    }
                    catch (InterruptedException e)
                    {
                        e.printStackTrace();
                    }
                }
                else
                {
                    ++success;
                }
            }

            if (success == 10)
            {
                break;
            }
        }

        for (DBTask task : taskList)
        {
            System.out.println(task.getRetValue());;
        }

        System.out.println("10个5百万数据表查询结束:" +format.format(new Date()));
    }
}

十三、测试结果

 

这里写图片描述

 

直接查询一个5千万条数据的数据库用时:45s。 
多线程同步查询10个5百万数据的数据库用时: 22s。

由于10个数据库放在两台服务器上,一个服务器5个数据库。如果将10个数据分别部署到10个服务器,效率将更高。


打赏

分享到:

最近浏览
wangzp昨天
暂无贡献等级
hellozhao前天
暂无贡献等级
meiyuxian10月16日
暂无贡献等级
vvvvvvvfdf10月14日
最代码贡献等级说明
wdyouwen10月12日
暂无贡献等级
IT小牛10月12日
最代码贡献等级说明
cxl11415810月11日
暂无贡献等级
hohoho10月11日
最代码贡献等级说明
gudumingjian10月10日
暂无贡献等级
zhang123qiang9月29日
暂无贡献等级
liwenhao9月29日
暂无贡献等级
xjc6211059月28日
暂无贡献等级
zxmmy5209月28日
暂无贡献等级
苏同学9月28日
最代码贡献等级说明
独行侠梦9月28日
最代码贡献等级说明
暂无贡献等级
cvsFeng9月28日
最代码贡献等级说明
yutons9月27日
暂无贡献等级
最代码广告位
顶部客服微信二维码底部
>扫描二维码关注最代码为好友扫描二维码关注最代码为好友