1. 程式人生 > >應用技術瓶頸之資料庫讀寫分離解決方案

應用技術瓶頸之資料庫讀寫分離解決方案

一直沒時間寫部落格了,一直在專研產品設計與技術解決方案。多年的積累與專案實戰發現專案業務幾乎每個程式設計師都能寫,但是解決方案卻不一定每個人能解決掉。我所認知的一個專案由於時間跟業務的爆發都會遇到兩個最大的技術瓶頸挑戰:

1、資料庫端的壓力瓶頸,以前再華為hwa專案組搞hadoop大資料時我就清晰的知道,專案都會因為時間的積累導致堆積的資料越來越多,而資料庫沒有進行處理會導致資料一直物理儲存著,會導致資料庫壓力越來越大,最終很難支撐業務,並且資料庫的單表都是有瓶頸的(記得mysql的單表超過1千萬就開始出現瓶頸的壓力,導致sql查詢開始大大減低,正常500w一下單錶速度特快);也有可能因為業務短暫的時間內突然爆發導致資料庫沒有辦法支撐,導致資料庫讀寫併發而死鎖問題;針對資料庫瓶頸的解決並不是增加物理硬體就能解決的,就像應用中的tomcat記憶體超過4G再往上加記憶體也沒多大效果了。

2、應用端的壓力瓶頸,每個應用都需要應用伺服器去容納,但是應用伺服器本身就是一個軟體,比如tomcat應用伺服器,他自己本身就是一個軟體,軟體一定有瓶頸,所以本身tomcat自身就有瓶頸了,並不會因為你的物理硬體升級就能解決的。就好像一個池子一桶桶的倒水進去,總有一天會溢位的。所以應用也是有瓶頸的,針對應用端的壓力,有很多解決方案,比如分散式快取,分散式部署這些解決方案。

雖以前再很多大企業都挑起過不少專案,很多時候大企業都有專門自己的DBA去解決資料庫端的技術瓶頸問題,但現在自己一直在設計屬於自己的產品,發現這篇文章寫的非常好,它描述的是資料庫端讀寫分離的解釋與資料庫端的搭建(雖然沒有具體到細節,但是非常清晰),所以如果遇到資料庫讀寫分離的,可以用心的花3,5分鐘看完,比較經典。以下內容轉載:

雖然知道處理大資料量時,資料庫要做讀寫分離,但是為什麼讀寫分離可以提高效能呢?

下面是搜來的一些解釋,看看再說!

一 什麼是讀寫分離

MySQL Proxy最強大的一項功能是實現“讀寫分離(Read/Write Splitting)”。基本的原理是讓主資料庫處理事務性查詢,而從資料庫處理SELECT查詢。資料庫複製被用來把事務性查詢導致的變更同步到叢集中 的從資料庫。 當然,主伺服器也可以提供查詢服務。使用讀寫分離最大的作用無非是環境伺服器壓力。可以看下這張圖:


二 讀寫分離的好處


1.增加冗餘

2.增加了機器的處理能力

3.對於讀操作為主的應用,使用讀寫分離是最好的場景,因為可以確保寫的伺服器壓力更小,而讀又可以接受點時間上的延遲。

三 讀寫分離提高效能之原因

1.物理伺服器增加,負荷增加
2.主從只負責各自的寫和讀,極大程度的緩解X鎖和S鎖爭用
3.從庫可配置myisam引擎,提升查詢效能以及節約系統開銷
4.從庫同步主庫的資料和主庫直接寫還是有區別的,通過主庫傳送來的binlog恢復資料,但是,最重要區別在於主庫向從庫傳送binlog是非同步的,從庫恢復資料也是非同步的
5.讀寫分離適用與讀遠大於寫的場景,如果只有一臺伺服器,當select很多時,update和delete會被這些select訪問中的資料堵塞,等待select結束,併發效能不高。 對於寫和讀比例相近的應用,應該部署雙主相互複製

6.可以在從庫啟動是增加一些引數來提高其讀的效能,例如--skip-innodb、--skip-bdb、--low-priority-updates以及--delay-key-write=ALL。當然這些設定也是需要根據具體業務需求來定得,不一定能用上

