1. 讀寫分離&讀寫分離 簡介

  • 主從同步延遲
  • 分配機制
  • 解決單點故障
  • 總結

2. 主從複製&讀寫分離 搭建

  • 搭建主從複製(雙主)
  • 搭建讀寫分離

1. 讀寫分離&讀寫分離 簡介

隨著使用者和資料的增多,單機的資料庫往往支撐不住快速發展的業務,所以資料庫叢集就產生了!

讀寫分離顧名思義就是讀和寫分離,對應到資料庫叢集一般都是一主一從(一個主庫,一個從庫)或者一主多從(一個主庫,多個從庫),業務伺服器把需要寫的操作都寫到主資料庫中,讀的操作都去從庫查詢。主庫會同步資料到從庫保證資料的一致性。

這種叢集方式的本質是把訪問的壓力從主庫轉移到從庫也就是在單機資料庫無法支撐併發讀寫,並且讀的請求很多的情況下適合這種讀寫分離的資料庫叢集。如果寫的操作很多的話不適合這種叢集方式,因為你的資料庫壓力還是在寫操作上,即使主從了之後壓力還是在主庫上,這樣和單機的區別就不大了。

在單機的情況下,一般我們做資料庫優化都會加索引,但是加了索引對查詢有優化,但會影響寫入,因為寫入資料會更新索引。所以做了主從之後,我們可以單獨地針對從庫(讀庫)做索引上的優化,而主庫(寫庫)可以減少索引而提高寫的效率。

還有兩點要注意:主從同步延遲、分配機制的考慮。

主從同步延遲

主庫有資料寫入之後,同時也寫入在binlog(二進位制日誌檔案)中,從庫是通過 binlog 檔案來同步資料的,這期間會有一定時間的延遲,可能是 1 秒,如果同時有大量資料寫入的話,時間可能更長。

這會導致什麼問題呢?比如有一個付款操作,你付款了,主庫是已經寫入資料,但是查詢是到從庫查,從庫裡還沒有你的付款記錄,所以頁面上查詢的時候你還沒付款。那可不急眼了啊,吞錢了這還了得!打電話給客服投訴!

所以為了解決主從同步延遲的問題有以下幾個方法:

1)二次讀取

二次讀取的意思就是讀從庫沒讀到之後再去主庫讀一下,只要通過對資料庫訪問的 API 進行封裝就能實現這個功能。很簡單,並且和業務之間沒有耦合。但是有個問題,如果有很多二次讀取相當於壓力還是回到了主庫身上,等於讀寫分離白分了。而且如有人惡意攻擊,就一直訪問沒有的資料,那主庫就可能爆了。

2)寫之後的馬上的讀操作訪問主庫

也就是寫操作之後,立馬的讀操作指定為訪問主庫,之後的讀操作則訪問從庫。這就等於寫死了,和業務強耦合了。

3)關鍵業務讀寫都由主庫承擔,非關鍵業務讀寫分離

類似付錢的這種業務,讀寫都到主庫,避免延遲的問題,但是例如改個頭像啊,個人簽名這種比較不重要的就讀寫分離,查詢都去從庫查,畢竟延遲一下影響也不大,不會立馬打客服電話投訴。

分配機制

分配機制的考慮也就是怎麼制定寫操作是去主庫寫,讀操作是去從庫讀。

一般有兩種方式:程式碼封裝、資料庫中介軟體。

1)程式碼封裝

程式碼封裝的實現很簡單,就是抽出一箇中間層,讓這個中間層來實現讀寫分離和資料庫連線。講白點就是搞個 provider 封裝了 save、select 等通常資料庫操作,內部 save 操作的 dataSource 是主庫的,select 操作的 dataSource 是從庫的。

優點:

  1. 實現簡單。
  2. 可以根據業務定製化變化,隨心所欲。

缺點:

  1. 如果哪個資料庫宕機了,發生主從切換了之後,就得修改配置重啟。
  2. 如果系統很大,一個業務可能包含多個子系統,一個子系統是 java 寫的,一個子系統用 go 寫的,這樣的話得分別為不同語言實現一套中間層,重複開發。

2)資料庫中介軟體

就是有一個獨立的系統,專門來實現讀寫分離和資料庫連線管理,業務伺服器和資料庫中介軟體之間是通過標準的 SQL 協議交流的,所以在業務伺服器看來資料庫中介軟體其實就是個資料庫。

