精华内容
下载资源
问答
  • MyCat 是目前流行的基于 java 语言编写的数据库中间件,是一个实现了 MySQL 协议 的服务器,前端用户可以把它看作是一个数据库代理,用 MySQL 客户端工具和命令行访问,而其后端可以用 MySQL 原生协议与多个 ...
  • 从零开发分布式数据库中间件 二、构建MyBatis的读写分离数据库中间件 在上一节 从零开发分布式数据库中间件 ...

    从零开发分布式数据库中间件 二、构建MyBatis的读写分离数据库中间件

      在上一节  从零开发分布式数据库中间件 一、读写分离的数据库中间件 中,我们讲了如何通过ThreadLocal来指定每次访问的数据源,并通过jdbc的连接方式来切换数据源,那么这一节我们使用我们常用的数据库持久层框架MyBatis来实现数据库读写分离。

    一、数据源代理:

    此类与上一节相似,即可以指定当前线程访问的数据源。

    1. package com.happyheng.datasource;  
    2.   
    3. /** 
    4.  * 数据源代理设置 
    5.  * Created by happyheng on 17/1/15. 
    6.  */  
    7. public class DataSourceProxy {  
    8.   
    9.     private static ThreadLocal<DataSourceEnum> threadLocal = new ThreadLocal<>();  
    10.   
    11.     public enum DataSourceEnum {  
    12.         MASTER,  
    13.         SLAVE  
    14.     }  
    15.   
    16.     /** 
    17.      * 为当前线程设置数据源 
    18.      */  
    19.     public static void setDataSource(DataSourceEnum sourceEnum) {  
    20.         threadLocal.set(sourceEnum);  
    21.     }  
    22.   
    23.     public static DataSourceEnum getDataSource() {  
    24.         return threadLocal.get();  
    25.     }  
    26.   
    27. }  
    package com.happyheng.datasource;
    
    /**
     * 数据源代理设置
     * Created by happyheng on 17/1/15.
     */
    public class DataSourceProxy {
    
        private static ThreadLocal<DataSourceEnum> threadLocal = new ThreadLocal<>();
    
        public enum DataSourceEnum {
            MASTER,
            SLAVE
        }
    
        /**
         * 为当前线程设置数据源
         */
        public static void setDataSource(DataSourceEnum sourceEnum) {
            threadLocal.set(sourceEnum);
        }
    
        public static DataSourceEnum getDataSource() {
            return threadLocal.get();
        }
    
    }
    

    二、数据源Map:

      首先我们需要将我们的读写数据源都写入到配置文件中,并设置到继承了AbstractRoutingDataSource抽象类的子类中,接下来我们会讲解AbstractRoutingDataSource的作用:

    1. <bean id="masterDataSource" class="org.apache.commons.dbcp.BasicDataSource"  
    2.       destroy-method="close">  
    3.     <property name="driverClassName" value="${master.driver}" />  
    4.     <property name="url" value="${master.dburl}" />  
    5.     <property name="username" value="${master.user}" />  
    6.     <property name="password" value="${master.password}" />  
    7. </bean>  
    8.   
    9. <bean id="slaveDataSource1" class="org.apache.commons.dbcp.BasicDataSource"  
    10.       destroy-method="close">  
    11.     <property name="driverClassName" value="${slave1.driver}" />  
    12.     <property name="url" value="${slave1.dburl}" />  
    13.     <property name="username" value="${slave1.user}" />  
    14.     <property name="password" value="${slave1.password}" />  
    15. </bean>  
    16.   
    17. <bean id="slaveDataSource2" class="org.apache.commons.dbcp.BasicDataSource"  
    18.       destroy-method="close">  
    19.     <property name="driverClassName" value="${slave2.driver}" />  
    20.     <property name="url" value="${slave2.dburl}" />  
    21.     <property name="username" value="${slave2.user}" />  
    22.     <property name="password" value="${slave2.password}" />  
    23. </bean>  
    24.   
    25. <bean id="dataSource" class="com.happyheng.datasource.OptionalDataSource" >  
    26.     <!-- 通过key-value的形式来关联数据源 -->  
    27.     <property name="targetDataSources">  
    28.         <map>  
    29.             <entry key="masterDataSource" value-ref="masterDataSource" />  
    30.             <entry key="slaveDataSource1" value-ref="slaveDataSource1" />  
    31.             <entry key="slaveDataSource2" value-ref="slaveDataSource2" />  
    32.         </map>  
    33.     </property>  
    34.     <property name="defaultTargetDataSource" ref="masterDataSource" />  
    35. </bean>  
    36.   
    37. <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">  
    38.     <property name="dataSource" ref="dataSource" />  
    39.     <property name="mapperLocations" value="classpath*:mybatis/**/*Mapper.xml"/>  
    40. </bean>  
        <bean id="masterDataSource" class="org.apache.commons.dbcp.BasicDataSource"
              destroy-method="close">
            <property name="driverClassName" value="${master.driver}" />
            <property name="url" value="${master.dburl}" />
            <property name="username" value="${master.user}" />
            <property name="password" value="${master.password}" />
        </bean>
    
        <bean id="slaveDataSource1" class="org.apache.commons.dbcp.BasicDataSource"
              destroy-method="close">
            <property name="driverClassName" value="${slave1.driver}" />
            <property name="url" value="${slave1.dburl}" />
            <property name="username" value="${slave1.user}" />
            <property name="password" value="${slave1.password}" />
        </bean>
    
        <bean id="slaveDataSource2" class="org.apache.commons.dbcp.BasicDataSource"
              destroy-method="close">
            <property name="driverClassName" value="${slave2.driver}" />
            <property name="url" value="${slave2.dburl}" />
            <property name="username" value="${slave2.user}" />
            <property name="password" value="${slave2.password}" />
        </bean>
    
        <bean id="dataSource" class="com.happyheng.datasource.OptionalDataSource" >
            <!-- 通过key-value的形式来关联数据源 -->
            <property name="targetDataSources">
                <map>
                    <entry key="masterDataSource" value-ref="masterDataSource" />
                    <entry key="slaveDataSource1" value-ref="slaveDataSource1" />
                    <entry key="slaveDataSource2" value-ref="slaveDataSource2" />
                </map>
            </property>
            <property name="defaultTargetDataSource" ref="masterDataSource" />
        </bean>
    
        <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
            <property name="dataSource" ref="dataSource" />
            <property name="mapperLocations" value="classpath*:mybatis/**/*Mapper.xml"/>
        </bean>


    二、AbstractRoutingDataSource数据源路由类:

      在MyBatis中,需从SqlSessionFactory中获取dao文件,而SqlSessionFactory即需要数据源,因为我们需要根据不同的情况来选定数据源,所以不能写死一个数据源,而是应该将数据源写入到AbstractRoutingDataSource中的map中,AbstractRoutingDataSource即能够根据不同的情况指定访问的数据源。

      还有需要注意的是,AbstractRoutingDataSource是一个抽象类,需要实现其determineCurrentLookupKey方法,来指定每次访问数据库的数据源。

    下为继承了AbstractRoutingDataSource的OptionalDataSource类:

    1. package com.happyheng.datasource;  
    2.   
    3. import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;  
    4.   
    5. /** 
    6.  * Created by happyheng on 17/1/10. 
    7.  */  
    8. public class OptionalDataSource extends AbstractRoutingDataSource {  
    9.   
    10.     // 数据源  
    11.     private String masterDataSource = "masterDataSource";  
    12.     private String[] slaveDataSource = {"slaveDataSource1""slaveDataSource2"};  
    13.   
    14.     @Override  
    15.     protected Object determineCurrentLookupKey() {  
    16.   
    17.   
    18.         DataSourceProxy.DataSourceEnum dataSourceEnum = DataSourceProxy.getDataSource();  
    19.   
    20.         if (dataSourceEnum == DataSourceProxy.DataSourceEnum.SLAVE) {  
    21.   
    22.             double random = Math.random();  
    23.             int randomIndex = (int)(random * slaveDataSource.length);  
    24.   
    25.             System.out.println("访问的是从数据库" + (randomIndex + 1));  
    26.             return slaveDataSource[randomIndex];  
    27.         } else {  
    28.   
    29.             System.out.println("访问的是主数据库");  
    30.             return masterDataSource;  
    31.         }  
    32.     }  
    33. }  
    package com.happyheng.datasource;
    
    import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
    
    /**
     * Created by happyheng on 17/1/10.
     */
    public class OptionalDataSource extends AbstractRoutingDataSource {
    
        // 数据源
        private String masterDataSource = "masterDataSource";
        private String[] slaveDataSource = {"slaveDataSource1", "slaveDataSource2"};
    
        @Override
        protected Object determineCurrentLookupKey() {
    
    
            DataSourceProxy.DataSourceEnum dataSourceEnum = DataSourceProxy.getDataSource();
    
            if (dataSourceEnum == DataSourceProxy.DataSourceEnum.SLAVE) {
    
                double random = Math.random();
                int randomIndex = (int)(random * slaveDataSource.length);
    
                System.out.println("访问的是从数据库" + (randomIndex + 1));
                return slaveDataSource[randomIndex];
            } else {
    
                System.out.println("访问的是主数据库");
                return masterDataSource;
            }
        }
    }
    

      首先,我们将一主两从的数据源都写入到OptionalDataSource的map中,而每次MyBatis访问数据库时,都会调用此类的determineCurrentLookupKey()来获取数据源map中的key,从而得到对应的数据源。

      可以看出,当我们发现是访问从数据库时,使用随机法来获取从数据库数据源,当发现是访问主数据库时,直接访问主数据库数据源。


    4、此项目已在github上开源,可以完整实现MyBatis的数据库读写分离,地址为:github。如果觉得不错,那么就star一下来鼓励我吧。 



    转载于:https://www.cnblogs.com/jpfss/p/8136375.html

    展开全文
  • 从零开发分布式数据库中间件 一、读写分离的数据库中间件 ...

    从零开发分布式数据库中间件 一、读写分离的数据库中间件

      在传统的单机体系中,我们在操作数据库时,只需要直接得到数据库的连接,然后操作数据库即可,可是在现在的数据爆炸时代,只靠单机是无法承载如此大的用户量的,即我们不能纵向扩展,那么我们就只能水平进行扩展,即使用读写分离的主从数据库来缓解数据库的压力,而在读写分离之后,如何使程序能正确的得到主数据库的连接或者是从数据库的连接,就是我们今天读写分离的数据库中间件需要实现的。

    一、主从数据库介绍:

      主从数据库即为一个主数据库会有对应n个从数据库,而从数据库只能有一个对应的从数据库。主从数据库中写的操作需要使用主数据库,而读操作使用从数据库。主数据库与从数据库始终保持数据一致性。其中保持数据库一致的原理即为当主数据库数据发生变化时,会将操作写入到主数据库日志中,而从数据库会不停的读取主数据库的日志保存到自己的日志系统中,然后进行执行,从而保持了主从数据库一致。


    二、开发前准备及ConnectionFactory类的开发:

      在了解了主从数据库后,我们可以进行分布式数据库中间件的开发,由于mysql本身支持主从数据库,但限于篇幅,就不讲mysql的主从配置了,我们先使用本机的mysql作为一主两从的数据库源即可,下面是我本机的数据库连接配置文件,其中有一主两从:

    1. master.driver=com.mysql.jdbc.Driver  
    2. master.dburl=jdbc\:mysql\://127.0.0.1\:3306/master_slave_db  
    3. master.user=root  
    4. master.password=mytestcon  
    5.   
    6. slave1.driver=com.mysql.jdbc.Driver  
    7. slave1.dburl=jdbc\:mysql\://127.0.0.1\:3306/master_slave_db  
    8. slave1.user=root  
    9. slave1.password=mytestcon  
    10.   
    11. slave2.driver=com.mysql.jdbc.Driver  
    12. slave2.dburl=jdbc\:mysql\://127.0.0.1\:3306/master_slave_db  
    13. slave2.user=root  
    14. slave2.password=mytestcon  
    master.driver=com.mysql.jdbc.Driver
    master.dburl=jdbc\:mysql\://127.0.0.1\:3306/master_slave_db
    master.user=root
    master.password=mytestcon
    
    slave1.driver=com.mysql.jdbc.Driver
    slave1.dburl=jdbc\:mysql\://127.0.0.1\:3306/master_slave_db
    slave1.user=root
    slave1.password=mytestcon
    
    slave2.driver=com.mysql.jdbc.Driver
    slave2.dburl=jdbc\:mysql\://127.0.0.1\:3306/master_slave_db
    slave2.user=root
    slave2.password=mytestcon
    有了主从数据库的连接配置后,就可以将配置进行封装。

    封装的DataSource类:

    1. public class DataSource {  
    2.   
    3.     private String driver;  
    4.   
    5.     private String url;  
    6.   
    7.     private String user;  
    8.   
    9.     private String password;  
    10.   
    11.   
    12.     public String getDriver() {  
    13.         return driver;  
    14.     }  
    15.   
    16.     public void setDriver(String driver) {  
    17.         this.driver = driver;  
    18.     }  
    19.   
    20.     public String getUrl() {  
    21.         return url;  
    22.     }  
    23.   
    24.     public void setUrl(String url) {  
    25.         this.url = url;  
    26.     }  
    27.   
    28.     public String getUser() {  
    29.         return user;  
    30.     }  
    31.   
    32.     public void setUser(String user) {  
    33.         this.user = user;  
    34.     }  
    35.   
    36.     public String getPassword() {  
    37.         return password;  
    38.     }  
    39.   
    40.     public void setPassword(String password) {  
    41.         this.password = password;  
    42.     }  
    43. }  
    public class DataSource {
    
        private String driver;
    
        private String url;
    
        private String user;
    
        private String password;
    
    
        public String getDriver() {
            return driver;
        }
    
        public void setDriver(String driver) {
            this.driver = driver;
        }
    
        public String getUrl() {
            return url;
        }
    
        public void setUrl(String url) {
            this.url = url;
        }
    
        public String getUser() {
            return user;
        }
    
        public void setUser(String user) {
            this.user = user;
        }
    
        public String getPassword() {
            return password;
        }
    
        public void setPassword(String password) {
            this.password = password;
        }
    }
    
    在Spring配置文件中,从配置文件中读取配置并将配置转换为封装的DataSource类:
    1. <bean id="masterDataSource" class="com.happyheng.connection.DataSource">  
    2.     <property name="driver" value="${master.driver}"/>  
    3.     <property name="url" value="${master.dburl}"/>  
    4.     <property name="user" value="${master.user}"/>  
    5.     <property name="password" value="${master.password}"/>  
    6. </bean>  
    7.   
    8. <bean id="slaveDataSource1" class="com.happyheng.connection.DataSource">  
    9.     <property name="driver" value="${slave1.driver}"/>  
    10.     <property name="url" value="${slave1.dburl}"/>  
    11.     <property name="user" value="${slave1.user}"/>  
    12.     <property name="password" value="${slave1.password}"/>  
    13. </bean>  
    14.   
    15. <bean id="slaveDataSource2" class="com.happyheng.connection.DataSource">  
    16.     <property name="driver" value="${slave2.driver}"/>  
    17.     <property name="url" value="${slave2.dburl}"/>  
    18.     <property name="user" value="${slave2.user}"/>  
    19.     <property name="password" value="${slave2.password}"/>  
    20. </bean>  
    21.   
    22. <bean id="propertyConfigurer"  
    23.       class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">  
    24.     <property name="locations">  
    25.         <list>  
    26.             <value>classpath:dataSource.properties</value>  
    27.         </list>  
    28.     </property>  
    29. </bean>  
        <bean id="masterDataSource" class="com.happyheng.connection.DataSource">
            <property name="driver" value="${master.driver}"/>
            <property name="url" value="${master.dburl}"/>
            <property name="user" value="${master.user}"/>
            <property name="password" value="${master.password}"/>
        </bean>
    
        <bean id="slaveDataSource1" class="com.happyheng.connection.DataSource">
            <property name="driver" value="${slave1.driver}"/>
            <property name="url" value="${slave1.dburl}"/>
            <property name="user" value="${slave1.user}"/>
            <property name="password" value="${slave1.password}"/>
        </bean>
    
        <bean id="slaveDataSource2" class="com.happyheng.connection.DataSource">
            <property name="driver" value="${slave2.driver}"/>
            <property name="url" value="${slave2.dburl}"/>
            <property name="user" value="${slave2.user}"/>
            <property name="password" value="${slave2.password}"/>
        </bean>
    
        <bean id="propertyConfigurer"
              class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
            <property name="locations">
                <list>
                    <value>classpath:dataSource.properties</value>
                </list>
            </property>
        </bean>
    有了主从的连接之后,我们就可以写一个ConnectionFactory类,此类可以为外部类直接提供主数据库、从数据库的连接,相当于数据库连接的封装:

    1. @Service  
    2. public class ConnectionFactory {  
    3.   
    4.     @Autowired  
    5.     private DataSource masterDataSource;  
    6.   
    7.     @Autowired  
    8.     private DataSource slaveDataSource1;  
    9.   
    10.     @Autowired  
    11.     private DataSource slaveDataSource2;  
    12.   
    13.     private List<DataSource> slaveDataSourceList;  
    14.   
    15.     private int slaveDataSourceSize;  
    16.   
    17.   
    18.     @PostConstruct  
    19.     private void init() {  
    20.         slaveDataSourceList = new ArrayList<>();  
    21.         slaveDataSourceList.add(slaveDataSource1);  
    22.         slaveDataSourceList.add(slaveDataSource2);  
    23.   
    24.         slaveDataSourceSize = slaveDataSourceList.size();  
    25.     }  
    26.   
    27.   
    28.     /** 
    29.      * 得到主数据的连接 
    30.      */  
    31.     public Connection getMasterConnection() {  
    32.         return getConnection(masterDataSource);  
    33.     }  
    34.   
    35.     /** 
    36.      * 得到从数据库的连接数量 
    37.      */  
    38.     public int getSlaveDataSourceSize() {  
    39.         return slaveDataSourceSize;  
    40.     }  
    41.   
    42.     /** 
    43.      * 得到从数据n的连接 
    44.      */  
    45.     public Connection getSlaveConnection(int index){  
    46.         return getConnection(slaveDataSourceList.get(index));  
    47.     }  
    48.   
    49.   
    50.     private Connection getConnection(DataSource dataSource){  
    51.   
    52.         Connection connection = null;  
    53.         try {  
    54.             Class.forName(dataSource.getDriver());  
    55.   
    56.             connection = DriverManager.getConnection(dataSource.getUrl(), dataSource.getUser(), dataSource.getPassword());  
    57.         } catch (Exception e) {  
    58.             e.printStackTrace();  
    59.         }  
    60.         return connection;  
    61.     }  
    62. }  
    @Service
    public class ConnectionFactory {
    
        @Autowired
        private DataSource masterDataSource;
    
        @Autowired
        private DataSource slaveDataSource1;
    
        @Autowired
        private DataSource slaveDataSource2;
    
        private List<DataSource> slaveDataSourceList;
    
        private int slaveDataSourceSize;
    
    
        @PostConstruct
        private void init() {
            slaveDataSourceList = new ArrayList<>();
            slaveDataSourceList.add(slaveDataSource1);
            slaveDataSourceList.add(slaveDataSource2);
    
            slaveDataSourceSize = slaveDataSourceList.size();
        }
    
    
        /**
         * 得到主数据的连接
         */
        public Connection getMasterConnection() {
            return getConnection(masterDataSource);
        }
    
        /**
         * 得到从数据库的连接数量
         */
        public int getSlaveDataSourceSize() {
            return slaveDataSourceSize;
        }
    
        /**
         * 得到从数据n的连接
         */
        public Connection getSlaveConnection(int index){
            return getConnection(slaveDataSourceList.get(index));
        }
    
    
        private Connection getConnection(DataSource dataSource){
    
            Connection connection = null;
            try {
                Class.forName(dataSource.getDriver());
    
                connection = DriverManager.getConnection(dataSource.getUrl(), dataSource.getUser(), dataSource.getPassword());
            } catch (Exception e) {
                e.printStackTrace();
            }
            return connection;
        }
    }
    封装完成后,我们就可以使用getMasterConnection()直接得到主数据库的连接,使用getSlaveConnection(int)可以得到从数据库1或者是从数据2的连接。


    三、Proxy代理类的实现:

      代理类即是可以让程序中数据库访问得到正确的数据库连接,所以称为代理。

      1、使用ThreadLocal为当前线程指定数据库访问模式:

      由于Proxy不知道程序使用的是主数据库还是从数据库,所以程序在访问数据库之前要调用Proxy代理类来为当前线程打一个Tag,即指定是使用主数据库还是从数据库。由于而web服务器中每个请求是多线程环境,所以使用ThreadLocal类:

      2、使用随机法来访问从数据库:

      由于从数据库有多个,所以我们可以使用随机法来随机访问每个从数据库,随机法在高并发的情况下有很平均的分布,性能也非常好。

      3、具体实现:

    1. @Service  
    2. public class DataSourceProxy {  
    3.   
    4.     ThreadLocal<String> dataSourceThreadLocal = new ThreadLocal<>();  
    5.   
    6.     public static final String MASTER = "master";  
    7.     public static final String SLAVE = "slave";  
    8.   
    9.     @Resource  
    10.     private ConnectionFactory connectionFactory;  
    11.   
    12.     /** 
    13.      * 设置当前线程的数据库Mode 
    14.      */  
    15.     public void setMode(String dataMode) {  
    16.         dataSourceThreadLocal.set(dataMode);  
    17.     }  
    18.   
    19.     /** 
    20.      * 得到当前数据库Mode 
    21.      */  
    22.     public String getMode() {  
    23.         return dataSourceThreadLocal.get();  
    24.     }  
    25.   
    26.     /** 
    27.      * 根据当前Mode得到Connection连接对象 
    28.      */  
    29.     public Connection getThreadConnection() {  
    30.   
    31.         // 1.判断当前是从数据还是主数据库,默认是主数据库  
    32.         String mode = getMode();  
    33.         if (!StringUtils.isEmpty(mode) && SLAVE.equals(mode)) {  
    34.   
    35.             // y1.如果是从数据库,那么使用随机数的形式来得到从数据库连接  
    36.             double random = Math.random();  
    37.             int index = (int) (random * connectionFactory.getSlaveDataSourceSize());  
    38.   
    39.             System.out.println("----使用的为第" + (index + 1) + "从数据库----");  
    40.   
    41.             return connectionFactory.getSlaveConnection(index);  
    42.         } else {  
    43.   
    44.             System.out.println("----使用的为主数据库----");  
    45.   
    46.             // f1.如果是主数据库,因为只有一个,所以直接获取即可  
    47.             return connectionFactory.getMasterConnection();  
    48.         }  
    49.   
    50.     }  
    51.   
    52. }  
    @Service
    public class DataSourceProxy {
    
        ThreadLocal<String> dataSourceThreadLocal = new ThreadLocal<>();
    
        public static final String MASTER = "master";
        public static final String SLAVE = "slave";
    
        @Resource
        private ConnectionFactory connectionFactory;
    
        /**
         * 设置当前线程的数据库Mode
         */
        public void setMode(String dataMode) {
            dataSourceThreadLocal.set(dataMode);
        }
    
        /**
         * 得到当前数据库Mode
         */
        public String getMode() {
            return dataSourceThreadLocal.get();
        }
    
        /**
         * 根据当前Mode得到Connection连接对象
         */
        public Connection getThreadConnection() {
    
            // 1.判断当前是从数据还是主数据库,默认是主数据库
            String mode = getMode();
            if (!StringUtils.isEmpty(mode) && SLAVE.equals(mode)) {
    
                // y1.如果是从数据库,那么使用随机数的形式来得到从数据库连接
                double random = Math.random();
                int index = (int) (random * connectionFactory.getSlaveDataSourceSize());
    
                System.out.println("----使用的为第" + (index + 1) + "从数据库----");
    
                return connectionFactory.getSlaveConnection(index);
            } else {
    
                System.out.println("----使用的为主数据库----");
    
                // f1.如果是主数据库,因为只有一个,所以直接获取即可
                return connectionFactory.getMasterConnection();
            }
    
        }
    
    }

    4、此工程已在github上开源,可以完整实现数据库的读写分离,地址为:github 。如果觉得不错,那么就star一下来鼓励我吧。 


    转载于:https://www.cnblogs.com/jpfss/p/8136349.html

    展开全文
  • 在传统的单机体系中,我们在操作数据库时,只需要...而在读写分离之后,如何使程序能正确的得到主数据库的连接或者是从数据库的连接,就是我们今天读写分离的数据库中间件需要实现的。 一、主从数据库介绍: 主...

    在传统的单机体系中,我们在操作数据库时,只需要直接得到数据库的连接,然后操作数据库即可,可是在现在的数据爆炸时代,只靠单机是无法承载如此大的用户量的,即我们不能纵向扩展,那么我们就只能水平进行扩展,即使用读写分离的主从数据库来缓解数据库的压力,而在读写分离之后,如何使程序能正确的得到主数据库的连接或者是从数据库的连接,就是我们今天读写分离的数据库中间件需要实现的。

    一、主从数据库介绍:

      主从数据库即为一个主数据库会有对应n个从数据库,而从数据库只能有一个对应的从数据库。主从数据库中写的操作需要使用主数据库,而读操作使用从数据库。主数据库与从数据库始终保持数据一致性。其中保持数据库一致的原理即为当主数据库数据发生变化时,会将操作写入到主数据库日志中,而从数据库会不停的读取主数据库的日志保存到自己的日志系统中,然后进行执行,从而保持了主从数据库一致。

     

    二、开发前准备及ConnectionFactory类的开发:

      在了解了主从数据库后,我们可以进行分布式数据库中间件的开发,由于mysql本身支持主从数据库,但限于篇幅,就不讲mysql的主从配置了,我们先使用本机的mysql作为一主两从的数据库源即可,下面是我本机的数据库连接配置文件,其中有一主两从:

     

    1. master.driver=com.mysql.jdbc.Driver  
    2. master.dburl=jdbc\:mysql\://127.0.0.1\:3306/master_slave_db  
    3. master.user=root  
    4. master.password=mytestcon  
    5.   
    6. slave1.driver=com.mysql.jdbc.Driver  
    7. slave1.dburl=jdbc\:mysql\://127.0.0.1\:3306/master_slave_db  
    8. slave1.user=root  
    9. slave1.password=mytestcon  
    10.   
    11. slave2.driver=com.mysql.jdbc.Driver  
    12. slave2.dburl=jdbc\:mysql\://127.0.0.1\:3306/master_slave_db  
    13. slave2.user=root  
    14. slave2.password=mytestcon  
    master.driver=com.mysql.jdbc.Driver
    master.dburl=jdbc\:mysql\://127.0.0.1\:3306/master_slave_db
    master.user=root
    master.password=mytestcon
    
    slave1.driver=com.mysql.jdbc.Driver
    slave1.dburl=jdbc\:mysql\://127.0.0.1\:3306/master_slave_db
    slave1.user=root
    slave1.password=mytestcon
    
    slave2.driver=com.mysql.jdbc.Driver
    slave2.dburl=jdbc\:mysql\://127.0.0.1\:3306/master_slave_db
    slave2.user=root
    slave2.password=mytestcon

    有了主从数据库的连接配置后,就可以将配置进行封装。

     

    封装的DataSource类:

     

    1. public class DataSource {  
    2.   
    3.     private String driver;  
    4.   
    5.     private String url;  
    6.   
    7.     private String user;  
    8.   
    9.     private String password;  
    10.   
    11.   
    12.     public String getDriver() {  
    13.         return driver;  
    14.     }  
    15.   
    16.     public void setDriver(String driver) {  
    17.         this.driver = driver;  
    18.     }  
    19.   
    20.     public String getUrl() {  
    21.         return url;  
    22.     }  
    23.   
    24.     public void setUrl(String url) {  
    25.         this.url = url;  
    26.     }  
    27.   
    28.     public String getUser() {  
    29.         return user;  
    30.     }  
    31.   
    32.     public void setUser(String user) {  
    33.         this.user = user;  
    34.     }  
    35.   
    36.     public String getPassword() {  
    37.         return password;  
    38.     }  
    39.   
    40.     public void setPassword(String password) {  
    41.         this.password = password;  
    42.     }  
    43. }  
    public class DataSource {
    
        private String driver;
    
        private String url;
    
        private String user;
    
        private String password;
    
    
        public String getDriver() {
            return driver;
        }
    
        public void setDriver(String driver) {
            this.driver = driver;
        }
    
        public String getUrl() {
            return url;
        }
    
        public void setUrl(String url) {
            this.url = url;
        }
    
        public String getUser() {
            return user;
        }
    
        public void setUser(String user) {
            this.user = user;
        }
    
        public String getPassword() {
            return password;
        }
    
        public void setPassword(String password) {
            this.password = password;
        }
    }
    

    在Spring配置文件中,从配置文件中读取配置并将配置转换为封装的DataSource类:

    1. <bean id="masterDataSource" class="com.happyheng.connection.DataSource">  
    2.     <property name="driver" value="${master.driver}"/>  
    3.     <property name="url" value="${master.dburl}"/>  
    4.     <property name="user" value="${master.user}"/>  
    5.     <property name="password" value="${master.password}"/>  
    6. </bean>  
    7.   
    8. <bean id="slaveDataSource1" class="com.happyheng.connection.DataSource">  
    9.     <property name="driver" value="${slave1.driver}"/>  
    10.     <property name="url" value="${slave1.dburl}"/>  
    11.     <property name="user" value="${slave1.user}"/>  
    12.     <property name="password" value="${slave1.password}"/>  
    13. </bean>  
    14.   
    15. <bean id="slaveDataSource2" class="com.happyheng.connection.DataSource">  
    16.     <property name="driver" value="${slave2.driver}"/>  
    17.     <property name="url" value="${slave2.dburl}"/>  
    18.     <property name="user" value="${slave2.user}"/>  
    19.     <property name="password" value="${slave2.password}"/>  
    20. </bean>  
    21.   
    22. <bean id="propertyConfigurer"  
    23.       class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">  
    24.     <property name="locations">  
    25.         <list>  
    26.             <value>classpath:dataSource.properties</value>  
    27.         </list>  
    28.     </property>  
    29. </bean>  
        <bean id="masterDataSource" class="com.happyheng.connection.DataSource">
            <property name="driver" value="${master.driver}"/>
            <property name="url" value="${master.dburl}"/>
            <property name="user" value="${master.user}"/>
            <property name="password" value="${master.password}"/>
        </bean>
    
        <bean id="slaveDataSource1" class="com.happyheng.connection.DataSource">
            <property name="driver" value="${slave1.driver}"/>
            <property name="url" value="${slave1.dburl}"/>
            <property name="user" value="${slave1.user}"/>
            <property name="password" value="${slave1.password}"/>
        </bean>
    
        <bean id="slaveDataSource2" class="com.happyheng.connection.DataSource">
            <property name="driver" value="${slave2.driver}"/>
            <property name="url" value="${slave2.dburl}"/>
            <property name="user" value="${slave2.user}"/>
            <property name="password" value="${slave2.password}"/>
        </bean>
    
        <bean id="propertyConfigurer"
              class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
            <property name="locations">
                <list>
                    <value>classpath:dataSource.properties</value>
                </list>
            </property>
        </bean>

    有了主从的连接之后,我们就可以写一个ConnectionFactory类,此类可以为外部类直接提供主数据库、从数据库的连接,相当于数据库连接的封装:

     

    1. @Service  
    2. public class ConnectionFactory {  
    3.   
    4.     @Autowired  
    5.     private DataSource masterDataSource;  
    6.   
    7.     @Autowired  
    8.     private DataSource slaveDataSource1;  
    9.   
    10.     @Autowired  
    11.     private DataSource slaveDataSource2;  
    12.   
    13.     private List<DataSource> slaveDataSourceList;  
    14.   
    15.     private int slaveDataSourceSize;  
    16.   
    17.   
    18.     @PostConstruct  
    19.     private void init() {  
    20.         slaveDataSourceList = new ArrayList<>();  
    21.         slaveDataSourceList.add(slaveDataSource1);  
    22.         slaveDataSourceList.add(slaveDataSource2);  
    23.   
    24.         slaveDataSourceSize = slaveDataSourceList.size();  
    25.     }  
    26.   
    27.   
    28.     /** 
    29.      * 得到主数据的连接 
    30.      */  
    31.     public Connection getMasterConnection() {  
    32.         return getConnection(masterDataSource);  
    33.     }  
    34.   
    35.     /** 
    36.      * 得到从数据库的连接数量 
    37.      */  
    38.     public int getSlaveDataSourceSize() {  
    39.         return slaveDataSourceSize;  
    40.     }  
    41.   
    42.     /** 
    43.      * 得到从数据n的连接 
    44.      */  
    45.     public Connection getSlaveConnection(int index){  
    46.         return getConnection(slaveDataSourceList.get(index));  
    47.     }  
    48.   
    49.   
    50.     private Connection getConnection(DataSource dataSource){  
    51.   
    52.         Connection connection = null;  
    53.         try {  
    54.             Class.forName(dataSource.getDriver());  
    55.   
    56.             connection = DriverManager.getConnection(dataSource.getUrl(), dataSource.getUser(), dataSource.getPassword());  
    57.         } catch (Exception e) {  
    58.             e.printStackTrace();  
    59.         }  
    60.         return connection;  
    61.     }  
    62. }  
    @Service
    public class ConnectionFactory {
    
        @Autowired
        private DataSource masterDataSource;
    
        @Autowired
        private DataSource slaveDataSource1;
    
        @Autowired
        private DataSource slaveDataSource2;
    
        private List<DataSource> slaveDataSourceList;
    
        private int slaveDataSourceSize;
    
    
        @PostConstruct
        private void init() {
            slaveDataSourceList = new ArrayList<>();
            slaveDataSourceList.add(slaveDataSource1);
            slaveDataSourceList.add(slaveDataSource2);
    
            slaveDataSourceSize = slaveDataSourceList.size();
        }
    
    
        /**
         * 得到主数据的连接
         */
        public Connection getMasterConnection() {
            return getConnection(masterDataSource);
        }
    
        /**
         * 得到从数据库的连接数量
         */
        public int getSlaveDataSourceSize() {
            return slaveDataSourceSize;
        }
    
        /**
         * 得到从数据n的连接
         */
        public Connection getSlaveConnection(int index){
            return getConnection(slaveDataSourceList.get(index));
        }
    
    
        private Connection getConnection(DataSource dataSource){
    
            Connection connection = null;
            try {
                Class.forName(dataSource.getDriver());
    
                connection = DriverManager.getConnection(dataSource.getUrl(), dataSource.getUser(), dataSource.getPassword());
            } catch (Exception e) {
                e.printStackTrace();
            }
            return connection;
        }
    }

    封装完成后,我们就可以使用getMasterConnection()直接得到主数据库的连接,使用getSlaveConnection(int)可以得到从数据库1或者是从数据2的连接。

     

    三、Proxy代理类的实现:

      代理类即是可以让程序中数据库访问得到正确的数据库连接,所以称为代理。

      1、使用ThreadLocal为当前线程指定数据库访问模式:

      由于Proxy不知道程序使用的是主数据库还是从数据库,所以程序在访问数据库之前要调用Proxy代理类来为当前线程打一个Tag,即指定是使用主数据库还是从数据库。由于而web服务器中每个请求是多线程环境,所以使用ThreadLocal类:

      2、使用随机法来访问从数据库:

      由于从数据库有多个,所以我们可以使用随机法来随机访问每个从数据库,随机法在高并发的情况下有很平均的分布,性能也非常好。

      3、具体实现:

    1. @Service  
    2. public class DataSourceProxy {  
    3.   
    4.     ThreadLocal<String> dataSourceThreadLocal = new ThreadLocal<>();  
    5.   
    6.     public static final String MASTER = "master";  
    7.     public static final String SLAVE = "slave";  
    8.   
    9.     @Resource  
    10.     private ConnectionFactory connectionFactory;  
    11.   
    12.     /** 
    13.      * 设置当前线程的数据库Mode 
    14.      */  
    15.     public void setMode(String dataMode) {  
    16.         dataSourceThreadLocal.set(dataMode);  
    17.     }  
    18.   
    19.     /** 
    20.      * 得到当前数据库Mode 
    21.      */  
    22.     public String getMode() {  
    23.         return dataSourceThreadLocal.get();  
    24.     }  
    25.   
    26.     /** 
    27.      * 根据当前Mode得到Connection连接对象 
    28.      */  
    29.     public Connection getThreadConnection() {  
    30.   
    31.         // 1.判断当前是从数据还是主数据库,默认是主数据库  
    32.         String mode = getMode();  
    33.         if (!StringUtils.isEmpty(mode) && SLAVE.equals(mode)) {  
    34.   
    35.             // y1.如果是从数据库,那么使用随机数的形式来得到从数据库连接  
    36.             double random = Math.random();  
    37.             int index = (int) (random * connectionFactory.getSlaveDataSourceSize());  
    38.   
    39.             System.out.println("----使用的为第" + (index + 1) + "从数据库----");  
    40.   
    41.             return connectionFactory.getSlaveConnection(index);  
    42.         } else {  
    43.   
    44.             System.out.println("----使用的为主数据库----");  
    45.   
    46.             // f1.如果是主数据库,因为只有一个,所以直接获取即可  
    47.             return connectionFactory.getMasterConnection();  
    48.         }  
    49.   
    50.     }  
    51.   
    52. }  
    @Service
    public class DataSourceProxy {
    
        ThreadLocal<String> dataSourceThreadLocal = new ThreadLocal<>();
    
        public static final String MASTER = "master";
        public static final String SLAVE = "slave";
    
        @Resource
        private ConnectionFactory connectionFactory;
    
        /**
         * 设置当前线程的数据库Mode
         */
        public void setMode(String dataMode) {
            dataSourceThreadLocal.set(dataMode);
        }
    
        /**
         * 得到当前数据库Mode
         */
        public String getMode() {
            return dataSourceThreadLocal.get();
        }
    
        /**
         * 根据当前Mode得到Connection连接对象
         */
        public Connection getThreadConnection() {
    
            // 1.判断当前是从数据还是主数据库,默认是主数据库
            String mode = getMode();
            if (!StringUtils.isEmpty(mode) && SLAVE.equals(mode)) {
    
                // y1.如果是从数据库,那么使用随机数的形式来得到从数据库连接
                double random = Math.random();
                int index = (int) (random * connectionFactory.getSlaveDataSourceSize());
    
                System.out.println("----使用的为第" + (index + 1) + "从数据库----");
    
                return connectionFactory.getSlaveConnection(index);
            } else {
    
                System.out.println("----使用的为主数据库----");
    
                // f1.如果是主数据库,因为只有一个,所以直接获取即可
                return connectionFactory.getMasterConnection();
            }
    
        }
    
    }

     

    4、此工程已在github上开源,可以完整实现数据库的读写分离,地址为:github 。如果觉得不错,那么就star一下来鼓励我吧。 

    转载于:https://www.cnblogs.com/firstdream/p/7851347.html

    展开全文
  • 数据库中间件的主要作用是向应用程序开发人员屏蔽读写分离和分库分表面临的挑战,并隐藏底层实现细节,使得开发人员可以像操作单库单表那样去操作数据。在介绍分库分表的主流设计方案前,我们首先回顾一下在单个库的...

    数据库中间件的主要作用是向应用程序开发人员屏蔽读写分离和分库分表面临的挑战,并隐藏底层实现细节,使得开发人员可以像操作单库单表那样去操作数据。在介绍分库分表的主流设计方案前,我们首先回顾一下在单个库的情况下,应用的架构,可以用下图进行描述:

    可以看到在操作单库单表的情况下,我们是直接在应用中通过数据源(c3p0、druid、dbcp等)与数据库建立连接,进行读写操作。而对于读写分离和分库分表,应用都要操作多个数据库实例,在这种情况下,我们就需要使用到数据库中间件。

    1 主流的数据库中间件设计方案

    典型的数据库中间件设计方案有2种:服务端代理(代理数据库)、客户端代理(代理数据源)。下图演示了这两种方案的架构:

    可以看到不论是代理数据库还是代理数据源,底层都操作了多个数据库实例。不同的是:

    在数据库代理中:

    我们独立部署一个代理服务,这个代理服务背后管理多个数据库实例。而在应用中,我们通过一个普通的数据源(c3p0、druid、dbcp等)与代理服务器建立连接,所有的sql操作语句都是发送给这个代理,由这个代理去操作底层数据库,得到结果并返回给应用。在这种方案下,分库分表和读写分离的逻辑对开发人员是完全透明的。

    在数据源代理中:

    应用程序需要使用一个特定的数据源,其作用是代理,内部管理了多个普通的数据源(c3p0、druid、dbcp等),每个普通数据源各自与不同的库建立连接。应用程序产生的sql交给数据源代理进行处理,数据源内部对sql进行必要的操作,如sql改写等,然后交给各个普通的数据源去执行,将得到的结果进行合并,返回给应用。数据源代理通常也实现了JDBC规范定义的API,因此能够直接与orm框架整合。

    2 主流的数据库中间件实现

    无论是代理数据库,还是代理数据源,二者的作用都是类似的。以下列出了这两种方案目前已有的实现以及各自的优缺点:

    数据库代理

    目前的实现方案有:阿里巴巴开源的cobar,mycat团队在cobar基础上开发的mycat,mysql官方提供的mysql-proxy,奇虎360在mysql-proxy基础开发的atlas。目前除了mycat,其他几个项目基本已经没有维护。

    优点:多语言支持。也就是说,不论你用的php、java或是其他语言,都可以支持。原因在于数据库代理本身就实现了mysql的通信协议,你可以就将其看成一个mysql 服务器。mysql官方团队为不同语言提供了不同的客户端却动,如java语言的mysql-connector-java,python语言的mysql-connector-python等等。因此不同语言的开发者都可以使用mysql官方提供的对应的驱动来与这个代理服务器建通信。

    缺点:实现复杂。因为代理服务器需要实现mysql服务端的通信协议,因此实现难度较大。

    数据源代理

    目前的实现方案有:阿里巴巴开源的tddl,大众点评开源的zebra,当当网开源的sharding-jdbc。需要注意的是tddl的开源版本只有读写分离功能,没有分库分表,且开源版本已经不再维护。大众点评的zebra开源版本代码已经很久更新,基本上处于停滞的状态。当当网的sharding-jdbc目前算是做的比较好的,代码时有更新,文档资料比较全。

    优点:更加轻量,可以与任何orm框架整合。这种方案不需要实现mysql的通信协议,因为底层管理的普通数据源,可以直接通过mysql-connector-java驱动与mysql服务器进行通信,因此实现相对简单。

    缺点:仅支持某一种语言。例如tddl、zebra、sharding-jdbc都是使用java语言开发,因此对于使用其他语言的用户,就无法使用这些中间件。版本升级困难,因为应用使用数据源代理就是引入一个jar包的依赖,在有多个应用都对某个版本的jar包产生依赖时,一旦这个版本有bug,所有的应用都需要升级。而数据库代理升级则相对容易,因为服务是单独部署的,只要升级这个代理服务器,所有连接到这个代理的应用自然也就相当于都升级了。

    ORM框架代理:

    目前有hibernate提供的hibernate-shards,也可以通过mybatis插件的方式编写。相对于前面两种方案,这种方案可以说是只有缺点,没有优点。

    Dragon项目采用的方案是代理客户端数据源的方式。

    展开全文
  • 数据库中间件

    2020-09-14 03:07:00
    不论是分库分表,还是读写分离,都是在数据库中间件层面对业务开发同学进行屏蔽 1.1.1 proxy模式 我们独立部署一个代理服务,这个代理服务背后管理多个数据库实例。而在应用中,我们通过一个普通的数据源(c3p0、d
  • 数据库中间件

    2019-03-29 21:44:43
    数据库中间件赋能IOT开发 * 为您省去协议服务器的开发,高性能RPC服务器可部署到私有云 * 采集数据直接入库 * 从云端获取远程指令、设备参数、业务规则、升级包 * 缩短一半以上的开发周期 云数据库中间件.....
  • 目前数据库中间件有很多,基本这些中间件在下都有了解和使用,各种中间件优缺点及使用场景也都有些心的。所以总结一个关于中间件比较的系列,希望可以对大家有帮助。 1. 什么是中间件 传统的架构模式就是 应用...
  • 数据库中间件mycat

    2016-01-27 09:50:31
    什么是MyCAT?简单的说,MyCAT就是: • 一个彻底开源的,面向企业应用开发的“大数据库集群” • 支持事务、ACID、可以替代Mysql的加强版数据库 • 一个可以视为“Mysql”集群的企业...• 一个新颖的数据库中间件产品
  • 例如,为了从Oracle数据库提取信息,开发人员可以调用面向数据库中间件来登录到数据库,请求信息并处理从数据库中提取的信息。尽管CLI是跨越多种数据库的通用API,通过定义良好的通用接口提供对任意数量的数据库...
  • 此外, 饿了么基于多语言(Java,Python,Go,NodeJS等)开发的现状导致迫切需要一个基于代理模式的数据库中间件(以下简称为DAL)。并以此实现连接复用熔断、限流、配置变更、分库分表等特性。由于所有SQL请求都会集中发往...
  • 本文根据dbaplus社群第183期线上分享整理而成,文末还有好书送哦~讲师介绍潘娟京东数科高级DBA主要负责京东数科数据库中间件开发、数据库运维自动化平台开发、生产数据库运维工作;多次参与京东6.18、11.11等大促...
  • Mycat数据库中间件

    2020-09-17 22:27:31
    1、什么是MyCat 1、一个彻底开源的,面向企业应用开发的大数据库集群 2、支持事务、ACID、可以替代MySQL的加强版数据库 3、一个可以视为MySQL集群的企业级数据库,用来替代昂贵的...6、一个新颖的数据库中间件产品
  • 为您提供CetusMySQL数据库中间件下载,Cetus是由C语言开发的关系型数据库MySQL的中间件,主要提供了一个全面的数据库访问代理功能。Cetus连接方式与MySQL基本兼容,应用程序几乎不用修改即可通过Cetus访问数据库,...
  • myCAT 是一个彻底开源的,面向企业应用开发的“大数据库集群” 支持... 一个新颖的数据库中间件产品。 一.什么是MyCat? MyCat是目前最流行的基于Java语言编写的数据库中间件,是一个实现了MySql协议的服务器,其核
  • 数据库中间件-jdbi

    2020-05-20 22:27:03
    Java 的数据库原生使用 jdbc,中间件有很多,例如说 hibernate、...数据库中间件就是节省开发人力,用很少的代码就可以快速开发。但是也会引入一些复杂度,特别是如果对中间件不熟悉或者中间件存在 bug,会导致更高.
  • MyCat是一个彻底开源的、面向企业应用开发的大数据库集群,是支持事务、ACID、可以替代MySQL的加强版数据库,是一个新颖的数据库中间件产品。随着互联网的高速发展,使得每一个线上的互联网应用都需要考虑高并发的...
  • 比较业界流行的MySQL分布式数据库中间件,关于每个产品的介绍,网上的资料比较多,本文只是对几款产品的架构进行比较,从中可以看出中间件发展和演进路线1 TDDL2 Amoeba3 Cobar4 MyCat总结1 TDDL不同于其它几款产品...
  • MyCat 是一个功能强大的分布式数据库中间件,是一个实现了 MySQL 协议的 Server,前端人员可以把它看做是一个数据库代理中间件,用 MySQL 客户端工具和命令行访问;而后端人员可以用 MySQL 原生协议与多个 MySQL ...
  • 目录 TOC \o "1-5" \h \z \o "Current Document" 产品客户化安装步骤 1 安装产品客户化模块 2 \o "Current Document" 安装Oracle数据库 5 配置Oracle数据库 9 配置Net服务名 9 \o "Current Document" 在Oracle数据库...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 3,551
精华内容 1,420
关键字:

数据库中间件开发