1. 讀寫分離&讀寫分離 簡介
- 主從同步延遲
- 分配機制
- 解決單點故障
- 總結
2. 主從複製&讀寫分離 搭建
- 搭建主從複製(雙主)
- 搭建讀寫分離
1. 讀寫分離&讀寫分離 簡介
讀寫分離顧名思義就是讀和寫分離,對應到資料庫叢集一般都是一主一從(一個主庫,一個從庫)或者一主多從(一個主庫,多個從庫),業務伺服器把需要寫的操作都寫到主資料庫中,讀的操作都去從庫查詢。主庫會同步資料到從庫保證資料的一致性。
這種叢集方式的本質是把訪問的壓力從主庫轉移到從庫,也就是在單機資料庫無法支撐併發讀寫,並且讀的請求很多的情況下適合這種讀寫分離的資料庫叢集。如果寫的操作很多的話不適合這種叢集方式,因為你的資料庫壓力還是在寫操作上,即使主從了之後壓力還是在主庫上,這樣和單機的區別就不大了。
在單機的情況下,一般我們做資料庫優化都會加索引,但是加了索引對查詢有優化,但會影響寫入,因為寫入資料會更新索引。所以做了主從之後,我們可以單獨地針對從庫(讀庫)做索引上的優化,而主庫(寫庫)可以減少索引而提高寫的效率。
還有兩點要注意:主從同步延遲、分配機制的考慮。
主從同步延遲
主庫有資料寫入之後,同時也寫入在binlog(二進位制日誌檔案)中,從庫是通過 binlog 檔案來同步資料的,這期間會有一定時間的延遲,可能是 1 秒,如果同時有大量資料寫入的話,時間可能更長。
這會導致什麼問題呢?比如有一個付款操作,你付款了,主庫是已經寫入資料,但是查詢是到從庫查,從庫裡還沒有你的付款記錄,所以頁面上查詢的時候你還沒付款。那可不急眼了啊,吞錢了這還了得!打電話給客服投訴!
所以為了解決主從同步延遲的問題有以下幾個方法:
1)二次讀取
二次讀取的意思就是讀從庫沒讀到之後再去主庫讀一下,只要通過對資料庫訪問的 API 進行封裝就能實現這個功能。很簡單,並且和業務之間沒有耦合。但是有個問題,如果有很多二次讀取相當於壓力還是回到了主庫身上,等於讀寫分離白分了。而且如有人惡意攻擊,就一直訪問沒有的資料,那主庫就可能爆了。
2)寫之後的馬上的讀操作訪問主庫
也就是寫操作之後,立馬的讀操作指定為訪問主庫,之後的讀操作則訪問從庫。這就等於寫死了,和業務強耦合了。
3)關鍵業務讀寫都由主庫承擔,非關鍵業務讀寫分離
類似付錢的這種業務,讀寫都到主庫,避免延遲的問題,但是例如改個頭像啊,個人簽名這種比較不重要的就讀寫分離,查詢都去從庫查,畢竟延遲一下影響也不大,不會立馬打客服電話投訴。
分配機制
分配機制的考慮也就是怎麼制定寫操作是去主庫寫,讀操作是去從庫讀。
一般有兩種方式:程式碼封裝、資料庫中介軟體。
1)程式碼封裝
程式碼封裝的實現很簡單,就是抽出一箇中間層,讓這個中間層來實現讀寫分離和資料庫連線。講白點就是搞個 provider 封裝了 save、select 等通常資料庫操作,內部 save 操作的 dataSource 是主庫的,select 操作的 dataSource 是從庫的。
優點:
- 實現簡單。
- 可以根據業務定製化變化,隨心所欲。
缺點:
- 如果哪個資料庫宕機了,發生主從切換了之後,就得修改配置重啟。
- 如果系統很大,一個業務可能包含多個子系統,一個子系統是 java 寫的,一個子系統用 go 寫的,這樣的話得分別為不同語言實現一套中間層,重複開發。
2)資料庫中介軟體
就是有一個獨立的系統,專門來實現讀寫分離和資料庫連線管理,業務伺服器和資料庫中介軟體之間是通過標準的 SQL 協議交流的,所以在業務伺服器看來資料庫中介軟體其實就是個資料庫。
優點:
- 因為是通過 SQL 協議的所以可以相容不同的語言不需要單獨寫一套。
- 由中介軟體來實現主從切換,業務伺服器不需要關心這點。
缺點:
- 多了一個系統其實就等於多了一個關心,比如資料庫中介軟體掛了。
- 多了一個系統就等於多了一個瓶頸,所以對中介軟體的效能要求也高,因為所有的資料庫操作都要先經過它。
- 中介軟體實現較為複雜,難度比程式碼封裝高多了。
常用的開源資料庫中介軟體有 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 資料庫
- 安裝命令:yum -y install mysql-server
- 配置登入使用者的密碼:mysqladmin -u root password '密碼'
- 配置允許第三方機器訪問本機 MySQL:
- delete from user where password = '';
- update user set host='%';
- flush privileges;
2)分別修改 MySQL 配置
配置 masterA:
- [root@adailinux ~]# vim /etc/my.cnf
- [mysqld]
- datadir = /data/mysql
- socket = /tmp/mysql.sock
- server_id = 1 # 指定server-id,必須保證主從伺服器的server-id不同
- auto_increment_increment = 2 # 設定主鍵單次增量
- auto_increment_offset = 1 # 設定單次增量中主鍵的偏移量:奇數
- log_bin = mysql-bin # 建立主從需要開啟log-bin日誌檔案
- log-slave-updates # 把更新的日誌寫到二進位制檔案(binlog)中,臺伺服器既做主庫又做從庫此選項必須要開啟
配置 masterB:
- [root@adailinux ~]# vim /etc/my.cnf
- [mysqld]
- datadir = /data/mysql
- socket = /tmp/mysql.sock
- server_id = 2 # 指定server-id,必須保證主從伺服器的server-id不同
- auto_increment_increment = 2 # 設定主鍵單次增量
- auto_increment_offset = 2 # 設定單次增量中主鍵的偏移量:偶數
- log_bin = mysql-bin # 建立主從需要開啟log-bin日誌檔案
- log-slave-updates = True # 把更新的日誌寫到二進位制檔案(binlog)中
以上為同步配置的核心引數。
server_id 有兩個用途:
- 用來標記 binlog event 的源產地,就是 SQL 語句最開始源自於哪裡。
- 用於 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 並檢視主庫狀態:
- [root@adailinux ~]# /etc/init.d/mysqld restart
- Shutting down MySQL.. SUCCESS!
- Starting MySQL. SUCCESS!
- masterA:
- [root@adailinux ~]# mysql -uroot
- mysql> show master status;
- +------------------+----------+--------------+------------------+-------------------+
- | File | Position | Binlog_Do_DB | Binlog_Ignore_DB | Executed_Gtid_Set |
- +------------------+----------+--------------+------------------+-------------------+
- | mysql-bin.000001 | 419 | TSC | mysql | |
- +------------------+----------+--------------+------------------+-------------------+
- 1 row in set (0.00 sec)
- masterB:
- [root@adailinux ~]# mysql -uroot
- mysql> show master status
- -> ;
- +------------------+----------+--------------+------------------+-------------------+
- | File | Position | Binlog_Do_DB | Binlog_Ignore_DB | Executed_Gtid_Set |
- +------------------+----------+--------------+------------------+-------------------+
- | mysql-bin.000001 | 419 | TSC | mysql | |
- +------------------+----------+--------------+------------------+-------------------+
- 1 row in set (0.00 sec)
3)配置同步資訊
masterA:
- [root@adailinux ~]# mysql -uroot
- 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;
- #注:IP為masterB的IP(即,從伺服器的IP)
- mysql> start slave;
- Query OK, 0 rows affected (0.05 sec)
- mysql> show slave status\G;
- 在此檢視有如下狀態說明配置成功:
- Slave_IO_Running: Yes
- Slave_SQL_Running: Yes
masterB:
- [root@adailinux ~]# mysql -uroot
- 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;
- Query OK, 0 rows affected, 2 warnings (0.06 sec)
- mysql> start slave;
- Query OK, 0 rows affected (0.04 sec)
- mysql> show slave status\G
- 在此檢視有如下狀態說明配置成功:
- Slave_IO_Running: Yes
- Slave_SQL_Running: Yes
4)測試主從同步
在 masterA 上建立一個庫,驗證 masterB 是否同步建立了該庫。
搭建讀寫分離
場景描述:
- 資料庫 Master 主伺服器
- 資料庫 Slave 從伺服器
- MySQL-Proxy 排程伺服器
以下操作,均是在 MySQL-Proxy 排程伺服器上進行的。
1)MySQL 伺服器安裝
2)檢查/安裝系統所需軟體包
- 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:手工安裝。
這裡我們建議採用原始碼包進行安裝:
- cd /opt/install
- wget http://www.lua.org/ftp/lua-5.1.4.tar.gz
- tar zvfxlua-5.2.3.tar.gz
- cd lua-5.1.4
- vi src/Makefile
- 在CFLAGS= -O2 -Wall $(MYCFLAGS) 這一行記錄里加上-fPIC,更改為 CFLAGS= -O2 -Wall -fPIC$(MYCFLAGS) 來避免編譯過程中出現錯誤。
- make linux(編譯到記憶體)
- 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
- tar -xzvf mysql-proxy-0.8.3-linux-rhel5-x86-64bit.tar.gz
- mv mysql-proxy-0.8.3-linux-rhel5-x86-64bit /opt/mysql-proxy
建立 Mysql-Proxy 服務管理指令碼:
- cd /opt/mysql-proxy
- mkdir scripts
- cp share/doc/mysql-proxy/rw-splitting.lua scripts/
- chmod +x /opt/mysql-proxy/scripts
- mkdir /opt/mysql-proxy/run
- mkdir /opt/mysql-proxy/log
- mkdir /opt/mysql-proxy/scripts
5)修改讀寫分離指令碼 rw-splitting.lua
修改預設連線,進行快速測試,不修改的話要達到連線數為 4 時才啟用讀寫分離
vi /opt/mysql-proxy/scripts/rw-splitting.lua
- -- connection pool
- if not proxy.global.config.rwsplitthen
- proxy.global.config.rwsplit = {
- min_idle_connections = 1, //預設為4
- max_idle_connections = 1, //預設為8
- is_debug = false
- }
- end
proxy.conf:
- [mysql-proxy]
- admin-username=root
- admin-password=admin
- proxy-read-only-backend-addresses=192.168.188.143,192.168.188.139
- proxy-backend-addresses=192.168.188.142
- proxy-lua-script=/opt/mysql-proxy/bin/rw-splitting.lua
- admin-lua-script=/opt/mysql-proxy/lib/mysql-proxy/lua/admin.lua
bin 目錄下執行:
- ./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)測試讀寫分離
- 登入 Proxy:mysql -u -p -h<proxy_ip> -P4040
- stop slave
- 在 Proxy 上插資料後,驗證主資料庫新增資料,而從沒有新增資料。