成長是一棵樹,總是在你不知不覺的情況下快樂長大;成長是一株草,總是在你不知不覺的情況下長滿大地;成長是一朵花,總是在你不知不覺的情況下開滿山頭。
這不,隨著時間的遷移。項目網站的用戶量、數據量持續上升,普通模式下的單機MySQL即將面臨被請求壓力打垮的情景。老早就被急的像熱鍋上螞蟻一樣的產品經理,把我給叫了過去,看著那誠懇的小眼神兒。心理甚是開心。
你說,早知道會遇到這樣的事兒,以前咋不見他好好說話,朝令夕改的需求,感覺就不要欠錢似的直接堆過來,今天終于輪到我 農民翻身把歌唱 啦。
自己首先就是一副處變不驚的表情,抽根煙,喝口茶。對產品說到:“不急不急,一切盡在我們團隊的預料之中”。 由我一一道來,這下咱真的是腿不酸了,腰板兒也硬朗了。好不容易,你撞我槍口上來了,還不得殺殺你的銳氣。 也讓你知道知道咋的手段。
隨著網站的發展,在面對高并發請求的時候。單機下面的MySQL肯定是會遇到性能的瓶頸。因為你單機無論是磁盤的存儲、I/O的開銷上都是會有資源上限。沒辦法支持網站后續的發展。畢竟雙拳難敵四手。正是因為這樣我們才會構建大規模、高性能的應用。基于“水平擴展”的方式來滿足項目發展的需要。這也是搭建高可用、可擴展性、災難恢復、備份及數據倉庫等工作的基礎。
在有規模的網站中,程序員往往都是避免不了這類型的話題。
為什么使用MySQL主從復制?
1、自身不足:解決單機下的瓶頸問題
為什么隨著公司的發展,需要越來越多的人,而不應該人數越少越好好嗎?那是因為公司越發展業務會增多,相關的事情也會越來越多。如果都交給一個人,那必定精力是有限。根本不能完成每天的工作。
單機MySQL也是一樣。它的磁盤、內存、I/O、CPU等資源都是有限制。
注:單機MySQL指在一臺物理服務器上安裝一臺MySQL,專門只提供數據庫的服務。單機多實例指在一臺物理服務器上可以安裝多臺MySQL實例
一般中、大型網站的數據量可能達到幾百GB、幾個TB的數據,甚至是更高。而你單機下面的磁盤容量可能才是幾十個GB。它能夠支持數據存儲的容量嗎?
網站項目規模過大,那必定你業務的請求量肯定是十分巨大的,在這么大的情況下面,少說也得億級的PV的用戶請求吧。這樣的條件下,你的I/O訪問的頻率過高,CPU切換忙都忙不過來,一直都是高負載的情況。它能搞的贏?
因為系統的軟件運行是由CPU每隔一個時間片切換后才能執行程序的。這樣服務器的壓力會異常的大,一直都高負荷運行狀態。從而導致宕機。 同時你在這樣的條件下,每一個請求走到MySQL后,都需要給每一個請求分配一個線程去執行該請求里面的SQL語句。那線程的資源開銷也是需要內存的啊。就算你是單機的MySQL。那你的內存也有上限,難道可以分配幾千個線程?
這不,葛優大爺出演的電影《甲方乙方》也說道了,"地主家也沒有余糧啊"
并且,你磁盤I/O的讀寫也是需要時間的,請求越多,你磁盤讀寫機會也機會多,而從數據庫的數據是需要從磁盤里面讀取到內存中,然后在響應給客戶端的。那所有的請求都要做磁盤尋址。那這個消耗也是很大的。
- 磁盤獲取數據過程的時間:訪問時間+傳輸時間
- 移動磁頭到磁盤面上的正確位置(訪問時間)
- 等待磁盤旋轉,使得所需要的數據在磁頭之下(訪問時間)
- 等待磁盤旋轉過去,所有需要的數據都被磁頭讀出(傳輸速度)
后面出一篇詳細的數據庫數據載入原理,這里大家先給一個總結
2、壓力分擔:讓請求負載均衡給多臺數據庫
做了主從復制后最明顯的特點就是,把相關的讀寫請求分離。主庫針對寫請求,從庫針對讀請求,這樣能夠在業務上面把各個數據庫的職責劃分開。對后續的一切客戶端的請求,我們可以根據讀或者是寫直接交給對應的數據庫。
一般項目絕大多數都是讀操作,通過MYSQL復制將讀操作分布到多個服務器上,從而實現對密集性應用的優化,通過Nginx簡單的代碼修改就能實現基本負載均衡。
對于小規模的網站,可以對機器名做硬編碼或使用DNS輪詢(將一個機器名指定到多個IP地址),當然也可以選擇復雜的方法,采用LVS網絡的負載均衡來實現網絡的情求的分擔。
業務上針請求類型來劃分。在讀請求上,針對負載均衡實施到多個數據庫中,你說這個手段怎么樣? 要得不呢? 咱程序員也不是吃素的。嗓子干了,喝口水再來
3、數據備份與數據分布
對于數據備份。這應當是每個網站中都需要考慮的環節,從而保證線上環境因為誤刪而導致數據庫不能追蹤回來。但是這里的主從復制和我們常說的備份不一樣。
主從復制是通過把主庫的數據復制到從庫中,雖然是復制的過程,但也可以達到備份的效果,就像你的電影從D盤復制到E盤,就存在2份了,刪除一份,另一份還在。
對于主從復制的過程中,可以選擇在不同的地理位置來分布數據,不同的數據中心內,即使不穩定的網絡條件下,遠程復制也是進行的。這樣我們能夠讓數據庫有多個數據中心節點,分布到各地。異地多活就是自在。
4、高可用和故障遷移
復制能夠幫助應用程序避免MySQL單點失敗。一個包含復制的設計良好的故障切換系統能夠顯著的縮短宕機時間。這也就是主從復制為什么可以做到高可用的特點。
假如我的服務器因為機房停電了,這臺服務器就沒法使用了,但是它的數據都被Copy到另外的服務器中,這樣我可以直接從庫里直接選擇一臺服務器來替代沒法工作的服務器。從而繼續提供服務。
總結一下,備份的操作就是做為高可用的首選條件。
如何使用MySQL主從復制
前面經過經過各方面的問題以及主從復制做了詳細的解析,大家需要自己匯總下主從復制的特點與解決到了單機什么的問題,因為面試有時候就喜歡問一些你對于業務特點的理解和底層原理,那前面是問題的理解,這個實現原理怎么來辦? 放心,作為暖男的我,下面就給大家一 一道明。
主從復制原理解析
MySQL主從復制上的復制,總的來說,有三個過程來實現數據的復制操作。
- 在主從上把數據更新記錄的SQL語句記錄到二進制日志(Binary Log)中,這些記錄被稱之為二進制日志
注:更新記錄的SQL語句為與DDL和DML類型語句。DDL語句就是對數據庫、表層面的操作,如CREATE、ALTER、DROP;DML就是對表中數據的增刪改查,如SELECT、UPDATE、INSERT、DELETE操作的語句。但是在二進制中SELECT不會記錄進去。
- 從庫將主庫上的日志復制到自己的中繼日志中(Relay Log)中
- 從庫讀取中繼日志的中SQL記錄,將其數據重放到備庫上
以上就是大概的描述,下面來個圖進行詳細過程梳理。
- 主庫db的更新事件(create、alter、drop、update、insert、delete)在準備提交事務完成數據更新前,將更新數據寫到binlog。日志記錄的順序按照事務的順序而非SQL執行順序。記錄完成后,在由主庫調用存儲引擎接口提交事務。
- 從庫啟動時創建一個I/O工作線程,該線程跟主庫建立一個普通網絡連接。
- 此時主庫將創建一個二進制轉儲線程(binlog dump thread),該線程只做日志讀取,不進行SQL執行。然后二進制轉儲線程讀取主庫二進制文件中內容,用于把binlog的內容發送到從庫。但它不會對事件進行輪詢,如果該線程追趕上了主庫的記錄同步,將進入睡眠狀態。直到主庫發送信號量通知其有新的更新操作到來才會被喚醒。
- 當主庫數據發來時,從庫創建的I/O工作線程,將會把主庫傳過來的binlog內容寫入到中繼日志(relay log)中
- 從庫還會創建一個SQL線程,從relay log里面讀取內容,從Exec_Master_Log_Pos位置開始執行讀取到的更新事件,將更新內容寫入到slave的db。當SQL線程追上I/O線程時,中繼日志在這個狀態下通過已經是在系統的緩存中進行讀取了,所以開銷很低。
主從復制大家步驟
前面咱們了解到原理,現在就著手來搭建主從復制。
復制前的準備工作:
- 主從數據庫版本最好一致
- 主從數據庫內數據保持一致
- 在每臺服務器上都創建復制賬號
- 配置主庫與從庫
- 通知備庫連接到主庫并從主庫復制數據
- 主數據庫:10.16.1.118 從數據庫:10.16.1.119
創建用戶
MySQL會賦予一些特殊的權限給復制線程,在備庫運行IO線程會建立一個到主庫的TCP/IP連接,這意味著必須在主庫創建一個用戶,并賦予其合適的權限,備庫I/O線程一該用戶名連接到主庫并讀取其二進制文件。
在每臺服務器上都創建復制用戶賬號,并授權:用戶:test 密碼:test1
# 創建用戶
create user 'test'@'10.16.1.118' identified by 'test1';
# 授權,只授予復制和客戶端訪問權限
grant replication slave on *.* to 'test'@'10.16.1.119';#分配權限
為什么每臺服務器都創建復制賬號呢?
用于監控和管理復制賬號的權限,主庫賬號用于從庫連接并進行日志復制使用,而從庫賬號,用于當主庫宕機時,把從庫切換為主庫時,自身所需的配置。
配置主庫與從庫
找到主數據庫的配置文件my.cnf,默認在 /etc/my.cnf
vi /etc/my.cnf
在[mysqld]部分插入
[mysqld] log-bin=mysql-bin #開啟二進制日志 server-id=1 #設置server-id,必須唯一
配置說明
log-bin:設置二進制日志文件的基本名; log-bin-index:設置二進制日志索引文件名; binlog_format:控制二進制日志格式,進而控制了復制類型,三個可選值 ? -STATEMENT:語句復制,默認選項 ? -ROW:行復制 ? -MIXED:混和復制 server-id:服務器設置唯一ID,默認為1,推薦取IP最后部分; sync-binlog:默認為0,為保證不會丟失數據,需設置為1,用于強制每次提交事務時,同步二進制日志到磁盤上。
打開mysql會話shell
mysql -uroot -p
查看master狀態
記錄二進制文件名(mysql-bin.000013)和位置(8369):
mysql> show master status; +------------------+----------+--------------+------------------+-------------------+ | File | Position | Binlog_Do_DB | Binlog_Ignore_DB | Executed_Gtid_Set | +------------------+----------+--------------+------------------+-------------------+ | mysql-bin.000013 | 8369 | | | | +------------------+----------+--------------+------------------+-------------------+ 1 row in set (0.00 sec)
配置從庫Slave
找到從數據庫的配置文件my.cnf,默認在/etc/my.cnf
vi /etc/my.cnf
在[mysqld]部分插入
[mysqld] server-id=2 #設置server-id,必須唯一 relay-log=/var/lib/mysql/mysql-relay-bin #指定中繼日志的位置和命名
啟動主從復制
重啟從mysql,打開從mysql會話,在從數據庫上執行同步SQL語句(需要主服務器主機名,登陸憑據,二進制文件的名稱和位置):
mysql> CHANGE MASTER TO -> MASTER_HOST='10.16.1.118 ', -> MASTER_USER='test', -> MASTER_PASSword='test1', -> MASTER_LOG_FILE='mysql-bin.000013', -> MASTER_LOG_POS=8369; Query OK, 0 rows affected, 2 warnings (0.02 sec)
啟動slave同步進程
mysql>start slave;
查看slave狀態
mysql> show slave statusG *************************** 1. row *************************** Slave_IO_State: Waiting for master to send event Master_Host: 10.16.1.118 Master_User: test Master_Port: 3306 Connect_Retry: 60 Master_Log_File: mysql-relay-bin.000002 Read_Master_Log_Pos: 8369 Relay_Log_File:relay-bin.000002 Relay_Log_Pos: 640 Relay_Master_Log_File: mysql-bin.000013 Slave_IO_Running: Yes Slave_SQL_Running: Yes Replicate_Do_DB: Replicate_Ignore_DB: ......
當Slave_IO_Running和Slave_SQL_Running都為YES的時候就表示主從同步設置成功了。
測試:
- 主數據庫
mysql> show databases; +--------------------+ | Database | +--------------------+ | information_schema | | mysql | | performance_schema | | sys | +--------------------+ 4 rows in set (0.00 sec) mysql> create database test; Query OK, 1 row affected (0.00 sec) mysql> show databases; +--------------------+ | Database | +--------------------+ | information_schema | | mysql | | test | | performance_schema | | sys | +--------------------+ 5 rows in set (0.00 sec)
- 從服務器
mysql> show databases; +--------------------+ | Database | +--------------------+ | information_schema | | mysql | | performance_schema | | sys | +--------------------+ 4 rows in set (0.00 sec) mysql> show databases; +--------------------+ | Database | +--------------------+ | information_schema | | mysql | | test | | performance_schema | | sys | +--------------------+ 5 rows in set (0.00 sec)
注:數據庫復制同步之前,兩者的數據庫、表一定要同步,不然的話會導致同步之前,并沒有把相關更新操作執行的SQL存儲到binlog日志,到時候就會導致數據不一致而報錯。
架構原理對我們的啟發
上面針對于原理上的解析,但是這個東西使用的模式設計,我們從中可以的得到什么啟發?并運用自己的業務系統中,這個是我們學習的另一個重點。作為暖男的我,豈能不幫助大家呢?
這種主從的復制架構實現了日志獲取和數據重放的解耦,允許這兩個過程是異步進行處理的,也就是l/O線程能夠獨立于SQL線程之外點的工作。
大家都知道異步的好處就是實現業務上的解耦,然后對業務進行針對性的操作,從而來提升執行的效率。這里面線程的職責分別是獲取與重放的單一職責處理。同時也方便后面系統的管理。
注:默認的主從方式為異步模式,后面出一篇詳細的同步方式和復制常見問題解決。盡請期待
假如,如果說從庫只有一個工作I/O完成所有的復制工作,那么就會阻塞日志讀取,在來進行重放,并且重放過程中,SQL語句執行時,還會進行語法、詞法分析等。這樣就導致復制效率降低,從而引發延遲問題。所以這也是為什么在這里要涉及業務分析的原因。現在大家對你們的業務有優化的考慮嗎? 歡迎留言討論
但這種架構也限制了復制的過程,其中一點在主庫上并發運行的操作在備庫只能串行化執行。因為只有一個SQL線程來重放中繼日志。在5.7以上有了多線程的模式。
總結一下:MySQL主從可以解決單機性能、存儲不足的問題,所以主從復制是通過擴容的思路來進行數據的分布,從而劃分請求來抵御過大的用戶請求。對于從庫實現的原理分為三大階段,分別是記錄日志、寫入到中繼日志、重放日志。對于架構模式考慮上,也需要代入我們的業務場景中來做分析使用
大家如果有什么需求,也是可以留言的。如有感悟,歡迎關注@php智慧與能力
大冬天的,作為暖男的我,也希望能夠給大家溫暖下去。關注下,咳咳咳咳。如果需要欠缺實戰的同學可以購買專欄額。