優點:

  1. 因為是通過 SQL 協議的所以可以相容不同的語言不需要單獨寫一套。
  2. 由中介軟體來實現主從切換,業務伺服器不需要關心這點。

缺點:

  1. 多了一個系統其實就等於多了一個關心,比如資料庫中介軟體掛了。
  2. 多了一個系統就等於多了一個瓶頸,所以對中介軟體的效能要求也高,因為所有的資料庫操作都要先經過它。
  3. 中介軟體實現較為複雜,難度比程式碼封裝高多了。

常用的開源資料庫中介軟體有 Mysql Proxy、Atlas、LVS 等。

為什麼使用 MySQL-Proxy 而不是 LVS?

  • LVS:分不清讀還是寫;不支援事務。
  • MySQL-Proxy:自動區分讀操作和寫操作;支援事務(注意在 MySQL-Proxy 中不要使用巢狀查詢,否則會造成讀和寫的混亂)。

解決單點故障

解決 Proxy 單點故障問題

MySQL-Proxy 實際上非常不穩定,在高併發或有錯誤連線的情況下,程序很容易自動關閉,因此開啟 --keepalive 引數讓程序自動恢復是個比較好的辦法,但還是不能從根本上解決問題,通常最穩妥的做法是在每個應用伺服器(如 Tomcat)上安裝一個 MySQL-Proxy 供自身使用(解決 Proxy 單點故障問題),雖然比較低效但卻能保證穩定性。

雙(多)主機制

Proxy 之後搭 LVS,LVS 為兩臺主資料庫做負載均衡,從資料庫從兩臺主資料庫同步。此方案旨在解決:

  • 主資料庫的單點故障問題(服務不可用、備份問題)
  • 分擔寫操作的訪問壓力。

不過多主需要考慮自增長 ID 問題,這個需要特別設定配置檔案,比如雙主可以使用奇偶。總之,主之間設定自增長 ID 相互不衝突就能解決自增長 ID 衝突問題。

總結

讀寫分離相對而言是比較簡單的,比分表分庫簡單,但是它只能分擔訪問的壓力,分擔不了儲存的壓力,也就是你的資料庫表的資料逐漸增多,但是面對一張表海量的資料,查詢還是很慢的,所以如果業務發展的快資料暴增,到一定時間還是得分庫分表。

正常情況下,只有當單機真的頂不住壓力了才會叢集,不要一上來就叢集,沒這個必要。有關於軟體的東西都是越簡單越好,複雜都是形勢所迫。

一般我們是先優化單機,如優化一些慢查詢,優化業務邏輯的呼叫或者加入快取等。如果真的優化到沒東西優化了,然後才上叢集。先讀寫分離,讀寫分離之後頂不住就再分庫分表。

2. 主從複製&讀寫分離 搭建

主從同步的方式也分很多種,一主多從、鏈式主從、多主多從,根據你的需要來進行設定。

搭建主從複製(雙主)

1)伺服器二臺:分別安裝 MySQL 資料庫

  1. 安裝命令:yum -y install mysql-server
  2. 配置登入使用者的密碼:mysqladmin -u root password '密碼'
  3. 配置允許第三方機器訪問本機 MySQL:
  1. delete from user where password = '';
  2. update user set host='%';
  3. flush privileges;

2)分別修改 MySQL 配置

配置 masterA:

  1. [root@adailinux ~]# vim /etc/my.cnf
  2. [mysqld]
  3. datadir = /data/mysql
  4. socket = /tmp/mysql.sock
  5. server_id = 1 # 指定server-id,必須保證主從伺服器的server-id不同
  6. auto_increment_increment = 2 # 設定主鍵單次增量
  7. auto_increment_offset = 1 # 設定單次增量中主鍵的偏移量:奇數
  8. log_bin = mysql-bin # 建立主從需要開啟log-bin日誌檔案
  9. log-slave-updates # 把更新的日誌寫到二進位制檔案(binlog)中,臺伺服器既做主庫又做從庫此選項必須要開啟

配置 masterB:

  1. [root@adailinux ~]# vim /etc/my.cnf
  2. [mysqld]
  3. datadir = /data/mysql
  4. socket = /tmp/mysql.sock
  5. server_id = 2 # 指定server-id,必須保證主從伺服器的server-id不同
  6. auto_increment_increment = 2 # 設定主鍵單次增量
  7. auto_increment_offset = 2 # 設定單次增量中主鍵的偏移量:偶數
  8. log_bin = mysql-bin # 建立主從需要開啟log-bin日誌檔案
  9. log-slave-updates = True # 把更新的日誌寫到二進位制檔案(binlog)中