7.分攤讀取。假如我們有1主3從,不考慮上述1中提到的從庫單方面設定,假設現在1 分鐘內有10條寫入,150條讀取。那麼,1主3從相當於共計40條寫入,而讀取總數沒變,因此平均下來每臺伺服器承擔了10條寫入和50條讀取(主庫不 承擔讀取操作)。因此,雖然寫入沒變,但是讀取大大分攤了,提高了系統性能。另外,當讀取被分攤後,又間接提高了寫入的效能。所以,總體效能提高了,說白 了就是拿機器和頻寬換效能。MySQL官方文件中有相關演算公式:官方文件 見6.9FAQ之“MySQL複製能夠何時和多大程度提高系統性能”

8.MySQL複製另外一大功能是增加冗餘,提高可用性,當一臺資料庫伺服器宕機後能通過調整另外一臺從庫來以最快的速度恢復服務,因此不能光看效能,也就是說1主1從也是可以的。

四 讀寫分離示意圖

    

五 讀寫分離模擬

實驗環境簡介

serv01:代理伺服器 192.168.1.11 serv01.host.com

serv08:主伺服器(主要寫資料,可讀可寫) 192.168.1.18 serv08.host.com

serv09:從伺服器(主要讀資料) 192.168.1.19 serv09.host.com

作業系統版本

RHEL Server6.1 64位系統

使用到的軟體包版本

mysql-5.5.29-linux2.6-x86_64.tar.gz

 mysql-proxy-0.8.2-linux-glibc2.3-x86-64bit.tar.gz

第一步,搭建MySQL伺服器,清空日誌。注意:代理伺服器中不需要裝MySQL

第二步,拷貝mysql-proxy-0.8.2-linux-glibc2.3-x86-64bit.tar.gz檔案,解壓檔案

[[email protected] 1005]# scp /opt/soft/ule-mysql/mysql-proxy-0.8.2-linux-glibc2.3-x86-64bit.tar.gz 192.168.1.11:/opt [root@serv01 opt]# tar -xvf mysql-proxy-0.8.2-linux-glibc2.3-x86-64bit.tar.gz -C /usr/local/ [root@serv01 opt]# cd /usr/local/ [root@serv01 local]# mv mysql-proxy-0.8.2-linux-glibc2.3-x86-64bit/ mysql-proxy [root@serv01 local]# ll mysql-proxy/ total 24 drwxr-xr-x. 2 7157 wheel 4096 Aug 17 2011 bin drwxr-xr-x. 2 7157 wheel 4096 Aug 17 2011 include drwxr-xr-x. 4 7157 wheel 4096 Aug 17 2011 lib drwxr-xr-x. 2 7157 wheel 4096 Aug 17 2011 libexec drwxr-xr-x. 3 7157 wheel 4096 Aug 17 2011 licenses drwxr-xr-x. 3 7157 wheel 4096 Aug 17 2011 share --可以檢視幫助 [root@serv01 bin]# ./mysql-proxy --help-all 

第三步,serv08主伺服器建立使用者,serv09從伺服器建立使用者,注意使用者名稱和密碼一致

serv08 mysql> grant all on *.* to 'larry'@'192.168.1.%' identified by 'larry'; Query OK, 0 rows affected (0.00 sec) serv09 mysql> grant all on *.* to 'larry'@'192.168.1.%' identified by 'larry'; Query OK, 0 rows affected (0.00 sec) 

第四步,serv09從伺服器更改設定,開啟slave,檢視slave狀態。建立測試資料庫,插入測試資料

serv09 mysql> change master to master_host='192.168.1.18', 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) mysql> start slave; Query OK, 0 rows affected (0.00 sec) mysql> show slave status \G; *************************** 1. row *************************** Slave_IO_State: Waiting for master to send event Master_Host: 192.168.1.18 Master_User: larry Master_Port: 3306 Connect_Retry: 60 Master_Log_File: mysql-bin.000001 Read_Master_Log_Pos: 107 Relay_Log_File: serv09-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: 2 1 row in set (0.00 sec) ERROR: No query specified mysql> select user,password,host from mysql.user; +------+-------------------------------------------+-----------------+ | user | password | host | +------+-------------------------------------------+-----------------+ | root | | localhost | | root | | serv08.host.com | | root | | 127.0.0.1 | | root | | ::1 | | | | localhost | | | | serv08.host.com | | rep | *0CDC8D34246E22649D647DB04E7CCCACAB4368B6 | 192.168.1.% | +------+-------------------------------------------+-----------------+ 7 rows in set (0.00 sec) mysql> create database larrydb; Query OK, 1 row affected (0.00 sec) mysql> use larrydb; Database changed mysql> create table user(id int, name varchar(30)); Query OK, 0 rows affected (0.01 sec) mysql> insert into user values(1,'larrywen'); Query OK, 1 row affected (0.01 sec) mysql> insert into user values(2,'wentasy'); Query OK, 1 row affected (0.00 sec) mysql> select * from user; +------+----------+ | id | name | +------+----------+ | 1 | larrywen | | 2 | wentasy | +------+----------+ 2 rows in set (0.00 sec) serv09 mysql> select * from larrydb.user; +------+----------+ | id | name | +------+----------+ | 1 | larrywen | | 2 | wentasy | +------+----------+ 2 rows in set (0.00 sec) 

