複製

複製

通過持久化功能,Redis保證了即使在服務器重啓的情況下也不會損失(或少量損失)數據。但是由於數據是存儲在一臺服務器上的,如果這臺服務器的硬盤出現故障,也會導致數據丟失。爲了避免單點故障,我們希望將數據庫複製多個副本以部署在不同的服務器上,即使有一臺服務器出現故障其他服務器依然可以繼續提供服務。這就要求當一臺服務器上的數據庫更新後,可以自動將更新的數據同步到其他服務器上,Redis提供了複製(replication)功能可以自動實現同步的過程。

       同步後的數據庫分爲兩類,一類是主數據庫(master),一類是從數據庫(slave)。主數據庫可以進行讀寫操作,當發生寫操作時自動將數據同步給從數據庫。而從數據庫一般是隻讀的,並接受主數據庫同步過來的數據。一個主數據庫可以擁有多個從數據庫,而一個從數據庫只能擁有一個主數據庫。

 

添加從數據庫

使用命令

開啓一個Redis服務實例

redis-server ./redis.conf

使用命令簡單添加實例的從服務

redis-server --port 6380 --slaveof 127.0.0.1 6379

使用配置

在從服務器的配置文件redis-6380.conf添加配置

slaveof <mater-id> <master-port>

服務器信息

連接到主服務器,使用info replication命令查看6379的角色role爲master(主數據庫),連接的從數據庫connected_salves爲1。

redis-cli -p 6379

127.0.0.1:6379> info replication

# Replication

role:master

connected_slaves:1

slave0:ip=127.0.0.1,port=6380,state=online,offset=70,lag=0

master_replid:e2bc3db13b3e91ec2ea0daf130768fc8184b8052

master_replid2:0000000000000000000000000000000000000000

master_repl_offset:70

second_repl_offset:-1

repl_backlog_active:1

repl_backlog_size:1048576

repl_backlog_first_byte_offset:1

repl_backlog_histlen:70

連接到從服務器,使用info replication命令查看6379的角色role爲salve(從數據庫)。

 [root@centos7 ~]# redis-cli -p 6380

127.0.0.1:6380> info replication

# Replication

role:slave

master_host:127.0.0.1

master_port:6379

master_link_status:up

master_last_io_seconds_ago:10

master_sync_in_progress:0

slave_repl_offset:154

slave_priority:100

slave_read_only:1

connected_slaves:0

master_replid:e2bc3db13b3e91ec2ea0daf130768fc8184b8052

master_replid2:0000000000000000000000000000000000000000

master_repl_offset:154

second_repl_offset:-1

repl_backlog_active:1

repl_backlog_size:1048576

repl_backlog_first_byte_offset:1

repl_backlog_histlen:154

可以通過設置從數據庫的配置文件中的slave-read-only爲no使從數據庫可寫,但是儘量不要使用,因爲沒有意義。

除了通過配置文件和客戶端來設置slaveof參數,還可以在運行時使用slaveof命令更換主數據庫,如果當前數據庫已經有了主數據庫,那麼就會停止和原來的主數據庫同步而和我們新指定的主數據庫同步。

 

原理

1、初始化連接階段

當從數據庫啓動後,會向主數據庫發送SYNC命令,主數據接收到SYNC命令後開始在後臺保存快照(即RDB持久化過程),並將保存期間收到的客戶端命令緩存起來。當快照完成後,Redis將快照文件和所有緩存的命令發送給從數據庫。從數據庫收到後,會載入快照文件並執行收到的緩存的命令。Redis使用Tcp協議通信,我們使用telnet工具僞裝爲從數據庫來了解同步的過程。

連接主數據庫

telnet 127.0.0.1 6379

Trying 127.0.0.1...

Connected to 127.0.0.1.

Escape character is '^]'.

發送PING命令確認主數據庫是否可以連接

PING

+PONG

主數據庫回覆+PONG,說明已經連上了主數據庫

如果主數據庫需要密碼才能連接,我們發送AUTH命令進行驗證,而後向主數據庫發送REPLCONF命令說明自己的端口號