以上為同步配置的核心引數。

server_id 有兩個用途:

  1. 用來標記 binlog event 的源產地,就是 SQL 語句最開始源自於哪裡。
  2. 用於 IO_thread 對主庫 binlog 的過濾。如果沒有設定 replicate-same-server-id=1 ,那麼當從庫的 IO_thread 發現 event 的源與自己的 server-id 相同時,就會跳過該 event,不把該 event 寫入到 relay log 中。從庫的 sql_thread 自然就不會執行該 event。這在鏈式或雙主結構中可以避免 sql 語句的無限迴圈。

注意:如果設定多個從伺服器,每個從伺服器必須有一個唯一的 server-id 值,且與主伺服器的以及其它從伺服器的都不相同。

分別重啟 masterA 和 masterB 並檢視主庫狀態:

  1. [root@adailinux ~]# /etc/init.d/mysqld restart
  2. Shutting down MySQL.. SUCCESS!
  3. Starting MySQL. SUCCESS!
  4.  
  5. masterA
  6. [root@adailinux ~]# mysql -uroot
  7. mysql> show master status;
  8. +------------------+----------+--------------+------------------+-------------------+
  9. | File | Position | Binlog_Do_DB | Binlog_Ignore_DB | Executed_Gtid_Set |
  10. +------------------+----------+--------------+------------------+-------------------+
  11. | mysql-bin.000001 | 419 | TSC | mysql | |
  12. +------------------+----------+--------------+------------------+-------------------+
  13. 1 row in set (0.00 sec)
  14.  
  15. masterB
  16. [root@adailinux ~]# mysql -uroot
  17. mysql> show master status
  18. -> ;
  19. +------------------+----------+--------------+------------------+-------------------+
  20. | File | Position | Binlog_Do_DB | Binlog_Ignore_DB | Executed_Gtid_Set |
  21. +------------------+----------+--------------+------------------+-------------------+
  22. | mysql-bin.000001 | 419 | TSC | mysql | |
  23. +------------------+----------+--------------+------------------+-------------------+
  24. 1 row in set (0.00 sec)

3)配置同步資訊

masterA:

  1. [root@adailinux ~]# mysql -uroot
  2. mysql> change master to master_host='192.168.8.132',master_port=3306,master_user='repl',master_password='123456',master_log_file='mysql-bin.000001',master_log_pos=419;
  3. #注:IP為masterB的IP(即,從伺服器的IP)
  4. mysql> start slave;
  5. Query OK, 0 rows affected (0.05 sec)
  6.  
  7. mysql> show slave status\G;
  8. 在此檢視有如下狀態說明配置成功:
  9. Slave_IO_Running: Yes
  10. Slave_SQL_Running: Yes

masterB:

  1. [root@adailinux ~]# mysql -uroot
  2. mysql>change master to master_host='192.168.8.131',master_port=3306,master_user='repl',master_password='123456',master_log_file='mysql-bin.000001',master_log_pos=419;
  3. Query OK, 0 rows affected, 2 warnings (0.06 sec)
  4.  
  5. mysql> start slave;
  6. Query OK, 0 rows affected (0.04 sec)
  7.  
  8. mysql> show slave status\G
  9. 在此檢視有如下狀態說明配置成功:
  10. Slave_IO_Running: Yes
  11. Slave_SQL_Running: Yes

4)測試主從同步

在 masterA 上建立一個庫,驗證 masterB 是否同步建立了該庫。

搭建讀寫分離

場景描述:

  • 資料庫 Master 主伺服器
  • 資料庫 Slave 從伺服器
  • MySQL-Proxy 排程伺服器

以下操作,均是在 MySQL-Proxy 排程伺服器上進行的。

1)MySQL 伺服器安裝

2)檢查/安裝系統所需軟體包

  1. yum -y install gcc* gcc-c++* autoconf* automake* zlib* libxml* ncurses-devel* libmcrypt* libtool* flex* pkgconfig* libevent* glib* readline*

3)編譯安裝 lua

MySQL-Proxy 的讀寫分離主要是通過 rw-splitting.lua 指令碼實現的,因此需要安裝 lua。

方式 1:一般系統自帶。

方式 2:手工安裝。

