一、主從復制架構簡介
通過前面幾篇的介紹中,我們都是在單機上使用Redis進行相關的實踐操作,從本篇起,我們將初步探索一下Redis的集群,而集群中最經典的架構便是主從復制架構。那么,我們首先來了解一下神馬是主從復制架構?
1.1 源於關系數據庫的讀寫分離
隨着網站業務的不斷發展,用戶量的不斷增加,數據量也成倍的增長,數據庫的訪問量也呈線性地增長。特別是在用戶訪問高峰期間,並發訪問量突然增大,數據庫的負載壓力也會增大,如果架構方案不夠健壯,那么數據庫服務器很有可能在高並發訪問負載壓力下宕機,造成數據訪問服務的失效,從而導致網站的業務中斷,給公司和用戶造成雙重損失。那么,有木有一種方案能夠解決此問題,使得數據庫不再因為負載壓力過高而成為網站的瓶頸呢?答案肯定是有的。
目前,大部分的主流關系型數據庫都提供了主從熱備功能,通過配置兩台(或多台)數據庫的主從關系,可以將一台數據庫服務器的數據更新同步到另一台服務器上。網站可以利用數據庫的這一功能,實現數據庫的讀寫分離,從而改善數據庫的負載壓力。
利用數據庫的讀寫分離,Web服務器在寫數據的時候,訪問主數據庫(Master),主數據庫通過主從復制機制將數據更新同步到從數據庫(Slave),這樣當Web服務器讀數據的時候,就可以通過從數據庫獲得數據。這一方案使得在大量讀操作的Web應用可以輕松地讀取數據,而主數據庫也只會承受少量的寫入操作,還可以實現數據熱備份,可謂是一舉兩得的方案。
1.2 基於MySQL的數據復制流程
剛剛我們了解了關系型數據庫的讀寫分離能夠實現數據庫的主從架構,那么主從架構中最重要的數據復制又是怎么一回事呢?MySQL作為最流行的關系型數據庫之一,通過了解MySQL的數據復制流程,會使得我們對Redis主從復制的認知會有一定的幫助。
從上圖來看,整體上有如下三個步湊:
(1)Master將改變記錄到二進制日志(binary log)中(這些記錄叫做二進制日志事件,binary log events);
(2)Slave將Master的二進制日志事件(binary log events)拷貝到它的中繼日志(relay log);
PS:從圖中可以看出,Slave服務器中有一個I/O線程(I/O Thread)在不停地監聽Master的二進制日志(Binary Log)是否有更新:如果沒有它會睡眠等待Master產生新的日志事件;如果有新的日志事件(Log Events),則會將其拷貝至Slave服務器中的中繼日志(Relay Log)。
(3)Slave重做中繼日志(Relay Log)中的事件,將Master上的改變反映到它自己的數據庫中。
PS:從圖中可以看出,Slave服務器中有一個SQL線程(SQL Thread)從中繼日志讀取事件,並重做其中的事件從而更新Slave的數據,使其與Master中的數據一致。只要該線程與I/O線程保持一致,中繼日志通常會位於OS的緩存中,所以中繼日志的開銷很小。
經過了上面的簡單簡介,我們初步了解了什么是主從復制,以及在關系型數據庫中數據是如何復制的。在這,我們不由疑問在Redis中又是怎樣實現數據復制的呢?別急,我們先來實踐一下,先對主從復制得到一個感性認識,再由感性認識升到理性認識去理解一下。So,Let's start doing.
二、在單機上模擬主從復制架構實踐
2.1 拷貝兩個服務到指定磁盤文件夾
(1)將第一篇中下載的Redis服務文件夾復制兩份,並給兩個文件夾取名為:RedisMasterService與RedisSlaveService;
(2)將兩個文件夾拷貝到Windows中指定的文件夾中,我這里統一拷貝到E:\下,便於后面的啟動測試;
2.2 分別修改Master和Slave的配置文件
(1)修改Master服務的配置文件(redis.conf)
這里,主要修改一下Master服務所綁定的IP地址(即Master服務器的IP地址),我這里由於是在本機進行的,所以直接設置為127.0.0.1即可。通過在redis.conf中搜索"bind",可以看到默認配置已經有了#bind 127.0.0.1的字符串,我們要做的只是將這句取消注釋就可以了。
PS:建議使用類似於EditPlus、Notepad++等專業一點的編輯器打開redis.conf配置文件,這樣查找和編輯都比較直觀明了。如果這些你都沒有,那你可以用Visual Studio打開來編輯(如果你連VS也沒有,我只能呵呵了,你用記事本編輯吧,么么嗒)。好吧,我就沒有EditPlus和Notepad++,重裝了系統就沒有裝這些編輯器,被你們看穿了。
(2)修改Slave服務的配置文件(redis.conf)
①修改Slave綁定的端口號:這里因為Master和Slave都在一台機器上,因此需要修改端口號以區分兩個Redis服務。如果不修改,則默認端口號位6379;修改方法也很簡單,搜索"port"關鍵詞,將port 6379改為port 6380即可。注意,這里只要不為6379即可,你可以隨便改,6380只是我這里設置的端口號而已。
②修改Slave綁定的IP地址,這里和Master一致,都改為bind 127.0.0.1即可;
③修改Master與Slave的對應關系配置:搜索"slaveof"關鍵詞,會找到這樣一句:# slaveof <masterip> <masterport>。我們要做的就是,取消這句話的注釋,並將<masterip>和<masterport>改為主服務器的IP地址和端口號,這里我們的Master服務還是本機,因此改為slaveof 127.0.0.1 6379即可。
2.3 分別啟動Master和Slave的服務
(1)啟動Master的服務:通過cmd跳轉到Master文件夾下,使用redis-server.exe redis.conf命令啟動Master服務,這里需要指定redis.conf是因為我們剛剛編輯修改了redis.conf,需要重新加載配置文件;
(2)啟動Slave的服務:操作步湊同上,也是redis-server.exe redis.conf,注意別忘了加上redis.conf這句;
(3)Slave服務啟動完成后,我們可以看到cmd中出現了一些不一樣的日志信息。這里,我們來簡單的了解一下:
①首先,在Slave服務的命令行中出現如下的日志信息:
這里,可以看到通過編輯了配置文件后,Slave服務在啟動后便會主動地連接Master服務(這里主要是通過發送SYNC命令請求同步連接),中間Master會發一個PING命令來檢測Slave的存活狀態(存在則繼續復制,失效則終止后面步湊),然后等待Master發送回其內存快照文件(這里你先將這個內存快照文件理解為一個數據備份文件或日志)。可以看出,這里Slave已經接受了Master的36 bytes數據,並將數據存儲到了內存中,最終成功完成與Master的同步。
②其次,在Master服務的命令行中出現了如下的日志信息:
這里,Master通過檢測發現有Slave發送了SYNC命令來判斷Master中是否有內存快照(或者更新的內存快照),沒有則開始執行內存快照(主要是將),有則等待其結束然后將快照文件發送給Slave。至此,Master端就終止了此次的SYNC通信。而Slave則會將數據快照文件保存到本地,待接收完成后,清空內存表,重新讀取Master發來的內存快照文件,形成一個狀態的循環。
2.4 在命令行中進行簡單數據讀寫測試
PS:這里主要是通過新開兩個cmd窗口來模擬兩個客戶端來操作Redis
(1)在Master服務中寫入一個Key/Value數據對
①首先,新開一個cmd,並在cmd中跳轉到Master文件夾下,使用redis-cli.exe -p 6379命令進入MasterRedis客戶端命令模式
②然后,為了確保測試成功,我們先將當前的Master中的數據清空一下,使用flushdb命令清空已存的所有數據(Redis會定期將數據從內存寫入文件中實現數據的持久化)
③最后,通過一個簡單的set命令將Key為testkey,Value為edisonchou的鍵值對數據存入Master中;
(2)在Slave中讀取剛剛在Master寫入的Key的Value數據
①首先,新開一個cmd,並在cmd中跳轉到Slave文件夾下,使用redis-cli.exe -p 6380命令進入SlaveRedis客戶端命令模式
②其次,通過一個簡單的get命令查詢是否存在Key為testkey的數據
從上圖可以看到,我們的Slave成功獲取了從Master發來的數據更改,並保存到了自己的數據文件中,使得我們通過訪問Slave也能夠得到在Master上寫入的數據。
(3)再來看看Master和Redis的服務的cmd窗口,是不是又多了一些日志信息:Master將1個changes寫入數據快照文件,發送給了Slave。而Slave也將這個change存入自己的數據文件並保存,也就使得{testkey,edisonchou}的這個數據在兩個Redis服務中都有了,或者說在Slave中成功復制了Master中的這個key/value對。
2.5 在程序中進行簡單數據讀寫測試
(1)新建一個C#的控制台項目,並在項目文件夾中新建一個Lib文件夾用以存放Redis的.Net驅動(ServiceStack.Redis的dll);
(2)寫入以下的代碼:
1 public static IRedisClientsManager redisClientManager = new PooledRedisClientManager(new string[] 2 { 3 "127.0.0.1:6379","127.0.0.1:6380" 4 }); 5 6 public static IRedisClient redisClient = redisClientManager.GetClient(); 7 8 static void Main(string[] args) 9 { 10 if(redisClient != null) 11 { 12 redisClient.Set<string>("UserName","EdisonChou"); 13 string userName = redisClient.Get<string>("UserName"); 14 15 Console.WriteLine("Hello,My name is {0}.Nice to meet you!", userName); 16 } 17 Console.ReadKey(); 18 }
通過調試運行,可以得到以下的結果:
(3)再通過Master和Slave的命令行客戶端查看存儲情況:
①Master中:get UserName
②Slave中:get UserName
三、回頭再看Redis主從復制模型
3.1 Redis的兩種存儲方式
Redis是一個支持持久化的內存數據庫,如何實現持久化呢?答案是Redis需要經常將內存中的數據同步到硬盤中來保證持久化。那么,Redis通過什么方式來存儲數據呢?
Redis支持兩種持久化方式:
(1)SnapsHotting:數據快照,這也是默認的方式。此方式是把數據做一個備份,將數據存儲到二進制文件中去(這里可以對比本文最開始介紹的MySQL的復制過程)。這個二進制的文件默認的文件名稱為dump.rdb。我們還可以通過配置設置自動做快照持久化的方式,比如:我們可以配置Redis在n秒內如果超過m個key鍵修改就自動做快照。
但是,快照方式雖然比較完美,但扔存在一定缺陷:由於快照方式是在一定間隔時間做一次的,所以如果Redis意外宕掉的話,就會丟失最后一次快照后的所有修改。因此在完美主義者的推動下,作者增加了aof方式,也就是下面我們所要介紹的方式。
(2)Append-Only File:縮寫為aof,意為只增文件。此方式在寫入內存數據的同時將操作命令保存到日志文件(默認命名為appendonly.aof),在Redis遇到意外情況后重啟時可以通過日志文件恢復數據庫狀態。但是,正因為如此,在一個並發更改上萬的系統中,命令日志是一個非常龐大的數據(日志文件會越來越大),管理維護成本非常高,恢復重建時間也會非常長,這樣會導致失去aof高可用性本意(aof的本意其實是數據可靠性及高可用性)。另外更重要的是Redis是一個內存數據結構模型,所有的優勢都是建立在對內存復雜數據結構高效的原子操作上,這樣就看出aof是一個非常不協調的部分。
剛剛我們說到,Redis默認的存儲方式快照方式,那么如果我們要開啟aof模式呢?只需要在redis.conf配置文件中將aof模式從no改為yes即可。
3.2 Redis的數據同步流程
首先,Redis的復制功能是完全建立在之前了解的基於內存快照的持久化策略基礎上的,也就是說無論你的持久化策略選擇的是什么,只要用到了Redis的復制功能,就一定會有內存快照發生。
(1)Slave端在配置文件中添加了slave of <masterip> <masterport>的指令,於是Slave啟動時讀取配置文件並向Master發送SYNC的命令,然后等待Master發送回其內存快照文件;
(2)首先,先說明一下:無論是第一次連接還是重新連接,Master都會啟動一個后台進程,將數據快照保存到數據文件(例如:dump.rdb)中,同時Master會記錄所有修改數據的命令並緩存在數據文件中。這里緊接第一步,Master接收到Slave發來的SYNC命令后,會首先向Slave發送一個PING命令來檢測Slave的存活狀態(主要看Slave是否失效,沒有失效則繼續后續操作,失效了則不繼續了)。然后,(當后台進程完成數據緩存操作后)Master就發送數據文件依次地實時發送給Slave。
PS:不管什么原因導致Slave和Master斷開重連都會重復以上過程。
(3)Slave接收到Master發來的數據文件之后,會保存到本地,待接收完成后,加載到內存中,這就完成了一次數據復制。之后,Master只要一有數據更新,便會寫入數據文件並發送給各個Slave,而Slave也會一直監聽Master發來的更新,並重新加載,形成一個數據同步的循環。
(4)若Slave出現故障導致宕機,恢復正常后會自動重新連接,Master收到Slave的連接后,將其完整的數據文件發送給Slave,如果Mater同時收到多個Slave發來的同步請求,Master只會在后台啟動一個進程保存數據文件,然后將其發送給所有的Slave,確保Slave正常。但是,在大數據量下,重新獲取整個完整的Master快照,一方面Slave恢復的時間會非常慢,另一方面也會給主庫帶來壓力。
四、學習小結
本篇我首先簡單介紹了一下主從復制架構的基本概念,然后在Windows上單機模擬實現Redis的主從復制架構並通過數據讀寫命令進行簡單測試並驗證數據是否復制成功,最后通過了解Redis的主從復制模型知道了Redis是如何進行數據復制的,從基礎理論到基礎實踐再到基礎理論,對於主從復制也不算陌生了(至少混了個臉熟)。不知不覺又2:30了,洗洗睡吧,今天(8月2日)還是七夕情人節,誰有多余的情人借用一下,我保證8月3日奉還(為毛我還木有女盆友,天理不容啊!)。
最后,如果各位園友覺得我的文章不錯或者對你有用,麻煩點一下“推薦”,讓我更有動力寫下去,謝謝!
參考文獻
(1)Linux中國,《最大的Redis集群:新浪Redis集群揭秘》,http://www.linuxeden.com/html/itnews/20131010/144377.html
(2)田琪,《Redis復制於可擴展集群搭建》,http://www.infoq.com/cn/articles/tq-redis-copy-build-scalable-cluster
(3)Stephen Liu,《Redis學習手冊(Key操作手冊)》,http://www.cnblogs.com/stephen-liu74/archive/2012/03/26/2356951.html
(4)zfl092005,《Redis主從配置及主從切換》,http://blog.csdn.net/zfl092005/article/details/17523945
(5)晨風微涼,《構建高性能數據庫緩存之Redis主從復制》,http://database.51cto.com/art/201407/444555.htm
(6)傳智播客王承偉,《數據優化之Redis公開課》,http://bbs.itcast.cn/thread-26525-1-1.html
(7)美舞映璜,《Redis配置文件redis.conf說明》,http://blog.sina.com.cn/s/blog_636415010101970j.html
附件下載
(1)RedisDemo.MasterAndSlave:http://pan.baidu.com/s/1dD1nIgT