REPLCONF listening-port 6380

2、數據同步階段

從數據庫發送SYNC命令開始數據同步

aof-preamble▒▒▒msg2worldmsg3hellomeshellofoo▒msghellomsg1helloǺ▒'xtermxtermxtermxtermxtermxtermxterm

*1

$4

PING

*2

$6

SELECT

$1

0

*3

$3

set

$4

msg3

$5

china

從數據庫會將收到的內容寫入到硬盤上的臨時文件,當寫入完成後從數據庫將這個臨時文件替換REDB快照文件,之後的操作與RDB持久化時啓動恢復的過程一樣。在同步的過程中,從數據庫並不會阻塞,而是繼續處理客戶端發來的命令。默認情況下,從數據庫會用同步前的數據對命令進行響應。可以配置slave-serve-stale-data參數爲no來使從數據庫在同步完成前對所有命令(除INFO和SLAVEOF)都回復錯誤:“SYNC with master in progress.”

之後主數據的任何數據變化都會同步給從數據庫,同步的內容和Redis通信協議一樣。

例如

主數據庫客戶端輸入

127.0.0.1:6379> set hell world

OK

127.0.0.1:6379>

從數據庫客戶端端返回

*3

$3

set

$4

hell

$4

world

3、心跳檢測階段

當數據同步完成以後,在此後的時間裏主從維護着心跳檢查來確認對方是否在線,每隔一段時間(默認10秒,通過repl-ping-slave-period參數指定)主數據庫向從數據庫發送PING命令判斷從數據庫是否在線,而從數據庫每秒1次向主節點發送REPLCONF ACK命令,命令格式爲:REPLCONF ACK {offset},其中offset指從數據庫保存的複製偏移量,作用一是彙報自己複製偏移量,主數據庫會對比複製偏移量向從節點發送未同步的命令,作用二在於判斷主數據庫是否在線,從庫接送命令並執行,最終實現與主庫數據相同

從服務器客戶端

*1

$4

PING

*1

$4

PING

全量、增量複製

Redis2.6之前,在數據同步階段,從數據庫會向主數據庫發送SYNC命令。同時主數據庫接收到SYNC命令後會執行bgsave命令在後臺保存快照(即RDB持久化的過程),並將保存快照期間接收到的命令緩存起來。當快照完成後,Redis會將快照文件和所有緩存的命令發送給從數據庫。從數據庫收到後,會載入快照文件並執行收到的緩存的命令。這叫做全量複製。

但這時會出現一個問題,當主從數據庫之間的連接斷開重連後,會重新進行復制初始化,即使從數據庫只是有幾條命令沒有收到,主數據庫也必須要將數據庫的所有數據重新傳輸給從數據庫。這使得主從數據庫斷線重連後的數據恢復過程效率很低下。

Redis2.8版的一個重要改進就是斷線重連能夠支持有條件的增量數據傳輸。當從數據庫重新連接上主數據庫後,主數據庫只需要將斷線期間執行的命令傳送給從數據庫,從而大大提高Redis複製的實用性。

增量複製的幾個概念。

1offset(複製偏移量):

主庫和從庫分別各自維護一個複製偏移量(可以使用info replication查看),用於標識自己複製的情況,在主庫中代表主庫向從庫傳遞的字節數,在從庫中代表從庫同步的字節數。每當主庫向從庫發送N個字節數據時,主庫的offset增加N,從庫每收到主庫傳來的N個字節數據時,從庫的offset增加N。因此offset總是不斷增大,這也是判斷主從數據是否同步的標誌,若主從的offset相同則表示數據同步量,不同則表示數據不同步。

2replication backlog buffer(複製積壓緩衝區):

複製積壓緩衝區是一個固定長度的FIFO隊列,大小由配置參數repl-backlog-size指定,默認大小1MB。需要注意的是該緩衝區由master維護並且有且只有一個,所有slave共享此緩衝區,其作用在於備份最近主庫發送給從庫的數據。

在主從命令傳播階段,主庫除了將寫命令發送給從庫外,還會發送一份到複製積壓緩衝區,作爲寫命令的備份。除了存儲最近的寫命令,複製積壓緩衝區中還存儲了每個字節相應的複製偏移量,由於複製積壓緩衝區固定大小先進先出的隊列,所以它總是保存的是最近redis執行的命令。

3run_id(服務器運行的唯一ID)

每個redis實例在啓動時候,都會隨機生成一個長度爲40的唯一字符串來標識當前運行的redis節點,查看此id可通過命令info server查看。

當主從複製在初次複製時,主庫將自己的runid發送給從庫,從庫將這個runid保存起來,當斷線重連時,從庫會將這個runid發送給主庫。主庫根據runid判斷能否進行部分複製:

如果從庫保存的runid與主節點現在的runid相同,說明主從庫之前同步過,主庫會根據offset偏移量之後的數據判斷是否執行部分複製,如果offset偏移量之後的數據仍然都在複製積壓緩衝區裏,則執行部分複製,否則執行全量複製;如果從庫保存的runid與主庫現在的runid不同,說明從庫在斷線前同步的redis節點並不是當前的主庫,只能進行全量複製。

-如果從服務器以前沒有複製過任何主服務器,或者之前執行過SLAVEOF no one命令,那麼從服務器在開始一次新的複製時將向主服務器發送PSYNC ? -1命令,主動請求主服務器進行完整重同步(因爲這時不可能執行部分重同步);

- 相反地,如果從服務器已經複製過某個主服務器,那麼從服務器在開始一次新的複製時將向主服務器發送PSYNC <runid> <offset>命令:其中runid是上一次複製的主服務器的運行ID,而offset則是從服務器當前的複製偏移量。

接收到這個命令的主服務器會通過這兩個參數來判斷應該對從服務器執行哪種同步操作,如何判斷已經在介紹runid時進行詳細說明。

根據情況,接收到PSYNC命令的主服務器會向從服務器返回以下三種回覆的其中一種:

(1)如果主服務器返回+FULLRESYNC <runid>  <offset>回覆,那麼表示主服務器將與從服務器執行完整重同步操作:

       其中runid是這個主服務器的運行ID,從服務器會將這個ID保存起來,在下一次發送PSYNC命令時使用;

       而offset則是主服務器當前的複製偏移量,從服務器會將這個值作爲自己的初始化偏移量。

6380脫離6379之前,6379的信息

6379的runid:e2bc3db13b3e91ec2ea0daf130768fc8184b8052

 

6380脫離6379之前,6380的信息

 

脫離6379的從服務

127.0.0.1:6380> slaveof no one

6380脫離6379之後的信息

Master_replid2變爲了原來6379的runid:e2bc3db13b3e91ec2ea0daf130768fc8184b8052

Master_replid爲新的runid

 

6379修改爲6380的從數據庫

127.0.0.1:6379> slaveof 127.0.0.1 6380

OK

6379作爲6380的從數據庫信息,

Master_replid2變爲6379原來自己的runid:e2bc3db13b3e91ec2ea0daf130768fc8184b8052

Master_replid爲新的runid,也是6380的runid:bef8fdd800a980a50d4806833eb428bf96e77d63

 

爲了觸發全量複製,這裏將複製積壓緩衝區的大小設置成了較小的大小

127.0.0.1:6380> config set repl-backlog-size 36172

接下來使用telnet模擬一個從數據庫,

[root@centos7 ~]# telnet 127.0.0.1 6380

Trying 127.0.0.1...

Connected to 127.0.0.1.

Escape character is '^]'.

PING

+PONG

當主數據庫回覆+PONG

鍵入:PSYNC e2bc3db13b3e91ec2ea0daf130768fc8184b8052 1

發現主數據庫執行了全複製

 

(2)如果主服務器返回+CONTINUE回覆,那麼表示主服務器將與從服務器執行部分同步操作,

        從服務器只要等着主服務器將自己缺少的那部分數據發送過來就可以了;

模擬主數據庫此時offset爲7226,然後我們鍵入

set hello world

set msg hell