這裡我們建議採用原始碼包進行安裝:

  1. cd /opt/install
  2. wget http://www.lua.org/ftp/lua-5.1.4.tar.gz
  3. tar zvfxlua-5.2.3.tar.gz
  4. cd lua-5.1.4
  5. vi src/Makefile
  6. 在CFLAGS= -O2 -Wall $(MYCFLAGS) 這一行記錄里加上-fPIC,更改為 CFLAGS= -O2 -Wall -fPIC$(MYCFLAGS) 來避免編譯過程中出現錯誤。
  7. make linux(編譯到記憶體)
  8. make install

4)安裝 Mysql-Proxy

MySQL-Proxy 可通過此網址獲得:http://mysql.cdpa.nsysu.edu.tw/Downloads/MySQL-Proxy/

推薦採用已經編譯好的二進位制版本,因為採用原始碼包進行編譯時,最新版的 MySQL-Proxy 對 automake、glib 以及 libevent 的版本都有很高的要求,而這些軟體包都是系統的基礎套件,不建議強行進行更新。

並且這些已經編譯好的二進位制版本在解壓後都在統一的目錄內,因此建議選擇以下版本:

  • 32 位 RHEL5 平臺:http://mysql.cdpa.nsysu.edu.tw/Downloads/MySQL-Proxy/mysql-proxy-0.8.4-linux-rhel5-x86-32bit.tar.gz
  • 64 位 RHEL5 平臺:http://mysql.cdpa.nsysu.edu.tw/Downloads/MySQL-Proxy/mysql-proxy-0.8.4-linux-rhel5-x86-64bit.tar.gz
  1. tar -xzvf mysql-proxy-0.8.3-linux-rhel5-x86-64bit.tar.gz
  2. mv mysql-proxy-0.8.3-linux-rhel5-x86-64bit /opt/mysql-proxy

建立 Mysql-Proxy 服務管理指令碼:

  1. cd /opt/mysql-proxy
  2. mkdir scripts
  3. cp share/doc/mysql-proxy/rw-splitting.lua scripts/
  4. chmod +x /opt/mysql-proxy/scripts
  5. mkdir /opt/mysql-proxy/run
  6. mkdir /opt/mysql-proxy/log
  7. mkdir /opt/mysql-proxy/scripts

5)修改讀寫分離指令碼 rw-splitting.lua

修改預設連線,進行快速測試,不修改的話要達到連線數為 4 時才啟用讀寫分離

vi /opt/mysql-proxy/scripts/rw-splitting.lua

  1. -- connection pool
  2. if not proxy.global.config.rwsplitthen
  3. proxy.global.config.rwsplit = {
  4. min_idle_connections = 1, //預設為4
  5. max_idle_connections = 1, //預設為8
  6. is_debug = false
  7. }
  8. end

proxy.conf:

  1. [mysql-proxy]
  2. admin-username=root
  3. admin-password=admin
  4. proxy-read-only-backend-addresses=192.168.188.143,192.168.188.139
  5. proxy-backend-addresses=192.168.188.142
  6. proxy-lua-script=/opt/mysql-proxy/bin/rw-splitting.lua
  7. admin-lua-script=/opt/mysql-proxy/lib/mysql-proxy/lua/admin.lua

bin 目錄下執行:

  1. ./mysql-proxy --defaults-file=/opt/mysql-proxy/proxy.conf

mysql-proxy 指令碼引數詳解:

  • --proxy_path=/opt/mysql-proxy/bin:定義 mysql-proxy 服務二進位制檔案路徑。
  • --proxy-backend-addresses=192.168.10.130:3306:定義後端主伺服器地址。
  • --proxy-lua-script=/opt/mysql-proxy/scripts/rw-splitting.lua:定義 lua 讀寫分離指令碼路徑。
  • --proxy-id=/opt/mysql-proxy/run/mysql-proxy.pid:定義 mysql-proxy PID 檔案路徑。
  • --daemon:定義以守護程序模式啟動。
  • --keepalive:使程序在異常關閉後能夠自動恢復。
  • --pid-file=$PROXY_PID:定義 mysql-proxy PID 檔案路徑。
  • --user=mysql:以 mysql 使用者身份啟動服務。
  • --log-level=warning:定義 log 日誌級別,由高到低分別有(error|warning|info|message|debug)。
  • --log-file=/opt/mysql-proxy/log/mysql-proxy.log:定義 log 日誌檔案路徑。

6)測試讀寫分離 

  1. 登入 Proxy:mysql -u -p -h<proxy_ip> -P4040
  2. stop slave
  3. 在 Proxy 上插資料後,驗證主資料庫新增資料,而從沒有新增資料。