使用Spring AOP實現讀寫分離(MySql實現主從復制)


1.  背景

我們一般應用對數據庫而言都是“讀多寫少”,也就說對數據庫讀取數據的壓力比較大,有一個思路就是說采用數據庫集群的方案,
其中一個是主庫,負責寫入數據,我們稱之為:寫庫;

其它都是從庫,負責讀取數據,我們稱之為:讀庫;

那么,對我們的要求是:
1、讀庫和寫庫的數據一致;
2、寫數據必須寫到寫庫;
3、讀數據必須到讀庫;

2.  實現方案

解決讀寫分離的方案有兩種:應用層解決和中間件解決。

1.應用層

目前的一些解決方案需要在程序中手動指定數據源,比較麻煩,后邊我會通過AOP思想來解決這個問題

2,中間件

MySQL-proxy:http://hi.baidu.com/geshuai2008/item/0ded5389c685645f850fab07
Amoeba for MySQL:http://www.iteye.com/topic/188598和http://www.iteye.com/topic/1113437
 
此處我們介紹一種在應用層的解決方案,通過spring動態數據源和AOP來解決數據庫的讀寫分離。
 
該方案目前已經在一個互聯網項目中使用了,而且可以很好的工作。
 
該方案目前支持
一讀多寫;當寫時默認讀操作到寫庫、當寫時強制讀操作到讀庫。
 
考慮未來支持
讀庫負載均衡、讀庫故障轉移等。
 
使用場景
不想引入中間件,想在應用層解決讀寫分離,可以考慮這個方案;
建議數據訪問層使用jdbc、ibatis,不建議hibernate
 
優勢
應用層解決,不引入額外中間件;
在應用層支持『當寫時默認讀操作到寫庫』,這樣如果我們采用這種方案,在寫操作后讀數據直接從寫庫拿,不會產生數據復制的延遲問題;
應用層解決讀寫分離,理論支持任意數據庫。
 
 
缺點
1、不支持@Transactional注解事務,此方案要求所有讀方法必須是read-only=true,因此如果是@Transactional,這樣就要求在每一個讀方法頭上加@Transactional 且readOnly屬性=true,相當麻煩。 :oops: 
2、必須按照配置約定進行配置,不夠靈活

2.1.  應用層解決

 
優缺點:
 
優點:
1、源程序不需要做任何改動就可以實現讀寫分離;
2、動態添加數據源不需要重啟程序;
 
缺點:
1、程序依賴於中間件,會導致切換數據庫變得困難;
2、由中間件做了中轉代理,性能有所下降;
 
相關中間件產品使用:
MySQL-proxy:http://hi.baidu.com/geshuai2008/item/0ded5389c685645f850fab07
Amoeba for MySQL:http://www.iteye.com/topic/188598和http://www.iteye.com/topic/1113437
 

3.  使用Spring基於應用層實現

3.1. 原理

 
在進入Service之前,使用AOP來做出判斷,是使用寫庫還是讀庫,判斷依據可以根據方法名判斷,比如說以query、find、get等開頭的就走讀庫,其他的走寫庫

 

3.2. DynamicDataSource

動態改變數據源
在介紹實現方式之前,我們先准備一些必要的知識,spring 的AbstractRoutingDataSource 類

AbstractRoutingDataSource這個類 是spring2.0以后增加的,我們先來看下AbstractRoutingDataSource的定義:
public abstract class AbstractRoutingDataSource extends AbstractDataSource implements InitializingBean  {}
AbstractRoutingDataSource繼承了AbstractDataSource ,而AbstractDataSource 又是DataSource 的子類。DataSource

3.3. DynamicDataSourceHolder

使用ThreadLocal技術來記錄當前線程中的數據源的key 

 

5.  一主多從的實現

很多實際使用場景下都是采用“一主多從”的架構的,所有我們現在對這種架構做支持,目前只需要修改DynamicDataSource即可。
 

6.  MySQL主從復制

