Redis 主從握手流程
在下方公眾號后臺回復:面試手冊,可獲取杰哥匯總的 3 份面試 PDF 手冊。
Redis是開源的key-value存儲系統(tǒng),可作為數(shù)據(jù)庫、緩存、消息組件。
Redis的作者是Salvatore Sanfilippo(網(wǎng)名為antirez),他在2009年開發(fā)完成并開源了Redis。
Redis由于性能極高、功能強大,迅速在業(yè)界流行,現(xiàn)已成為高并發(fā)系統(tǒng)中最常用的組件之一。
Redis提供了多種類型的數(shù)據(jù)結(jié)構(gòu),如字符串(String)、散列(Hash)、列表(List)、集合(Set)、有序集合(Sorted Set)等。
Redis還是分布式系統(tǒng),主從集群可以實現(xiàn)數(shù)據(jù)熱備份,哨兵(Sentinel)機制可以保證主從集群高可用,Cluster集群則提供了水平擴展的能力。
Redis還提供了持久化、Lua腳本、Module模塊、Stream消息流、Tracking機制等一系統(tǒng)強大功能,適用于各種業(yè)務(wù)場景。
Redis是一個典型的“小而美”的程序。
Redis實現(xiàn)簡單,源碼非常優(yōu)雅簡潔,閱讀起來并不吃力,而且Redis功能齊全,涵蓋了數(shù)據(jù)存儲、分布式、消息流等眾多特性,非常值得深入學習。
Redis中的一個重要概念就是主從復制機制。
下面詳細分析Redis主從復制機制中主從握手的過程。
Redis主從復制機制中有兩個角色:主節(jié)點與從節(jié)點。
主節(jié)點處理用戶請求,并將數(shù)據(jù)復制給從節(jié)點。
主從復制機制主要有以下作用:
數(shù)據(jù)冗余,將數(shù)據(jù)熱備份到從節(jié)點,即使主節(jié)點由于磁盤損壞丟失數(shù)據(jù),從節(jié)點依然保留數(shù)據(jù)副本。
讀/寫分離,可以由主節(jié)點提供寫服務(wù),從節(jié)點提供讀服務(wù),提高Redis服務(wù)整體吞吐量。
故障恢復,主節(jié)點故障下線后,可以手動將從節(jié)點切換為主節(jié)點,繼續(xù)提供服務(wù)。
高可用基礎(chǔ),主從復制機制是Sentinel和Cluster機制的基礎(chǔ),Sentinel和Cluster都實現(xiàn)了故障轉(zhuǎn)移,即主節(jié)點故障停止后,Redis負責選擇一個從節(jié)點切換為主節(jié)點,繼續(xù)提供服務(wù)。
下面將主從復制流程分為三個階段。
握手階段:主從連接成功后,從節(jié)點需要將自身信息(如IP地址、端口等)發(fā)送給主節(jié)點,以便主節(jié)點能認識自己。
同步階段:從節(jié)點連接主節(jié)點后,需要先同步數(shù)據(jù),數(shù)據(jù)達到一致(或者只有最新的變更不一致)后才進入復制階段。
Redis支持兩種同步機制:
全量同步:從節(jié)點發(fā)送命令PSYNC ? -1,要求進行全量同步,主節(jié)點返回響應(yīng)+FULLRESYNC,表明同意全量同步。隨后,主節(jié)點生成RDB數(shù)據(jù)并發(fā)送給從節(jié)點。這種方式常用于新的從節(jié)點首次同步數(shù)據(jù)。
部分同步:從節(jié)點發(fā)送命令PSYNC replid offset,要求進行部分同步,主節(jié)點響應(yīng)+CONTINUE,表明同意部分同步。主節(jié)點只需要把復制積壓區(qū)中offset偏移量之后的命令發(fā)送給從節(jié)點即可(主節(jié)點會將執(zhí)行的寫命令都寫入復制積壓區(qū))。這種方式常用于主從連接斷開重連時同步數(shù)據(jù)。如果offset不在復制積壓區(qū)中,那么主節(jié)點也會返回+FULLRESYNC,要求進行全量同步。
復制階段:主節(jié)點在運行期間,將執(zhí)行的寫命令傳播給從節(jié)點,從節(jié)點接收并執(zhí)行這些命令,從而達到復制數(shù)據(jù)的效果。Redis使用的是異步復制,主節(jié)點傳播命令后,并不會等待從節(jié)點返回ACK確認。異步復制的優(yōu)點是低延遲和高性能,缺點是可能在短期內(nèi)主從節(jié)點數(shù)據(jù)不一致。
本文中指的命令,包含命令名及執(zhí)行命令的參數(shù)。
PSYNC命令涉及以下屬性:
server.master_repl_offset:記錄當前服務(wù)器已執(zhí)行命令的偏移量。
server.replid:40位十六進制的隨機字符串,在主節(jié)點中是自身ID,在從節(jié)點中記錄的是主節(jié)點ID。
server.replid2:用于主節(jié)點,存放上一個主節(jié)點ID。
server.repl_backlog:復制積壓區(qū),主節(jié)點將最近執(zhí)行的寫命令寫入復制積壓區(qū),用于實現(xiàn)部分同步。
下面介紹一下Redis主從握手流程。
主從復制的機制是由從節(jié)點發(fā)起流程,我們可以發(fā)送REPLICAOF命令到某個服務(wù)器,要求它成為指定服務(wù)器的從節(jié)點:
REPLICAOF <masterip> <masterport>
或者在配置文件中添加配置REPLICAOF
提示:從Redis 5開始為SLAVEOF命令提供別名REPLICAOF,這兩個命令的作用一樣。
下面以從節(jié)點的視角,分析主從握手的過程。
從節(jié)點握手階段涉及以下屬性。
server.repl_state:用于從節(jié)點,標志從節(jié)點當前復制狀態(tài)。有如下值:
REPL_STATE_NONE:無主從復制關(guān)系。
REPL_STATE_CONNECT:待連接。
REPL_STATE_CONNECTING:正在連接。
…(部分握手狀態(tài)并沒有列出)
REPL_STATE_TRANSFER:從節(jié)點正在接收RDB數(shù)據(jù)。
REPL_STATE_CONNECTED:已連接,主從同步完成。
從節(jié)點使用replicaofCommand函數(shù)處理REPLICAOF命令。
該函數(shù)執(zhí)行如下邏輯:
(1)如果處理的命令是REPLICAOF NO ONE,則將當前服務(wù)器轉(zhuǎn)換為主節(jié)點,取消原來的主從復制關(guān)系,退出函數(shù)。
(2)調(diào)用replicationSetMaster函數(shù),與給定服務(wù)器建立主從復制關(guān)系。
另外,我們在配置文件中配置REPLICAOF
從節(jié)點server.repl_state進入REPL_STATE_CONNECT狀態(tài)后,主從復制流程已經(jīng)開始。
serverCron時間事件負責對REPL_STATE_CONNECT狀態(tài)進行處理:
int serverCron(struct aeEventLoop *eventLoop, long long id, void *clientData) {
...
if (server.repl_state == REPL_STATE_CONNECT) {
if (connectWithMaster() == C_OK) {
serverLog(LL_NOTICE,"MASTER <-> REPLICA sync started");
}
}
}
調(diào)用connectWithMaster函數(shù)進行處理,該函數(shù)負責建立主從網(wǎng)絡(luò)連接:
int connectWithMaster(void) {
// [1]
server.repl_transfer_s = server.tls_replication ? connCreateTLS() : connCreateSocket();
// [2]
if (connConnect(server.repl_transfer_s, server.masterhost, server.masterport,
NET_FIRST_BIND_ADDR, syncWithMaster) == C_ERR) {
...
return C_ERR;
}
// [3]
server.repl_transfer_lastio = server.unixtime;
server.repl_state = REPL_STATE_CONNECTING;
return C_OK;
}
【1】創(chuàng)建一個Socket套接字。connCreateTLS函數(shù)創(chuàng)建TLS連接,connCreateSocket函數(shù)創(chuàng)建TCP連接,它們都返回套接字文件描述符。該連接是主從節(jié)點網(wǎng)絡(luò)通信的連接,本書稱之為主從連接。
【2】connConnect函數(shù)負責連接到主節(jié)點,并且在連接成功后調(diào)用syncWithMaster函數(shù)。
【3】從節(jié)點server.repl_state進入REPL_STATE_CONNECTING狀態(tài)。
網(wǎng)絡(luò)連接成功后,從節(jié)點調(diào)用syncWithMaster函數(shù),進入握手階段:
void syncWithMaster(connection *conn) {
char tmpfile[256], *err = NULL;
int dfd = -1, maxtries = 5;
int psync_result;
...
// [1]
if (server.repl_state == REPL_STATE_CONNECTING) {
connSetReadHandler(conn, syncWithMaster);
connSetWriteHandler(conn, NULL);
server.repl_state = REPL_STATE_RECEIVE_PONG;
err = sendSynchronousCommand(SYNC_CMD_WRITE,conn,"PING",NULL);
if (err) goto write_error;
return;
}
...
// [2]
if (server.repl_state != REPL_STATE_RECEIVE_PSYNC) {
goto error;
}
// more
}
【1】根據(jù)server.repl_state狀態(tài),執(zhí)行對應(yīng)操作。
從節(jié)點發(fā)送給主節(jié)點的信息,主節(jié)點會記錄在從節(jié)點客戶端,并在INFO命令中輸出這些信息。另外,Sentinel模塊需要從主節(jié)點INFO命令響應(yīng)中獲取這些從節(jié)點信息。
【2】執(zhí)行到這里,主從握手階段已經(jīng)完成。server.repl_state必須處于REPL_STATE_ RECEIVE_PSYNC狀態(tài),否則報錯。
下面使用Linux tcpdump工具抓取主從連接報文,分析主從節(jié)點握手階段的通信內(nèi)容(主節(jié)點端口為6000):
tcpdump tcp -i lo -nn port 6000 -T RESP
tcpdump支持RESP協(xié)議,最后一個選項-T RESP要求tcpdump以RESP協(xié)議格式解析報文。
其中6000端口為主節(jié)點端口,60374端口為從節(jié)點通信端口。從tcpdump的輸出可以清晰地看到主從節(jié)點在握手階段的通信內(nèi)容。
提示:tcpdump解析后的RESP內(nèi)容并不會展示數(shù)據(jù)類型的標志符,如主節(jié)點對從節(jié)點PING命令的響應(yīng)實際上是“-NOAUTH Authentication required.”,請讀者閱讀源碼時注意。
以主節(jié)點視角分析握手階段,主節(jié)點不斷處理來自從節(jié)點的命令(包括PING、AUTH、REPLCONF),感興趣的讀者可自行閱讀代碼。
Redis主從握手流程到此就分析完畢了。
本書深入地分析了Redis核心功能的內(nèi)部機制與實現(xiàn)方式,大部分內(nèi)容源自對Redis源碼的分析,并從中總結(jié)出實現(xiàn)原理。通過閱讀本書,讀者可以快速、輕松地了解Redis的內(nèi)部運行機制。
贈書規(guī)則:為本文「點贊」+ 「在看」 +「留言」且與文章內(nèi)容相關(guān)的優(yōu)質(zhì)留言即可上墻并從所有留言中選出3位點贊最高的讀者留言將各獲得一本。
截止時間:2021年8月31日,晚 20:00
領(lǐng)書須知:提供點贊、在看的截圖
注意事項:最終獲贈者請在24小時以內(nèi)添加我的微信,備注:贈書??
推薦閱讀
大廠面試!我和面試官之間關(guān)于Redis的一場對弈!
2020 年最新版 68 道Redis面試題,20000 字干貨,趕緊收藏起來備用!