第五步,為了檢視現象,serv09從伺服器關閉slave

mysql> stop slave;
Query OK, 0 rows affected (0.01 sec)

第六步,serv 01檢視是否有MySQL使用者,修改rw-splitting.lua檔案,修改如下幾個引數

[[email protected]serv01 mysql-proxy]# id mysql uid=500(mysql) gid=500(mysql) groups=500(mysql) [[email protected]serv01 mysql-proxy]# vim rw-splitting.lua [[email protected]serv01 mysql-proxy]# cat rw-splitting.lua | grep -e min_idle_connections -e max_idle_connections -e is_debug min_idle_connections = 1,--最小空閒連線數,為了測試,這裡設定為1 max_idle_connections = 1,--最大空閒連線數,為了測試,這裡設定為1 is_debug = true--是否開啟Debug除錯,為了檢視除錯資訊,這裡設定為true

第七步,啟動mysql-proxy

[[email protected]serv01 mysql-proxy]# /etc/init.d/mysql-proxy start Starting mysql-proxy: --先確定是否可以連線 [[email protected]serv01 ~]# mysql -ularry -plarry -h 192.168.1.18 Welcome to the MySQL monitor. Commands end with ; or \g. Your MySQL connection id is 6 Server version: 5.5.29-log Source distribution Copyright (c) 2000, 2012, Oracle and/or its affiliates. All rights reserved. Oracle is a registered trademark of Oracle Corporation and/or its affiliates. Other names may be trademarks of their respective owners. Type 'help;' or '\h' for help. Type '\c' to clear the current input statement. mysql> exit Bye [[email protected]serv01 ~]# mysql -ularry -plarry -h 192.168.1.19 Welcome to the MySQL monitor. Commands end with ; or \g. Your MySQL connection id is 8 Server version: 5.5.29-log Source distribution Copyright (c) 2000, 2012, Oracle and/or its affiliates. All rights reserved. Oracle is a registered trademark of Oracle Corporation and/or its affiliates. Other names may be trademarks of their respective owners. Type 'help;' or '\h' for help. Type '\c' to clear the current input statement. mysql> exit Bye 

第八步,檢視現象

[[email protected]serv01 ~]# /etc/init.d/mysql-proxy start Starting mysql-proxy: [[email protected]serv01 ~]# mysql -ularry -plarry -h 192.168.1.11 [connect_server] 192.168.1.11:51054 [1].connected_clients = 0 [1].pool.cur_idle = 0 [1].pool.max_idle = 1 [1].pool.min_idle = 1 [1].type = 1 [1].state = 0 [1] idle-conns below min-idle Welcome to the MySQL monitor. Commands end with ; or \g. [read_query] 192.168.1.11:51054 current backend = 0 client default db = client username = larry query = select @@version_comment limit 1 sending to backend : 192.168.1.19:3306 is_slave : false server default db: server username : larry in_trans : false in_calc_found : false COM_QUERY : true Your MySQL connection id is 10 Server version: 5.5.29-log Source distribution Copyright (c) 2000, 2012, Oracle and/or its affiliates. All rights reserved. Oracle is a registered trademark of Oracle Corporation and/or its affiliates. Other names may be trademarks of their respective owners. Type 'help;' or '\h' for help. Type '\c' to clear the current input statement. mysql> mysql> use larrydb; [read_query] 192.168.1.11:51054 current backend = 0 client default db = client username = larry query = SELECT DATABASE()  sending to backend : 192.168.1.19:3306 is_slave : false server default db: server username : larry in_trans : false in_calc_found : false COM_QUERY : true [read_query] 192.168.1.11:51054 current backend = 0 client default db = client username = larry sending to backend : 192.168.1.19:3306 is_slave : false server default db: server username : larry in_trans : false in_calc_found : false COM_QUERY : false Database changed mysql> select * from user; [read_query] 192.168.1.11:51054 current backend = 0 client default db = larrydb client username = larry query = select * from user sending to backend : 192.168.1.19:3306 is_slave : false server default db: larrydb server username : larry in_trans : false in_calc_found : false COM_QUERY : true +------+----------+ | id | name | +------+----------+ | 1 | larrywen | | 2 | wentasy | +------+----------+ 2 rows in set (0.00 sec) mysql> insert into user values(3,'jsutdb'); [read_query] 192.168.1.11:51644 current backend = 0 client default db = larrydb client username = larry query = insert into user values(3,'jsutdb')  sending to backend : 192.168.1.19:3306 is_slave : false server default db: larrydb server username : larry in_trans : false in_calc_found : false COM_QUERY : true Query OK, 1 row affected (0.00 sec) serv08 mysql> select * from user; +------+----------+ | id | name | +------+----------+ | 1 | larrywen | | 2 | wentasy | +------+----------+ 2 rows in set (0.00 sec) serv09 mysql> select * from larrydb.user; +------+----------+ | id | name | +------+----------+ | 1 | larrywen | | 2 | wentasy | | 3 | jsutdb | +------+----------+ 3 rows in set (0.00 sec) 

第九步,以上的測試雖有效果,但不是預期。排查原因,重新配置。發現proxy-read-only-backend-addressesproxy-backend-addresses引數配置出錯,proxy-read-only-backend-addresses應該配置成從伺服器的IP地址,proxy-backend-addresses應該配置成主伺服器的IP地址。

[[email protected] ~]# vim /etc/init.d/mysql-proxy [[email protected] ~]# cat /etc/init.d/mysql-proxy #!/bin/sh # # mysql-proxy This script starts and stops the mysql-proxy daemon # # chkconfig: - 78 30 # processname: mysql-proxy # description: mysql-proxy is a proxy daemon to mysql # Source function library. . /etc/rc.d/init.d/functions #PROXY_PATH=/usr/local/bin PROXY_PATH=/usr/local/mysql-proxy/bin prog="mysql-proxy" # Source networking configuration. . /etc/sysconfig/network # Check that networking is up. [ ${NETWORKING} = "no" ] && exit 0 # Set default mysql-proxy configuration. #PROXY_OPTIONS="--daemon" PROXY_OPTIONS="--proxy-read-only-backend-addresses=192.168.1.19:3306 --proxy-backend-addresses=192.168.1.18:3306 --proxy-lua-script=/usr/local/mysql-proxy/share/doc/mysql-proxy/rw-splitting.lua" #PROXY_PID=/usr/local/mysql-proxy/run/mysql-proxy.pid PROXY_PID=/var/run/mysql-proxy.pid # Source mysql-proxy configuration. if [ -f /etc/sysconfig/mysql-proxy ]; then . /etc/sysconfig/mysql-proxy fi PATH=$PATH:/usr/bin:/usr/local/bin:$PROXY_PATH # By default it's all good RETVAL=0 # See how we were called. case "$1" in start) # Start daemon. echo -n $"Starting $prog: "  $NICELEVEL $PROXY_PATH/mysql-proxy $PROXY_OPTIONS --daemon --pid-file=$PROXY_PID --user=mysql --log-level=debug --log-file=/var/log/mysql-proxy.log --proxy-address=192.168.1.11:3306 RETVAL=$? echo if [ $RETVAL = 0 ]; then touch /var/lock/subsys/mysql-proxy fi ;; stop) # Stop daemons. echo -n $"Stopping $prog: " killproc $prog RETVAL=$? echo if [ $RETVAL = 0 ]; then rm -f /var/lock/subsys/mysql-proxy rm -f $PROXY_PID fi ;; restart) $0 stop sleep 3 $0 start ;; condrestart) [ -e /var/lock/subsys/mysql-proxy ] && $0 restart ;; status) status mysql-proxy RETVAL=$? ;; *) echo "Usage: $0 {start|stop|restart|status|condrestart}" RETVAL=1 ;; esac exit $RETVAL 

第十步,測試。插入資料,可以發現連線的是主伺服器,查詢的時候也是主伺服器。說明主伺服器和從伺服器均有讀的的功能。