6.1. 原理

mysql主(稱master)從(稱slave)復制的原理:
1、master將數據改變記錄到二進制日志(binarylog)中,也即是配置文件log-bin指定的文件(這些記錄叫做二進制日志事件,binary log events)
2、slave將master的binary logevents拷貝到它的中繼日志(relay log)
3、slave重做中繼日志中的事件,將改變反映它自己的數據(數據重演)

6.2. 主從配置需要注意的地方

1、主DB server和從DB server數據庫的版本一致
2、主DB server和從DB server數據庫數據一致[ 這里就會可以把主的備份在從上還原,也可以直接將主的數據目錄拷貝到從的相應數據目錄]
3、主DB server開啟二進制日志,主DB server和從DB server的server_id都必須唯一
 

6.3. 主庫配置(windows,Linux下也類似)

第一步,主服務器創建用戶並清空日志

mysql> show privileges;
mysql> grant replication client, replication slave on *.* to 'larry'@'192.168.1.%' identified by 'larry';
mysql> show binary logs;
mysql> reset master;
mysql> show binary logs;
+------------------+-----------+
| Log_name         | File_size |
+------------------+-----------+
| mysql-bin.000001 |       107 |
+------------------+-----------+
1 row in set (0.00 sec)

第二步,修改從服務器的server-id,把server-id改為2然后重啟mysql

[root@serv08 ~]# cat /etc/my.cnf | grep server-id
server-id = 1
#server-id       = 2
[root@serv08 ~]# vim /etc/my.cnf 
[root@serv08 ~]# cat /etc/my.cnf | grep server-id
server-id = 2
#server-id       = 2
[root@serv08 ~]# /etc/init.d/mysqld restart

第三步,從服務器清空日志

mysql> show binary logs;
mysql> reset master;    
mysql> show binary logs;
+------------------+-----------+
| Log_name         | File_size |
+------------------+-----------+
| mysql-bin.000001 |       107 |
+------------------+-----------+
1 row in set (0.00 sec)

mysql> show slave status;
Empty set (0.00 sec)

第四步,從服務器通過change master to命令修改設置

mysql> change master to
    -> master_host='192.168.1.11',
    -> master_user='larry',
    -> master_password='larry',
    -> master_port=3306,
    -> master_log_file='mysql-bin.000001',
    -> master_log_pos=107;
Query OK, 0 rows affected (0.01 sec)

第五步,開啟slave。

mysql> slave start;
mysql>show slave status ;
mysql>show slave status \G;

第六步,從服務器查看是否和主服務器通信成功。如果出現 Slave_IO_Running和Slave_SQL_Running都是yes,則證明配置成功

*************************** 1. row ***************************
               Slave_IO_State: Waiting for master to send event
                  Master_Host: 192.168.1.11
                  Master_User: larry
                  Master_Port: 3306
                Connect_Retry: 60
              Master_Log_File: mysql-bin.000001
          Read_Master_Log_Pos: 107
               Relay_Log_File: serv08-relay-bin.000002
                Relay_Log_Pos: 253
        Relay_Master_Log_File: mysql-bin.000001
             Slave_IO_Running: Yes Slave_SQL_Running: Yes
              Replicate_Do_DB: 
          Replicate_Ignore_DB: 
           Replicate_Do_Table: 
       Replicate_Ignore_Table: 
      Replicate_Wild_Do_Table: 
  Replicate_Wild_Ignore_Table: 
                   Last_Errno: 0
                   Last_Error: 
                 Skip_Counter: 0
          Exec_Master_Log_Pos: 107
              Relay_Log_Space: 410
              Until_Condition: None
               Until_Log_File: 
                Until_Log_Pos: 0
           Master_SSL_Allowed: No
           Master_SSL_CA_File: 
           Master_SSL_CA_Path: 
              Master_SSL_Cert: 
            Master_SSL_Cipher: 
               Master_SSL_Key: 
        Seconds_Behind_Master: 0
