引言
咱們都知道redis性能很高,單機版的qps能夠達到10萬+,可是即便如此咱們也不會在生產環境去搭建單機版本的redis,緣由以下:node
一、由於存在單點,若是進程掛掉或者機器宕機,redis不可用redis
二、redis的讀和寫全在一臺機器上,沒法知足讀多寫少的場景。數據庫
主從流程架構
Redis雖然讀取寫入的速度都特別快,可是也會產生讀壓力特別大的狀況。爲了分擔讀壓力,Redis支持主從複製,Redis的主從結構能夠採用一主多從或者級聯結構,下圖爲級聯結構。安全
1.搭建主從架構
接下來教你們如何搭建主從架構的redis,咱們能夠根據不一樣的端口號模擬在單機啓動3個redis進程,端口號6379的爲master,端口號6370 和6371的爲slave。服務器
咱們首先複製redis.conf文件2份,分別爲redis_6370.conf 和redis_6371.conf ,而後分別對這兩個文件修改以下參數:網絡
1slaveof 127.0.0.1 6379架構
啓動master:併發
1./redis-server redis.confless
啓動slave1:socket
1./redis-server redis_6370.conf
啓動slave2:
1./redis-server redis_6371.conf
接下來咱們在master上設置一個值,以下圖:
咱們看下slave1 和slave2 上是否能取到該值,以下圖:
能夠看到此時slave1和slave2已經有相應的值,說明同步完成。
2. Redis各配置參數介紹:
- slaveof
- 設置從節點指向要複製的主節點
- masterauth
- 複製使用的密碼
- slave-serve-stale-data yes
- 當slave丟失與master的鏈接時,或者slave仍然在於master進行數據同步時(尚未與master保持一致)
- slave能夠有兩種方式來響應客戶端請求:
- 1) 若是 slave-serve-stale-data 設置成 'yes' (the default) slave會仍然響應客戶端請求,此時可能會有問題。
- 2) 若是 slave-serve-stale data設置成 'no' slave會返回"SYNC with master in progress"這樣的錯誤信息。 但 INFO 和SLAVEOF命令除外。
- slave-read-only yes
- 從節點是否只讀。
- repl-diskless-sync no (no, Disk-backed, Diskless )
- 複製集同步策略:磁盤或者socket
- no: 表示不啓用
- Disk-backed, 基於磁盤的
- Diskless: 無磁盤的
- 新slave鏈接或者老slave從新鏈接時候不能只接收不一樣,得作一個全同步。須要一個新的RDB文件dump出來,而後從master傳到slave。
- 能夠有兩種狀況:
- 一、基於硬盤(disk-backed):master建立一個新進程dump RDB,完事以後由父進程(即主進程)增量傳給slaves。
- 二、基於socket(diskless):master建立一個新進程直接dump RDB到slave的socket,不通過主進程,不通過硬盤。
- 基於硬盤的話,RDB文件建立後,一旦建立完畢,能夠同時服務更多的slave就是一對多。基於socket的話, 新slave來了後,得排隊(若是超出了repl-diskless-sync-delay還沒來),完事兒一個再進行下一個。
- 當用diskless的時候,master等待一個repl-diskless-sync-delay的秒數,若是沒slave來的話,就直接傳,後來的得排隊等了。不然就能夠一塊兒傳。
- disk較慢,而且網絡較快的時候,能夠用diskless。(默認用disk-based)
- repl-diskless-sync-delay 5
- 若是啓用無磁盤的同步,在接入進來延遲5秒之後在同步數據
- repl-ping-slave-period 10
- 主節點每隔10秒探測一次從節點是否存活。
- repl-timeout 60
- 主從節點若是網絡斷開超時60秒未通訊,就超時斷開鏈接
- repl-disable-tcp-nodelay no
- 是否啓用tcp的延遲發送, 就是說數據量比TCP的首部信息加起來還要小,這樣反覆的發送較小的數據很佔用資源,當開啓之後就會累積到必定的數據量在發送。可是對於較高的場景,若是延遲發送的時間過長,可能主持同步時間就會延遲。
- repl-backlog-size 1mb
- 若是主Redis等了一段時間以後,仍是沒法鏈接到從Redis,那麼緩衝隊列中的數據將被清理掉。咱們能夠設置主Redis要等待的時間長度。若是設置爲0,則表示永遠不清理。默認是1個小時。
- slave-priority 100
- 複製集羣中,主節點故障時,sentinel應用場景中的主節點選舉時使用的優先級;數字越小優先級越高,但0表示不參與選舉;
- min-slaves-to-write 3
- 主節點僅容許其可以通訊的從節點數量大於等於此處的值時接受寫操做;意思就是說,咱們的redis集羣最少要有3臺以上才能知足咱們線上的業務,若是低於這個數,將沒法啓用.
- min-slaves-max-lag 10
- 從節點延遲時長滯後超出此處指定的時長時,主節點會拒絕寫入操做;
主從底層實現原理
redis的複製功能主要分爲同步(sync)和命令傳播(command propagate)兩個操做:
- 同步操做用於將從服務器的數據庫狀態更新至主服務器當前所處的數據庫狀態;
- 命令傳播操做則用於在主服務器的數據庫狀態被修改,致使主從服務器的數據庫狀態出現不一致時,讓主從服務器的數據庫從新回到一致狀態。
如下爲redis2.8以前版本的同步實現原理:
1. 同步
當客戶端向從服務器發送SLAVEOF命令,要求從服務器複製主服務器時,從服務器首先須要執行同步操做,也便是,將從服務器的數據庫狀態更新至主服務器當前所處的數據庫狀態。
從服務器對主服務器的同步操做須要經過向主服務器發送SYNC命令來完成,如下是SYNC命令的執行步驟:
- 從服務器向主服務器發送SYNC命令;
- 收到SYNC命令的主服務器執行BGSAVE命令,在後臺生成一個RDB文件,並使用一個緩衝區記錄從如今開始執行的全部寫命令;
- 當主服務器的BGSAVE命令執行完畢時,主服務器會將BGSAVE命令生成的RDB文件發送給從服務器,從服務器接收並載入這個RDB文件,將本身的數據庫狀態更新至主服務器執行BGSAVE命令時的數據庫狀態。
- 主服務器將記錄在緩衝區裏面的全部寫命令發送給從服務器,從服務器執行這些寫命令,將本身的數據庫狀態更新至主服務器數據庫當前所處的狀態。
2. 命令傳播
在執行完同步操做以後,主從服務器之間數據庫狀態已經相同了。但這個狀態並不是一成不變,若是主服務器執行了寫操做,那麼主服務器的數據庫狀態就會修改,並致使主從服務器狀態再也不一致。
因此爲了讓主從服務器再次回到一致狀態,主服務器須要對從服務器執行命令傳播操做:主服務器會將本身執行的寫命令,也便是形成主從服務器不一致的那條寫命令,發送給從服務器執行,當從服務器執行了相同的寫命令以後,主從服務器將再次回到一致狀態。
3. redis2.8以前版本複製的缺陷
在Redis中,從服務器對主服務器的複製能夠分爲如下兩種狀況:
- 初次複製:從服務器之前沒有複製過任何主服務器,或者從服務器當前要複製的主服務器和上一次複製的主服務器不一樣;
- 斷線後重複製:處於命令傳播階段的主從服務器由於網絡緣由而中斷了複製,但從服務器經過自動重鏈接從新連上了主服務器,並繼續複製主服務器。
對於初次複製來講,舊版複製功能可以很好地完成任務,但對於斷線後重複製來講,舊版複製功能雖然也能讓主從服務器從新回到一致狀態,但效率卻很是低。
SYNC命令是很是消耗資源的,由於每次執行SYNC命令,主從服務器須要執行一下操做:
- 主服務器須要執行BGSAVE命令來生成RDB文件,這個生成操做會耗費主服務器大量的CPU、內存和磁盤I/O資源;
- 主服務器須要將本身生成的RDB文件發送給從服務器,這個發送操做會耗費主從服務器大量的網絡資源(帶寬和流量),並對主服務器響應命令請求的時間產生影響;
- 接收到RDB文件的從服務器須要載入主服務器發來的RDB文件,而且在載入期間,從服務器會由於阻塞而沒辦法處理命令請求。
SYNC是一個如此消耗資源的命令,因此Redis最好在真須要的時候才須要執行SYNC命令
新版複製功能的實現
爲了解決舊版複製功能在處理斷線重複制狀況時的低效問題,Redis從2.8版本開始,使用PSYNC命令代替SYNC命令來執行復制時的同步操做。
PSYNC命令具備完整重同步(full resynchronization)和部分重同步(partial resynchronization)兩種模式:
- 其中完整重同步用於處理初次複製狀況:完整重同步的執行步驟和SYNC命令的執行步驟基本同樣,它們都是經過讓主服務器建立併發送RDB文件,以及向從服務器發送保存在緩衝區裏面的寫命令來進行同步;
- 而部分重同步則用於處理斷線後重複製狀況:當從服務器在斷線後從新鏈接主服務器時,若是條件容許,主服務器能夠將主從服務器鏈接斷開期間執行的寫命令發送給從服務器,從服務器只要接收並執行這些寫命令,就能夠將數據庫更新至主服務器當前所處的狀態
部分重同步實現
部分重同步功能由如下三個部分構成:
- 主服務器的複製偏移量(replication offset)和從服務器的複製偏移量;
- 主服務器的複製積壓緩衝區(replication backlog);
- 服務器的運行ID(run ID)
複製偏移量
執行復制的雙方——主服務器和從服務器會分別維護一個複製偏移量:
- 主服務器每次向從服務器傳播N個字節的數據時,就將本身的複製偏移量的值加上N;
- 從服務器每次收到主服務器傳播來的N個字節的數據時,就將本身的複製偏移量的值加上N;
經過對比主從服務器的複製偏移量,程序能夠很容易地知道主從服務器是否處於一致狀態:
- 若是主從服務器處於一致狀態,那麼主從服務器二者的偏移量老是相同的;
- 相反,若是主從服務器二者的偏移量並不相同,那麼說明主從服務器並未處於一致狀態。
假設Slave A在短線以後當即從新鏈接master,而且成功,接下來slave A向master 發送PSYNC命令,master對比slave A的偏移量發現不相等,那麼這時候是使用徹底同步仍是部分同步呢?若是部分同步的話,master如何補償slave A在短線期間的數據呢?這就和下面所說的複製積壓緩衝區有關。
複製積壓緩衝區
複製積壓緩衝區是由主服務器維護的一個固定長度(fixed-size)先進先出(FIFO)隊列,默認大小爲1MB。
和普通先進先出隊列隨着元素的增長和減小而動態調整長度不一樣,固定長度先進先出隊列的長度是固定的,當入隊元素的數量大於隊列長度時,最早入隊的元素會被彈出,而新元素會被放入隊列。
當主服務器進行命令傳播時,它不只會將寫命令發送給全部從服務器,還會將寫命令入隊到複製積壓緩衝區裏面,如圖所示:
所以,主服務器的複製積壓緩衝區裏面會保存着一部分最近傳播的寫命令,而且複製積壓緩衝區會爲隊列中的每一個字節記錄相應的複製偏移量,就像下表所示的那樣。
當從服務器從新連上主服務器時,從服務器會經過PSYNC命令將本身的複製偏移量offset發送給主服務器,主服務器會根據這個複製偏移量來決定對從服務器執行何種同步操做:
- 若是offset偏移量以後的數據仍然存在於複製積壓緩衝區裏面,那麼主服務器將對從服務器執行部分重同步操做;
- 相反,若是offset偏移量以後的數據已經不存在於複製積壓緩衝區,那麼主服務器將對從服務器執行完整重同步操做。
複製積壓緩衝區的大小
Redis爲複製積壓緩衝區設置的默認大小爲1MB,若是主服務器須要執行大量寫命令,又或者主從服務器斷線後重鏈接所需的時間比較長,那麼這個大小也許並不合適。若是複製積壓緩衝區的大小設置得不恰當,那麼PSYNC命令的複製重同步模式就不能正常發揮做用,所以,正確估算和設置複製積壓緩衝區的大小很是重要。
複製積壓緩衝區的最小大小能夠根據公式second*write_size_per_second來估算:
- 其中second爲從服務器斷線後從新鏈接上主服務器所需的平均時間(以秒計算);
- 而write_size_per_second則是主服務器平均每秒產生的寫命令數據量(協議格式的寫命令的長度總和);
例如,若是主服務器平均每秒產生1 MB的寫數據,而從服務器斷線以後平均要3秒才能從新鏈接上主服務器,那麼複製積壓緩衝區的大小就不能低於3MB。
爲了安全起見,能夠將複製積壓緩衝區的大小設2secondwrite_size_per_second,這樣能夠保證絕大部分斷線狀況都能用部分重同步來處理。至於複製積壓緩衝區大小的修改方法,能夠參考配置文件中關於repl-backlog-size選項的說明。
服務器運行ID
除了複製偏移量和複製積壓緩衝區以外,實現部分重同步還須要用到服務器運行ID(run ID):
- 每一個Redis服務器,不論主服務器仍是從服務,都會有本身的運行ID;
- 運行ID在服務器啓動時自動生成,由40個隨機的十六進制字符組成,例如53b9b28df8042fdc9ab5e3fcbbbabff1d5dce2b3;
當從服務器對主服務器進行初次複製時,主服務器會將本身的運行ID傳送給從服務器,而從服務器則會將這個運行ID保存起來.
當從服務器斷線並從新連上一個主服務器時,從服務器將向當前鏈接的主服務器發送以前保存的運行ID:
- 若是從服務器保存的運行ID和當前鏈接的主服務器的運行ID相同,那麼說明從服務器斷線以前複製的就是當前鏈接的這個主服務器,主服務器能夠繼續嘗試執行部分重同步操做;
- 相反地,若是從服務器保存的運行ID和當前鏈接的主服務器的運行ID並不相同,那麼說明從服務器斷線以前複製的主服務器並非當前鏈接的這個主服務器,主服務器將對從服務器執行完整重同步操做。
PSYNC命令的實現
PSYNC命令的調用方法有兩種:
- 若是從服務器之前沒有複製過任何主服務器,或者以前執行過SLAVEOF no one命令,那麼從服務器在開始一次新的複製時將向主服務器發送PSYNC ? -1命令,主動請求主服務器進行完整重同步(由於這時不可能執行部分重同步);
- 相反地,若是從服務器已經複製過某個主服務器,那麼從服務器在開始一次新的複製時將向主服務器發送PSYNC命令:其中runid是上一次複製的主服務器的運行ID,而offset則是從服務器當前的複製偏移量,接收到這個命令的主服務器會經過這兩個參數來判斷應該對從服務器執行哪一種同步操做。
根據狀況,接收到PSYNC命令的主服務器會向從服務器返回如下三種回覆的其中一種:
- 若是主服務器返回+FULLRESYNC,那麼表示主服務器將與從服務器執行完整重同步操做:其中runid是這個主服務器的運行ID,從服務器會將這個ID保存起來,在下一次發送PSYNC命令時使用;而offset則是主服務器當前的複製偏移量,從服務器會將這個值做爲本身的初始化偏移量;
- 若是主服務器返回+CONTINUE,那麼表示主服務器將與從服務器執行部分重同步操做,從服務器只要等着主服務器將本身缺乏的那部分數據發送過來就能夠了;
- 若是主服務器返回-ERR回覆,那麼表示主服務器的版本低於Redis 2.8,它識別不了PSYNC命令,從服務器將向主服務器發送SYNC命令,並與主服務器執行完整同步操做。