set msg2 hi

 

使用telnet模擬從數據庫增量複製主數據庫的數據

首先連接到主數據庫鍵入命令PING,主數據庫返回PONG

連接後,我們鍵入PSYNC e2bc3db13b3e91ec2ea0daf130768fc8184b8052 7226

我們發現主數據返回+CONTINUE,說明使增量複製,接下來我們看到了

*3

$3

set

$5

hello

$5

World

確實是增量複製

 

(3)如果主服務器返回-ERR回覆,那麼表示主服務器的版本低於Redis 2.8,它識別不了PSYNC命令,

    從服務器將向主服務器發送SYNC命令,並與主服務器執行完整同步操作。

由此可見psync也有不足之處,當從庫重啓以後runid發生變化,也就意味者從庫還是會進行全量複製,而在實際的生產中進行從庫的維護很多時候會進行重啓,而正是有由於全量同步需要主庫執行快照,以及數據傳輸會帶不小的影響。因此在4.0版本,psync命令做了以下改進。

redis4.0新版本除了增加混合持久化,還優化了psync(以下稱psync2)並實現即使redis實例重啓的情況下也能實現部分同步,下面主要介紹psync2實現過程。psync2在psync1基礎上新增兩個複製id(可使用info replication 查看):

 

master_replid:一個長度爲41個字節(40個隨機串+’0’)的字符串,每個redis實例都有,和runid沒有直接關聯,但和runid生成規則相同。當實例變爲從實例後,自己的master_replid會被上一個主實例的master_replid覆蓋。

master_replid2:復默認初始化爲全0,用於存儲上次主實例的master_replid。

在4.0之前的版本,redis複製信息完全丟失,所以每個實例重啓後只能進行全量複製,到了4.0版本,仍然可以使用部分同步,其實現過程:

1、存儲複製信息

redis在關閉時,通過shutdown save,都會調用rdbSaveInfoAuxFields函數,把當前實例的repl-id和repl-offset保存到RDB文件中,當前的RDB存儲的數據內容和複製信息是一致性的可通過redis-check-rdb命令查看。

2、重啓後加載RDB文件中的複製信息

redis加載RDB文件,會專門處理文件中輔助字段(AUX fields)信息,把其中repl_id和repl_offset加載到實例中,分別賦給master_replid和master_repl_offset兩個變量值,特別注意當從庫開啓了AOF持久化,redis加載順序發生變化優先加載AOF文件,但是由於aof文件中沒有複製信息,所以導致重啓後從實例依舊使用全量複製!

3、向主庫上報復制信息,判斷是否進行部分同步

從實例向主庫上報master_replid和master_repl_offset+1;從實例同時滿足以下兩條件,就可以部分重新同步,否則執行全量同步:

滿足部分重新同步的兩個必要條件:

1)、從實例上報master_replid串,與主實例的master_replid或master_replid2有一個相等,用於判斷主從未發生改變;

2)、從實例上報的master_repl_offset+1字節,還存在於主實例的複製積壓緩衝區中,用於判斷從庫丟失部分是否在複製緩衝區中;

psync2除了解決redis重啓使用部分同步外,還爲解決在主庫故障時候從庫切換爲主庫時候使用部分同步機制。redis從庫默認開啓複製積壓緩衝區功能,以便從庫故障切換變化master後,其他落後該從庫可以從緩衝區中獲取缺少的命令。該過程的實現通過兩組replid、offset替換原來的master runid和offset變量實現:

  1. master_replid和master_repl_offset:如果redis是主實例,則表示爲自己的replid和複製偏移量; 如果redis是從實例,則表示爲自己主實例的master_replid和同步主實例的複製偏移量。
  2. master_replid2和second_repl_offset:無論主從,都表示自己上次主實例master_replid和複製偏移量;用於兄弟實例或級聯複製,主庫故障切換psync。
  3. 判斷是否使用部分複製條件:如果從庫提供的master_replid與master的master_replid不同,且與master的master_replid2不同,或同步速度快於master; 就必須進行全量複製,否則執行部分複製
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章