1. 程式人生 > >從零開發分布式數據庫中間件 一、讀寫分離的數據庫中間件(轉)

從零開發分布式數據庫中間件 一、讀寫分離的數據庫中間件(轉)

mark str 日誌系統 arraylist none views gpo arr 體系

從零開發分布式數據庫中間件 一、讀寫分離的數據庫中間件

在傳統的單機體系中,我們在操作數據庫時,只需要直接得到數據庫的連接,然後操作數據庫即可,可是在現在的數據爆炸時代,只靠單機是無法承載如此大的用戶量的,即我們不能縱向擴展,那麽我們就只能水平進行擴展,即使用讀寫分離的主從數據庫來緩解數據庫的壓力,而在讀寫分離之後,如何使程序能正確的得到主數據庫的連接或者是從數據庫的連接,就是我們今天讀寫分離的數據庫中間件需要實現的。

一、主從數據庫介紹:

主從數據庫即為一個主數據庫會有對應n個從數據庫,而從數據庫只能有一個對應的從數據庫。主從數據庫中寫的操作需要使用主數據庫,而讀操作使用從數據庫。主數據庫與從數據庫始終保持數據一致性。其中保持數據庫一致的原理即為當主數據庫數據發生變化時,會將操作寫入到主數據庫日誌中,而從數據庫會不停的讀取主數據庫的日誌保存到自己的日誌系統中,然後進行執行,從而保持了主從數據庫一致。


二、開發前準備及ConnectionFactory類的開發:

在了解了主從數據庫後,我們可以進行分布式數據庫中間件的開發,由於mysql本身支持主從數據庫,但限於篇幅,就不講mysql的主從配置了,我們先使用本機的mysql作為一主兩從的數據庫源即可,下面是我本機的數據庫連接配置文件,其中有一主兩從:

[java] view plain copy print?
  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. slave1.driver=com.mysql.jdbc.Driver
  6. slave1.dburl=jdbc\:mysql\://127.0.0.1\:3306/master_slave_db
  7. slave1.user=root
  8. slave1.password=mytestcon
  9. slave2.driver=com.mysql.jdbc.Driver
  10. slave2.dburl=jdbc\:mysql\://127.0.0.1\:3306/master_slave_db
  11. slave2.user=root
  12. slave2.password=mytestcon
有了主從數據庫的連接配置後,就可以將配置進行封裝。

封裝的DataSource類:

[java] view plain copy print?
  1. public class DataSource {
  2. private String driver;
  3. private String url;
  4. private String user;
  5. private String password;
  6. public String getDriver() {
  7. return driver;
  8. }
  9. public void setDriver(String driver) {
  10. this.driver = driver;
  11. }
  12. public String getUrl() {
  13. return url;
  14. }
  15. public void setUrl(String url) {
  16. this.url = url;
  17. }
  18. public String getUser() {
  19. return user;
  20. }
  21. public void setUser(String user) {
  22. this.user = user;
  23. }
  24. public String getPassword() {
  25. return password;
  26. }
  27. public void setPassword(String password) {
  28. this.password = password;
  29. }
  30. }
在Spring配置文件中,從配置文件中讀取配置並將配置轉換為封裝的DataSource類:[java] view plain copy print?
  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. <bean id="slaveDataSource1" class="com.happyheng.connection.DataSource">
  8. <property name="driver" value="${slave1.driver}"/>
  9. <property name="url" value="${slave1.dburl}"/>
  10. <property name="user" value="${slave1.user}"/>
  11. <property name="password" value="${slave1.password}"/>
  12. </bean>
  13. <bean id="slaveDataSource2" class="com.happyheng.connection.DataSource">
  14. <property name="driver" value="${slave2.driver}"/>
  15. <property name="url" value="${slave2.dburl}"/>
  16. <property name="user" value="${slave2.user}"/>
  17. <property name="password" value="${slave2.password}"/>
  18. </bean>
  19. <bean id="propertyConfigurer"
  20. class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
  21. <property name="locations">
  22. <list>
  23. <value>classpath:dataSource.properties</value>
  24. </list>
  25. </property>
  26. </bean>
有了主從的連接之後,我們就可以寫一個ConnectionFactory類,此類可以為外部類直接提供主數據庫、從數據庫的連接,相當於數據庫連接的封裝:

[java] view plain copy print?
  1. @Service
  2. public class ConnectionFactory {
  3. @Autowired
  4. private DataSource masterDataSource;
  5. @Autowired
  6. private DataSource slaveDataSource1;
  7. @Autowired
  8. private DataSource slaveDataSource2;
  9. private List<DataSource> slaveDataSourceList;
  10. private int slaveDataSourceSize;
  11. @PostConstruct
  12. private void init() {
  13. slaveDataSourceList = new ArrayList<>();
  14. slaveDataSourceList.add(slaveDataSource1);
  15. slaveDataSourceList.add(slaveDataSource2);
  16. slaveDataSourceSize = slaveDataSourceList.size();
  17. }
  18. /**
  19. * 得到主數據的連接
  20. */
  21. public Connection getMasterConnection() {
  22. return getConnection(masterDataSource);
  23. }
  24. /**
  25. * 得到從數據庫的連接數量
  26. */
  27. public int getSlaveDataSourceSize() {
  28. return slaveDataSourceSize;
  29. }
  30. /**
  31. * 得到從數據n的連接
  32. */
  33. public Connection getSlaveConnection(int index){
  34. return getConnection(slaveDataSourceList.get(index));
  35. }
  36. private Connection getConnection(DataSource dataSource){
  37. Connection connection = null;
  38. try {
  39. Class.forName(dataSource.getDriver());
  40. connection = DriverManager.getConnection(dataSource.getUrl(), dataSource.getUser(), dataSource.getPassword());
  41. } catch (Exception e) {
  42. e.printStackTrace();
  43. }
  44. return connection;
  45. }
  46. }
封裝完成後,我們就可以使用getMasterConnection()直接得到主數據庫的連接,使用getSlaveConnection(int)可以得到從數據庫1或者是從數據2的連接。


三、Proxy代理類的實現:

代理類即是可以讓程序中數據庫訪問得到正確的數據庫連接,所以稱為代理。

1、使用ThreadLocal為當前線程指定數據庫訪問模式:

由於Proxy不知道程序使用的是主數據庫還是從數據庫,所以程序在訪問數據庫之前要調用Proxy代理類來為當前線程打一個Tag,即指定是使用主數據庫還是從數據庫。由於而web服務器中每個請求是多線程環境,所以使用ThreadLocal類:

2、使用隨機法來訪問從數據庫:

由於從數據庫有多個,所以我們可以使用隨機法來隨機訪問每個從數據庫,隨機法在高並發的情況下有很平均的分布,性能也非常好。

3、具體實現:

[java] view plain copy print?
  1. @Service
  2. public class DataSourceProxy {
  3. ThreadLocal<String> dataSourceThreadLocal = new ThreadLocal<>();
  4. public static final String MASTER = "master";
  5. public static final String SLAVE = "slave";
  6. @Resource
  7. private ConnectionFactory connectionFactory;
  8. /**
  9. * 設置當前線程的數據庫Mode
  10. */
  11. public void setMode(String dataMode) {
  12. dataSourceThreadLocal.set(dataMode);
  13. }
  14. /**
  15. * 得到當前數據庫Mode
  16. */
  17. public String getMode() {
  18. return dataSourceThreadLocal.get();
  19. }
  20. /**
  21. * 根據當前Mode得到Connection連接對象
  22. */
  23. public Connection getThreadConnection() {
  24. // 1.判斷當前是從數據還是主數據庫,默認是主數據庫
  25. String mode = getMode();
  26. if (!StringUtils.isEmpty(mode) && SLAVE.equals(mode)) {
  27. // y1.如果是從數據庫,那麽使用隨機數的形式來得到從數據庫連接
  28. double random = Math.random();
  29. int index = (int) (random * connectionFactory.getSlaveDataSourceSize());
  30. System.out.println("----使用的為第" + (index + 1) + "從數據庫----");
  31. return connectionFactory.getSlaveConnection(index);
  32. } else {
  33. System.out.println("----使用的為主數據庫----");
  34. // f1.如果是主數據庫,因為只有一個,所以直接獲取即可
  35. return connectionFactory.getMasterConnection();
  36. }
  37. }
  38. }

4、此工程已在github上開源,可以完整實現數據庫的讀寫分離,地址為:github 。如果覺得不錯,那麽就star一下來鼓勵我吧。


從零開發分布式數據庫中間件 一、讀寫分離的數據庫中間件(轉)