從零開發分布式數據庫中間件 一、讀寫分離的數據庫中間件(轉)
從零開發分布式數據庫中間件 一、讀寫分離的數據庫中間件
在傳統的單機體系中,我們在操作數據庫時,只需要直接得到數據庫的連接,然後操作數據庫即可,可是在現在的數據爆炸時代,只靠單機是無法承載如此大的用戶量的,即我們不能縱向擴展,那麽我們就只能水平進行擴展,即使用讀寫分離的主從數據庫來緩解數據庫的壓力,而在讀寫分離之後,如何使程序能正確的得到主數據庫的連接或者是從數據庫的連接,就是我們今天讀寫分離的數據庫中間件需要實現的。
一、主從數據庫介紹:
主從數據庫即為一個主數據庫會有對應n個從數據庫,而從數據庫只能有一個對應的從數據庫。主從數據庫中寫的操作需要使用主數據庫,而讀操作使用從數據庫。主數據庫與從數據庫始終保持數據一致性。其中保持數據庫一致的原理即為當主數據庫數據發生變化時,會將操作寫入到主數據庫日誌中,而從數據庫會不停的讀取主數據庫的日誌保存到自己的日誌系統中,然後進行執行,從而保持了主從數據庫一致。
二、開發前準備及ConnectionFactory類的開發:
在了解了主從數據庫後,我們可以進行分布式數據庫中間件的開發,由於mysql本身支持主從數據庫,但限於篇幅,就不講mysql的主從配置了,我們先使用本機的mysql作為一主兩從的數據庫源即可,下面是我本機的數據庫連接配置文件,其中有一主兩從:
[java] view plain copy print?- 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類:
[java] view plain copy print?- 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;
- }
- }
- <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>
[java] view plain copy print?
- @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;
- }
- }
三、Proxy代理類的實現:
代理類即是可以讓程序中數據庫訪問得到正確的數據庫連接,所以稱為代理。
1、使用ThreadLocal為當前線程指定數據庫訪問模式:
由於Proxy不知道程序使用的是主數據庫還是從數據庫,所以程序在訪問數據庫之前要調用Proxy代理類來為當前線程打一個Tag,即指定是使用主數據庫還是從數據庫。由於而web服務器中每個請求是多線程環境,所以使用ThreadLocal類:
2、使用隨機法來訪問從數據庫:
由於從數據庫有多個,所以我們可以使用隨機法來隨機訪問每個從數據庫,隨機法在高並發的情況下有很平均的分布,性能也非常好。
3、具體實現:
[java] view plain copy print?- @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一下來鼓勵我吧。
從零開發分布式數據庫中間件 一、讀寫分離的數據庫中間件(轉)