[[email protected]serv01 ~]# mysql -ularry -plarry -h 192.168.1.11 [connect_server] 192.168.1.11:57891 [1].connected_clients = 0 [1].pool.cur_idle = 0 [1].pool.max_idle = 1 [1].pool.min_idle = 1 [1].type = 1 [1].state = 1 [1] idle-conns below min-idle [read_query] 192.168.1.11:57891 current backend = 0 client default db = client username = larry query = select @@version_comment limit 1  sending to backend : 192.168.1.18:3306 is_slave : false server default db: server username : larry in_trans : false in_calc_found : false COM_QUERY : true mysql> insert into user values(5,'test'); Query OK, 1 row affected (0.01 sec) [read_query] 192.168.1.11:57893 current backend = 0 client default db = larrydb client username = larry query = insert into user values(5,'test')  sending to backend : 192.168.1.18:3306 is_slave : false server default db: larrydb server username : larry in_trans : false in_calc_found : false COM_QUERY : true mysql> select * from user; +------+----------+ | id | name | +------+----------+ | 1 | larrywen | | 2 | wentasy | | 5 | test | +------+----------+ 3 rows in set (0.00 sec) [read_query] 192.168.1.11:57893 current backend = 0 client default db = larrydb client username = larry query = select * from user  sending to backend : 192.168.1.18:3306 is_slave : false server default db: larrydb server username : larry in_trans : false in_calc_found : false COM_QUERY : true serv08主伺服器檢視資料,可以查詢到,說明主伺服器可以寫 mysql> select * from larrydb.user; +------+----------+ | id | name | +------+----------+ | 1 | larrywen | | 2 | wentasy | | 5 | test | +------+----------+ 3 rows in set (0.00 sec) serv09從伺服器查詢資料,發現不可查詢到,說明從伺服器只讀 mysql> mysql> select * from larrydb.user; +------+----------+ | id | name | +------+----------+ | 1 | larrywen | | 2 | wentasy | | 3 | jsutdb | | 4 | db | +------+----------+ 4 rows in set (0.00 sec) 

第十一步,開啟slave。發現數據同步成功。

mysql> start slave;
Query OK, 0 rows affected (0.00 sec)

mysql> select * from larrydb.user;
+------+----------+
| id   | name     |
+------+----------+
|    1 | larrywen |
|    2 | wentasy  |
|    3 | jsutdb   |
|    4 | db       |
|    5 | test     |
+------+----------+
5 rows in set (0.00 sec)
轉自:http://blog.csdn.net/justdb/article/details/17331569 

********************下面是另一篇啦啦啦***************************

隨著一個網站的業務不斷擴充套件,資料不斷增加,資料庫的壓力也會越來越大,對資料庫或者SQL的基本優化可能達不到最終的效果,我們可以採用讀寫分離的策 略來改變現狀。讀寫分離現在被大量應用於很多大型網站,這個技術也不足為奇了。ebay就做得非常好。ebay用的是oracle,聽說是用Quest Share Plex 來實現主從複製資料。

     讀寫分離簡單的說是把對資料庫讀和寫的操作分開對應不同的資料庫伺服器,這樣能有效地減輕資料庫壓力,也能減輕io壓力。主資料庫提供寫操作,從資料庫提 供讀操作,其實在很多系統中,主要是讀的操作。當主資料庫進行寫操作時,資料要同步到從的資料庫,這樣才能有效保證資料庫完整性。Quest SharePlex就是比較牛的同步資料工具,聽說比oracle本身的流複製還好,mysql也有自己的同步資料技術。mysql只要是通過二進位制日誌來複制資料。通過日誌在從資料庫重複主資料庫的操作達到複製資料目的。這個複製比較好的就是通過非同步方法,把資料同步到從資料庫。

      主資料庫同步到從資料庫後,從資料庫一般由多臺資料庫組成這樣才能達到減輕壓力的目的。讀的操作怎麼樣分配到從資料庫上?應該根據伺服器的壓力把讀的操作分配到伺服器,而不是簡單的隨機分配。mysql提供了MySQL-Proxy實現讀寫分離操作。不過MySQL-Proxy好像很久不更新了。oracle可以通過F5有效分配讀從資料庫的壓力。


 mysql的讀寫分離
       上面說的資料庫同步複製,都是在從同一種資料庫中,如果我要把oracle的資料同步到mysql中,其實要實現這種方案的理由很簡單,mysql免費,oracle太貴。好像Quest SharePlex也實現不了改功能吧。好像現在市面還沒有這個工具吧。那樣應該怎麼實現資料同步?其實我們可以考慮自己開發一套同步資料元件,通過訊息,實現非同步複製資料。其實這個實現起來要考慮很多方面問題,高併發的問題,失敗記錄等。其實這種方法也可以同步數據到memcache中。聽說oracle的Stream也能實現,不過沒有試過。

轉自:http://www.cnblogs.com/qlee/archive/2011/04/08/2009738.html