對于大型企業(yè)來說,一臺 Redis 實例要保證可用性,往往會配置主從庫。這一點上其實和 MySQL 是一樣的,我們絕大部分的業(yè)務需求通常的情況都是讀多寫少。在這種情況下,合理的分攤讀庫請求,就可以大大加強請求的響應速度。對比 MySQL 來說,Redis 的主從復制簡單的沒朋友。
對于我們測試學習來說,一般不會去建立多個虛擬機或者 Docker ,更不可能去買多臺真正的服務器來練習。大家一般都是多開幾個實例,只要端口號不重復就行了。因此,我們需要多建兩個配置文件,區(qū)分開端口號。
// vim redis_80.conf
include /usr/local/etc/redis.conf
port 6380
pidfile "/var/run/redis_6380.pid"
dbfilename "dump6380.rdb"
默認的 Redis 端口號是 6379 ,為了測試方便,我們就新建兩個文件,一個用 6380 ,一個用 6381 。配置文件可以直接 include
默認的那個主配置文件,然后覆蓋寫上新的端口號就可以了。另外需要注意的是,pidfile 和 dbfilename 也要換個名字,要不就會共用 6379 的。6381 的配置文件也是類似的,這里就不粘出來了。然后我們啟動 6380 和 6381 。
// 服務端 6380
redis-server redis_80.conf
// 服務端 6381
redis-server redis_81.conf
如果你不想在控制臺看到啟動情況,或者說想讓實例在后臺運行的話,可以再加上下面這個配置。
// 配置文件
daemonize yes
然后使用客戶端進入 6380 和 6381 ,直接運行 SLAVEOF <host> <port>
命令,其中 host 指的是主庫的地址,port 指的是主庫的端口號。
// 客戶端 6380
? redis-cli -p 6380
127.0.0.1:6380> SLAVEOF 127.0.0.1 6379
OK
// 客戶端 6381
? redis-cli -p 6381
127.0.0.1:6381> SLAVEOF 127.0.0.1 6379
OK
好了,6380 和 6381 就是 6379 的兩臺從庫了。是不是很簡單,我們去驗證一下。
// 客戶端 6379
127.0.0.1:6379> INFO Replication
# Replication
role:master
connected_slaves:2
slave0:ip=127.0.0.1,port=6380,state=online,offset=252,lag=0
slave1:ip=127.0.0.1,port=6381,state=online,offset=252,lag=1
………………
127.0.0.1:6379> role
1) "master"
2) (integer) 1313
3) 1) 1) "127.0.0.1"
2) "6380"
3) "1313"
2) 1) "127.0.0.1"
2) "6381"
3) "1313"
在 6379 上,使用 INFO Replication
就可以看到當前的角色是 master ,有兩臺從庫,它們的狀態(tài)信息也都能看到。另外還有一個命令就是 role ,它也能看到當前的主從狀態(tài)信息。第一行是本機的角色,下面是從庫的情況。同樣的,在從庫上也能看到相關的信息。
// 客戶端 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:4
master_sync_in_progress:0
slave_read_repl_offset:378
slave_repl_offset:378
slave_priority:100
slave_read_only:1
………………
127.0.0.1:6380> role
1) "slave"
2) "127.0.0.1"
3) (integer) 6379
4) "connected"
5) (integer) 1341
好了,現(xiàn)在試試看主從之間有沒有真正的實現(xiàn)復制吧。
// 客戶端 6379
? redis-cli -p 6379
127.0.0.1:6379> set a 111
OK
// 客戶端 6380
? redis-cli -p 6380
127.0.0.1:6380> get a
"111"
可以看到,我們在 6379 添加了一個 a 的數(shù)據(jù),然后到 6380 可以 GET 到這條數(shù)據(jù)。嗯,目前來看主從配置是完全沒問題啦。
要說明的是,SLAVEOF 命令也可以配置到配置文件中,這樣實例一啟動就直接會進行主從連接。
當主庫和從庫建立起連接后,主庫會發(fā)送命令流來保持對從庫的更新,包括客戶端的寫入、key的過期等。如果從庫斷開連接,過一段時間又重新連接之后,會進行部分重同步,可能會丟失部分命令。如果無法進行部分重同步的話,從庫會請求進行全量同步,也就是主庫會把 rdb 整個發(fā)送給從庫。
整個同步復制的過程都是走的異步復制,具有低延遲和高性能的特點。從庫和從庫之間也可以建立主從關系,從而形成一個樹狀的同步結構,當最頂上的主庫掛了的時候,可以馬上選擇一臺擁有從庫的從庫來切換成主庫,從而保證服務提供的可用性。
如果想要高性能,我們其實可以這樣配置,那就是主庫不做持久化,而在從庫中選一臺出來做持久化。雖然這樣做可以獲取到很高的性能,但是,假如主庫掛了,那么當它重啟的時候,會是一個空的數(shù)據(jù)庫。這樣的空數(shù)據(jù)庫也會同步給從庫,從而導致所有數(shù)據(jù)全部被清空,這一點是需要注意的。特別是配置了一些自動重啟實例的工具的情況下,非常危險。
因此,還是更建議主從都開啟持久化,或者使用哨兵機制以及分布式的方式來進行部署,這兩塊我們后面的文章馬上就會學習到哦。
如果主庫有密碼,我們可以通過 masterauth <password>
這個命令或者直接在配置文件中寫上,來設置從庫連接到主庫之后使用的密碼。
SLAVEOF 命令已經是過時的命令了,在 Redis5 之后,更推薦使用的是 REPLICAOF <masterip> <masterport>
,參數(shù)和使用方式與 SLAVEOF 是一樣的。在現(xiàn)在的版本中,默認的配置文件中也只能搜到 REPLICAOF ,搜不到 SLAVEOF ,但是這個命令還是可以正常使用的。大家將來應用的時候,如果版本是在 Redis5 及以上的話,還是盡量使用新的命令哦。
Redis 的從庫不會主動清理過期的 Key ,這是為啥呢?因為要聽老大的嘛。當主庫的 Key 過期后,主庫會發(fā)送一個 DEL 給從庫,從而實現(xiàn)過期 Key 的刪除。
但是,主從多少會有延遲的嘛,所以從庫也有自己的一個邏輯時鐘用以報告不違反數(shù)據(jù)庫一致性的讀操作中存在的 Key 。也就是說,從庫雖然不會主動過期,但也會在自己的計時中標注當前這個 Key 是否已經過期,只不過,它不會真的刪除這個 Key ,而是要等待主庫的命令到達。如果客戶端請求,它將返回一個空值。在 Redis3.2 之前的版本中,從庫沒有自己的邏輯時鐘,這是一個大問題,所以老版本的小伙伴一定要注意(這個現(xiàn)在應該很少了吧)。
因此,要注意主從庫,特別是不在一臺服務器上的主從庫之間的時鐘同步性。
另外一種情況就是沒有設置過期時間的,就是主庫主動刪除的 Key 。只要是有同步,那么必然就會有延遲,因此,盡量將主從實例的主機部署在同一個機房,盡量的減少同步延遲就是最重要的步驟了,這一塊不僅是 Redis ,任何牽涉到主從復制這種機制的應用都會有這個問題,可以考慮應用層面上的代碼解決方案,更深層次上就是整體架構設計方面的問題了。這一塊咱的水平不夠,也說不出個所以然來,更重要的是,沒經歷過啊,中小型公司能將一臺 Redis 實例的極限性能能發(fā)揮出來的都少之又少,所以只能將來我們學習到相關架構實踐的時候再來說吧!
另外配置文件中的 repl-disable-tcp-nodelay 參數(shù)如果選擇 no 的話,可以降低主從延遲,但是會加大帶寬消耗,如果在一個機房是可以考慮關閉這個參數(shù)的,畢竟內網基本是不收費的,而且?guī)掃€大。
第一臺主庫會有一個 replication ID ,這是一個隨機字符串,在 INFO 中可以看到。
127.0.0.1:6379> info replication
………………
master_replid:1cf72f79da111329945c43855616ca5cf4b43f1a
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:0
second_repl_offset:-1
………………
同時也有一個偏移量 offset 。
主庫將自己產生的復制流發(fā)送給從庫的時候,發(fā)送多少個字節(jié)的數(shù)據(jù),自身的偏移量就會增加多少,目的就是當有新的操作修改數(shù)據(jù)時,可以以此更新從庫的狀態(tài)。這個其實就和 MySQL 主從中的 binlog 日志一樣,在配置主庫的時候,我們也會記錄當前的一個偏移量,然后等待從庫同步到與主庫相同的狀態(tài)時,才開放主庫的寫操作。一般也會通過偏移量來確定主從之間是否產生延遲。
接著,從庫通過 PSYNC 命令發(fā)送請求并獲得 replication ID 和 offset ,開始比較自己的同步情況,然后進行數(shù)據(jù)同步。這一塊我們可以通過 SYNC 命令看到。
127.0.0.1:6380> SYNC
Entering replica output mode... (press Ctrl-C to quit)
SYNC with master, discarding 14838 bytes of bulk transfer...
SYNC done. Logging commands from master.
"set","b","222"
"ping"
開啟 SYNC 命令之后,從庫會不停地發(fā)送 PING 命令,這時你可以在主庫上隨便進行一個操作,然后回到從庫,就可以看到 SYNC 也輸出了從主庫上獲得的命令信息。
默認情況下,Redis 中的從庫是只讀的,就像下面這樣。
// 客戶端 6380
127.0.0.1:6380> set a 123
(error) READONLY You can't write against a read only replica.
這是因為默認情況下,在配置文件中,有這樣一個配置 slave-read-only yes
,它的意思很明顯,就是從庫只能讀。我們可以修改配置文件,也可以動態(tài)修改它。
127.0.0.1:6380> config set slave-read-only no
OK
127.0.0.1:6380> set a 123
OK
127.0.0.1:6380> get a
"123"
// 客戶端 6379
? ~ redis-cli -p 6379
127.0.0.1:6379> get a
"111"
127.0.0.1:6379> set a 456
OK
// 客戶端 6380
? ~ redis-cli -p 6380
127.0.0.1:6380> get a
"456"
從上面的測試情況可以看出,將 slave-read-only 設置為 no 之后,從庫也可以寫數(shù)據(jù)了。但是主庫一旦修改了這條數(shù)據(jù),那么從庫還會跟著主庫變。
說實話,這一塊不要有太大別的想法,從庫做好自己的事就好了,要是從庫能夠任意修改,那么數(shù)據(jù)一致性就會出現(xiàn)大問題了。
除了提供讀庫的分攤流量職責之外,主從復制還有一個好處就是可以快速地進行主從切換,從而提高整個 Redis 實例的可用性,也就是我們常說的 主備架構 。將從機或者多個從機的某一臺從機當做備份機,如果主機崩掉了,可以馬上用備份機頂上。今天我們先來看看怎么手動實現(xiàn)主備切換。
// 關閉6379
? brew services stop redis
Stopping `redis`... (might take a while)
==> Successfully stopped `redis` (label: homebrew.mxcl.redis)
// 查看6380狀態(tài)
? redis-cli -p 6380
127.0.0.1:6380> role
1) "slave"
2) "127.0.0.1"
3) (integer) 6379
4) "connect"
5) (integer) -1
// 6380服務端日志
48041:S 22 Jun 2022 09:58:32.532 * Connecting to MASTER 127.0.0.1:6379
48041:S 22 Jun 2022 09:58:32.532 * MASTER <-> REPLICA sync started
48041:S 22 Jun 2022 09:58:32.532 # Error condition on socket for SYNC: Connection refused
在上面的操作中,當我們關閉了 6379 ,也就是主庫之后,從庫的狀態(tài)中,connect 變成了 -1 ,同時,服務實例輸出的日志也出現(xiàn)了報錯信息,表示當前的連接不可用。接下來,我們就趕緊把 6380 升級成主庫吧。
// 6380
127.0.0.1:6380> slaveof no one
OK
127.0.0.1:6380> role
1) "master"
2) (integer) 2826
3) (empty array)
使用命令 SLAVEOF no one
就可以將當前的從庫轉回一臺默認的普通實例,這時默認它就是主庫,不過是一臺還沒有從庫的主庫而已。接下來,把 6381 轉換成 6380 的從庫。
// 6381
127.0.0.1:6381> slaveof 127.0.0.1 6380
OK
127.0.0.1:6381> role
1) "slave"
2) "127.0.0.1"
3) (integer) 6380
4) "connected"
5) (integer) 2826
這個就不多說了,當 6379 恢復之后,我們再讓 6379 成為 6380 的主庫就好了。
應用程序上呢?這個真沒辦法,我們需要去修改連接信息,讓程序去連接新的主庫,從庫的配置也要修改。是不是很麻煩?別急,下一篇我們就來學習自動切換的哨兵機制。
今天的內容很簡單,因為確實 Redis 在主從配置這一塊非常方便。而且因為 Redis 本身就非??欤源蟛糠智闆r下我們不需要像 MySQL 那樣在新開從庫的時候要等從庫同步很久才能使用。它也是可以邊提供寫入服務,邊進行從庫同步的,如果確實主庫的數(shù)據(jù)量非常大,需要進行全量同步從庫的話,可以在主庫使用 WAIT
命令進行鎖寫等待。
好了,下一篇我們就要學習激動人心的主從高可用功能:哨兵機制 。別掉隊哦,進階系列的內容不多了,但后面可是篇篇精彩。
參考文檔:
https://redis.io/docs/manual/replication/