zoukankan      html  css  js  c++  java
  • 从零开发分布式数据库中间件 一、读写分离的数据库中间件

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

    一、主从数据库介绍:

      主从数据库即为一个主数据库会有对应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一下来鼓励我吧。 

  • 相关阅读:
    WebView.自动登录
    Android.对话框(AlertDialog/Toast/Snackbar)
    ubuntu解压rar
    sqlserver2005 存储过程模板及调用
    win7 32位下装oracle 10g报未知错误
    oracle下常用查询更新命令(身份证号判断男女,更新语句多表查询)
    如何建立一个android工程
    ubuntu14.04 配置android及sdk等相关操作
    mysql 常用简单的几个命令
    linux的tar简单使用
  • 原文地址:https://www.cnblogs.com/firstdream/p/7851347.html
Copyright © 2011-2022 走看看