Master_SSL_Verify_Server_Cert: No
                Last_IO_Errno: 0
                Last_IO_Error: 
               Last_SQL_Errno: 0
               Last_SQL_Error: 
  Replicate_Ignore_Server_Ids: 
             Master_Server_Id: 1
1 row in set (0.00 sec)

ERROR: 
No query specified

第八步,測試

--slave查看數據庫
mysql> show databases;
+--------------------+
| Database           |
+--------------------+
| information_schema |
| mysql              |
| performance_schema |
| test               |
+--------------------+
4 rows in set (0.02 sec)

--master創建數據庫
mysql> create database larrydb;
Query OK, 1 row affected (0.00 sec)
--master查看數據庫
mysql> show databases;
+--------------------+
| Database           |
+--------------------+
| information_schema |
| larrydb            |
| mysql              |
| performance_schema |
| test               |
+--------------------+
5 rows in set (0.01 sec)

--slave查看數據庫,發現已經同步
mysql> show databases;
+--------------------+
| Database           |
+--------------------+
| information_schema |
| larrydb            |
| mysql              |
| performance_schema |
| test               |
+--------------------+
5 rows in set (0.00 sec)

--master創建表 插入數據
mysql> use larrydb;
Database changed
mysql> create table test(id int(11));
Query OK, 0 rows affected (0.00 sec)

mysql> insert into test values(1);
Query OK, 1 row affected (0.00 sec)

--slave查看數據是否同步成功,發現數據已經同步
mysql> use larrydb;
Database changed
mysql> show tables;
+-------------------+
| Tables_in_larrydb |
+-------------------+
| test              |
+-------------------+
1 row in set (0.00 sec)

mysql> select * from test;
+------+
| id   |
+------+
|    1 |
+------+
1 row in set (0.00 sec)

第九步,查看進程狀態

--master查看進程狀態
mysql> show processlist;
+----+-------+--------------------+---------+-------------+------+-----------------------------------------------------------------------+------------------+
| Id | User  | Host               | db      | Command     | Time | State                                                                 | Info             |
+----+-------+--------------------+---------+-------------+------+-----------------------------------------------------------------------+------------------+
|  1 | root  | localhost          | larrydb | Query       |    0 | NULL                                                                  | show processlist |
|  2 | larry | 192.168.1.18:41393 | NULL    | Binlog Dump |  854 | Master has sent all binlog to slave; waiting for binlog to be updated | NULL             |
+----+-------+--------------------+---------+-------------+------+-----------------------------------------------------------------------+------------------+
2 rows in set (0.00 sec)

--slave查看進程狀態
mysql> show processlist;
+----+-------------+-----------+---------+---------+------+-----------------------------------------------------------------------------+------------------+
| Id | User        | Host      | db      | Command | Time | State                                                                       | Info             |
+----+-------------+-----------+---------+---------+------+-----------------------------------------------------------------------------+------------------+
|  1 | root        | localhost | larrydb | Query   |    0 | NULL                                                                        | show processlist |
|  2 | system user |           | NULL    | Connect |  880 | Waiting for master to send event                                            | NULL             |
|  3 | system user |           | NULL    | Connect |   65 | Slave has read all relay log; waiting for the slave I/O thread to update it | NULL             |
+----+-------------+-----------+---------+---------+------+-----------------------------------------------------------------------------+------------------+
3 rows in set (0.00 sec)

備注:如果需要制定同步的數據庫,需要修改slave從庫 /etc/my.cnf 這個mysql配置文件。

# vim /etc/my.cnf 

增加兩端語句

binlog-do-db = 同步的數據
binlog-ignore-db = mysql,information_schema

需要注意的地方

1、兩個數據庫的版本和數據應該保持一致

2、對應的端口,防火牆應該開啟

Spring項目源碼 https://github.com/jiafuweiJava/MasterSlave

數據庫學習地址:http://blog.csdn.net/justdb/article/details/13168569

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM