博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
从零开发分布式数据库中间件 一、读写分离的数据库中间件
阅读量:5868 次
发布时间:2019-06-19

本文共 9218 字,大约阅读时间需要 30 分钟。

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

一、主从数据库介绍:

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

 

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

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

 

[java]
  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.Drivermaster.dburl=jdbc\:mysql\://127.0.0.1\:3306/master_slave_dbmaster.user=rootmaster.password=mytestconslave1.driver=com.mysql.jdbc.Driverslave1.dburl=jdbc\:mysql\://127.0.0.1\:3306/master_slave_dbslave1.user=rootslave1.password=mytestconslave2.driver=com.mysql.jdbc.Driverslave2.dburl=jdbc\:mysql\://127.0.0.1\:3306/master_slave_dbslave2.user=rootslave2.password=mytestcon

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

 

封装的DataSource类:

 

[java]
  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类:

[java]
  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>  
classpath:dataSource.properties

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

 

[java]
  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. }  
@Servicepublic class ConnectionFactory {    @Autowired    private DataSource masterDataSource;    @Autowired    private DataSource slaveDataSource1;    @Autowired    private DataSource slaveDataSource2;    private List
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、具体实现:

[java]
  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. }  
@Servicepublic class DataSourceProxy {    ThreadLocal
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上开源,可以完整实现数据库的读写分离,地址为: 。如果觉得不错,那么就star一下来鼓励我吧。 

转载地址:http://ietnx.baihongyu.com/

你可能感兴趣的文章
centos桌面使用
查看>>
2.Struts2-Action
查看>>
Android第四次作业
查看>>
Python 3 进阶 —— 使用 PyMySQL 操作 MySQL
查看>>
ACCEPTANCE CRITERIA FOR USER STORIES
查看>>
线性方程组的迭代求解算法——原理
查看>>
loadrunner关联及web_reg_save_param方法浅析
查看>>
初识DJango——Web框架
查看>>
php动态获取网页图片路径~
查看>>
28、可变参数和集合数组的互转
查看>>
[AX]AX2012 Form开发概览
查看>>
[AX]AX2012 Number sequence framework :(一)概览与原理浅析
查看>>
小程序单图上传到服务器
查看>>
iOS开发 - OC - 实现本地数据存储的几种方式一
查看>>
查看tomcat,jdk的操作位数
查看>>
C语言中的指针
查看>>
c51较c比较,单片机最小系统
查看>>
模式化窗口问题![window.dialogArguments]
查看>>
数据结构 - 二叉树(重构 + 遍历)
查看>>
四则运算程序
查看>>